第六課 - Dojo|Starknet開發實戰訓練營二期

TinTinLand
5 位粉絲
2 觀看·2025年1月1日
哈嘍,大家好,這節課我們進入DOJO的學習
首先,DOJO是一個社區驅動的開源項目
我們把它描述為一個可驗證的遊戲引擎
和一個全面的工具包,用於構建遊戲
DOJO目前已經到了一個相對穩定的版本
大約構建了兩年多的時間了
經歷了多次迭代,語法也經歷了很大的改動
DOJO是一個基於Caro的遊戲框架
提供了一整套的工具鏈
藉助DOJO可以省去編寫複雜的索引器
管理複雜模式或設置查詢系統的麻煩
包含的工具,例如Soro,Katana,Torrid
DOJO提倡採用更加模塊化的方法來構建應用程序
在DOJO之前,每次嘗試製作鏈上遊戲
你必須自己構建軟件堆站
包括設計自己的索引器、合約架構和客戶端
沒有一個一致的包可以打包所有這些功能
並提供良好的開發者體驗
因此,每次想做一個遊戲都需要重新創建一個引擎
你會花費很多重複構建相同東西的時間
這也是DOJO這個引擎出現的目的
包括像MOD和其他一些引擎
都是用來解決這些問題
關於全鏈遊
鏈上遊戲實際上就是一個遊戲
其狀態和邏輯完全在公共區塊鏈上定義
整個遊戲由智能合約定義
與傳統遊戲不同
鏈上遊戲的邏輯和狀態完全在公共網絡上
這意味著任何人都可以與之交互
創建自己的客戶端
這種設計為遊戲設計帶來了全新的可能性
關於可證明的遊戲
我們使用的是Caro對戰
這對於可驗證遊戲很重要
可驗證遊戲是指基於靈知識虛擬機設計的遊戲
其執行可以被驗證
因此他們是用Caro和其他靈知識語言編寫
這使得我們可以將大量計算從公共網絡轉移到客戶端設備
從而為遊戲開發提供更多的算力
關於自主世界
如果你是第一次聽說
自主世界實際上就是一個沒有單一權威的遊戲協議
開發者不是遊戲的唯一控制者
自主世界隨著玩家的變化而進化
最簡單的例子就是以太坊本身
它是一個用最小約束級的自主世界
這些約束就是EVM
也就是指的以太坊的虛擬機
如果你遵守EVM的規則
就能在其上做很多事情
下面我們講一下鏈遊的發展史
鏈上遊戲的歷史可以追溯到Dark Forest
這是第一個將整個遊戲狀態和邏輯放在鏈上的遊戲
激發了開發者的興趣
催生了鏈上遊戲行業
開發鏈上遊戲的原因很多
包括可組合性、模塊化等
有興趣的同學可以去看一看Dark Forest
它是一個最早的鏈上遊戲
是一個開發者優先的遊戲
第一個將整個遊戲放在鏈上
並將其公開
圍繞它形成了一個插件生態系統
鏈上遊戲是一種全新的設計商業模式和遊戲玩法
全鏈遊戲與Web 2.5遊戲是完全不同的
那麼我說的Web 2.5遊戲是指那些邏輯在鏈下運行
僅使用鏈上的一些資產的遊戲
而真正有趣的是將整個遊戲放在鏈上
這樣可以創造出全新的商業模式
之所以這樣做的原因之一
是區塊鏈是完美的託管和仲裁工具
你可以將它用於遊戲託管
也可以在交易發生時向10個人進行小額支付
有很多不同的方式
隨著低成本的Layer 2的興起
所有這些都變得可能
這可以打開全新的商業模式
創造出可組合性的遊戲
那麼我們為什麼要做鏈遊
首先是可組合性和插件
我們在同一條鏈上去構建
一切都是共享的狀態
這相當於每個遊戲提供一個開放的API
你可以創造這種可組合的遊戲環境
也可以創造一個開放的插件環境
因為人們可以拿你的遊戲創建一個不同的客戶端
或略有不同的版本
從而形成一個全新的遊戲
其實像反恐精英和Dota
都是由遊戲模組演變而來的
它們並不是開發者最初想到的展現形式
另一個重要的原因是區塊鏈帶來的確定性
人們希望知道他們的在遊戲中做的事情不會被取消
鏈上遊戲中作弊是不可能的
因此你可以輕鬆的在玩家之間轉移資產
並創造出一些高風險的博弈性的遊戲
鏈上的遊戲可以分為三類
短期體驗性遊戲
基於回合的遊戲
如策略遊戲和桌面遊戲
以及自主世界
短期體驗遊戲成本低易於上手
通常包含代幣循環
可以從這種遊戲開始
去熟悉整個鏈遊的開發過程
那麼我們為什麼要使用DOJO去開發一個全鏈遊
DOJO的建立是為了真正的加速這些遊戲的開發
這是它主要的目標
我們使用DOJO可能只需要幾天
就可以開發出一款遊戲
它標準化了鏈上遊戲的開發體驗
所以如果你知道如何開發一個DOJO遊戲
就可以很快上手修改其他遊戲
因為它們使用相同的技術站
另外DOJO通過模型來存儲狀態
並標準化了狀態的存儲
因此你不必要再考慮如何在合約中構建你的狀態
DOJO也帶來了很多開發者工具來改進開發體驗
最重要的是它最小化了樣板代碼
所以你不需要重寫內容
它有很多紅可以減少代碼量
DOJO的工作方式是擴展了Carol的編譯器
像編譯器注入紅
這樣你可以寫一些簡短的函數來節省時間
並保持代碼整潔
其架構可以看作是一個世界合約
你不需要再碰這個合約
世界合約中包含了你的模型和系統
以及相互授權的關係
一切都存在這裡
並隨時可以進行查詢
這裡我簡單提一下剛才提到的模型和系統
分別指DOJO裡的model和system
後面會詳細的去講解
首先model模型可以理解為DOJO的一個結構體
在模型中
你可以將其視為建制隊的存儲
建是被hash後的
用於在數據庫中引用模型
而system系統就是一個普通的合約
同時需要添加一些DOJO合約的屬性
因此編譯器會注入一些內容加快我們的開發
關於DOJO的工具鏈
今天我們將會學習如何使用Sorocatana和Toray
首先Catana是一款高性能的排域器
通過一個簡單的命令
就可以在本地啟動一個節點
Catana的速度很快且易於使用
Toray是一個索引器
可以自動生成GraphQL和JPG API
以進行實時的狀態更新
它設計的目的是使查詢和訂閱鏈上狀態變得更加容易
而Sorocatana是一個命令行工具
用於規劃和管理智能合約的部署升級和維護
那麼好
下面我們將去演示如何去安裝DOJO
以及如何編譯部署和進行交互
本節課呢
我還是建議使用ASDF對DOJO的版本進行管理
如果是在Windows上建議使用WSL進行開發
在安裝DOJO之前
首先你本地需要安裝好了Gate, Rust和Scrub
那麼第一步我們需要去添加ASDF的插件
用asdf-plugin-add-dojo指向DOJO的GitHub地址
添加完成以後
我們使用asdf-install-dojo-latest去安裝DOJO的最新版本
我們可以使用asdf-lit-dojo去查看DOJO的歷史版本
那麼現在最新的版本就是1.0.9
接著我們使用Sorocatana這個命令行工具去初始化一個項目
我們用Sorocatana
我先去刪除掉我本地的
那麼我們用sozo-initial-dojo-starter進入根目錄
在根目錄下面我們去配置一下使用的DOJO版本
local-dojo-1.0.9
好,接下來用IDE打開我們的項目
首先我們去查看一下DOJO starter這個項目的結構
DOJO的項目與典型的Caro項目非常類似
主要區別在於包含DOJO特定的紅
先打開scab-tomo這個文件
這是一個配置文件
定義了項目的依賴、原數據和其他的配置
那麼這裡的依賴項使用的是1.0.2的版本
我們可以把它改成我們新裝的版本1.0.9
這個版本指的是你項目自身的版本號的迭代
不改也可以
然後我們看一下stos這個文件夾底下的models文件
在models下面我們定義了DOJO需要的模型
通過使用dojo-model這個屬性
告知DOJO編譯器這是一個model
在model裡面player字段有一個屬性k
表示這個model是由player字段進行索引
意味著需要使用player字段去檢索這個model
我們還在這個model裡面定義了其他字段
remaining last direction還有can't remove
這裡player remaining can't remove都是carrow的基本類型
而last direction我們使用了一個自定義的結構體
它是一個每舉類型包含五個遍體
需要注意的是model所有的字段的類型
必須派生introspect這個trait也就是特徵
另外一個點需要注意的是
然後我們看一下這個position這個model
它也是由player字段索引
並包含一個自定義的結構體vega2
那麼這個自定義的數據結構體同樣的派生出了introspect
而這裡使用的是introspect packet
它的目的是為了節省空間
包含兩個字段x和y分別是32位的無符號整形
下面我們看一下dojo的system
在systems這個文件夾下定義了一個actions文件
通過添加dojo contract的屬性
去告知dojo編譯器這是一個system
它其實就是一個常規的stacknet合約
同樣我們需要使用stacknet interface結構
去向外界暴露我們需要執行的函數
這裡暴露的兩個函數分別是spawn和move
參數我們使用的是ref self 翻新key
ref這裡表示我們可以通過這個函數改變合約狀態的值
接下來我們看一下該函數的具體實現
首先我們拿到這個word變量
word在dojo中是一個管理system和model的中間橋樑
我們使用word read model
可以查詢到對應該player的position這個model的值
而當我們需要更新或新建一個實例時
需要使用word write model
在這個函數裡我們通過get color address
去獲取調用該合約的賬戶的地址
通過read model得到該玩家的position model的值
在dojo中如果該玩家沒有實例過這個model的實例
那麼他除了k以外的值都是默認值0
因此對於position回到我們在model的定義
當我們第一次調用spawn這個函數的時候
他返回的x和y都是0
在這裡當我們新建一個position實例的時候
position vacx這個值就是0
vacy也是0
所以調用完spawn以後
player的x和y都是10
然後這裡我們去修改狀態
接下來對於move這個model
我們給他一個猝死值remaining 100
last direction設置到null這個變體
commove設置為true
然後用read model新建或修改該model的狀態
這個函數有一個問題
就是我們可以多次去調用spawn這個函數
而每次調用spawn函數
我們的position都會在x和y軸上去分別加10
後面我們去實際運行的時候可以去觀察這個值的變化
接下來我們看一下move這個函數
在執行振動的邏輯之前
我們同樣使用read model
去獲取該player的position和move這兩個model的實例
然後將move的remaining-1
更新他的last direction
到我們傳入的direction這個變量
使用next position方法去更新position的位置
這裡是我們定義的next position這個函數
緊接著去更新這兩個實例的值
最後我們發出了一個自定義的event
而event定義moved在這個地方
通過dojoevent這個屬性去告知dojo編譯器
自定義結構題move的是一個event
event的處理方式非常類似於model
發出的event和model的更新一樣都會被tory進行索引
這個代碼實例非常簡單
非常易於我們數學者進行學習
總結一下model就像結構化數據庫的條目一樣
管理和組織鏈上的數據
它和常規的orm非常類似
而system它實現了業務邏輯和狀態的更改
和常規的karo合約很像
但是有一些語法糖
word也就是這裡的word
它是連接所有model和system的中心樞紐
word合約可以確保一次性
管理授權
並協調應用程序不同部分之間的交互
下面我們就嘗試構建和運行這個dojo項目
首先使用
首先使用solo build去編譯項目
接下來我們準備將其部署到katana
那麼我們需要在本地去運行一個katana
我們新建一個terminal
然後使用katana
dev
devnode
這裡命令的含義是
首先我們使用--dev
去啟動katana的一個開發者模式
然後使用nofe去指定
我們在katana上的所有操作
都是不需要收取開支費的
點擊回收運行
運行以後可以看到我們的RPC的server
是在5050這個端口上
而且有一些預部署的賬戶地址可以供我們使用
那麼接下來在安裝和運行過程當中
一個非常重要的文件是這個dojo-dev
這裡可以看到我們配置的env
RPC的url指向了我們本地運行的katana
account.js和privatekey
分別對應一個已經預先部署的賬戶地址
那這裡對應的實際上就是第一個
如果你在運行katana的時候沒有這個地址
我們可以從中任意複製一個到這個地方來
這裡writer指的是我們將授予action
也就是actions這個system
所有model的狀態更新和創建權限
好,運行katana以後
我們回到最初的terminal
通過sozomegrate這個命令
去部署我們的遊戲
部署完成以後我們可以看到
生成的word或者dress是這個
那我們複製一下
並且在dojo-dev-tomo下去修改這個詞
現在整個項目已經部署到了katana
然後我們就希望使用torrid
索引器去索引整個項目的狀態更新
使得客戶端可以輕易的去查詢狀態變化
那麼我們就需要去運行一個torrid索引器
新建一個terminal
我們使用命令torrid word地址回測
好,torrid已經運行了
接著我們使用自動部署的graphql服務
去在瀏覽器上查看一下
該服務使用SQLite作為數據庫
我們通過這個地址進行訪問
torrid會自動將你的數據組織到表中
然後輕鬆的使用graphql執行查詢
那麼這裡我們就演示一下
如何去查詢move和position
這兩個model的狀態變化
使用這個query運行
position現在是沒有任何的值的
新建一個
我們再看一下move
move也沒有任何的值
好,接下來我們去執行一下合約暴露的函數
回到最初的terminal
看一下我們的action
我們先去執行spawn這個函數
運行命令sojo
execute sojo starter
sojo starter是我配置的namespace
然後actions
就是我們的合約
然後執行spawn
好,返回transaction哈希以後就說明執行成功了
那麼再回到我們的graphql去查看一下
首先運行move
這裡已經能看到我們的player這個地址
新建了一個move的實例
而它的值也是我們在初始化中
初始後的值
然後看一下position
這裡就是剛才講到的
它的x和y值
在第一次執行spawn的時候
分別會變成10和10
當我們執行第二次spawn的時候
它應該會變成20和20
我們再執行一次看一下
再查一下
值變成了20和20
接下來我們執行一下這個move函數
一旦執行move以後
x和y的值會根據我們移動方向的變化
加1或減1
同樣我們執行一下move函數
把spawn改成move
這裡有一個錯誤
是因為我沒有傳入參數
那我們需要傳入的參數是direction
在models裡面
direction的定義是一個enum
它分別有五個變體
當我們使用call data去傳入參數的時候
分別可以使用0,1,2,3,4去代表
none,left,right,up,down
我們先試一下none
call data none是0
好,執行完成
那麼如果我們call data是0的話
我們應該沒有向任何方向移動
但是我們的remaining會減1
那現在我們去驗證一下
ok,沒有向任何地方移動
那麼在move裡
我們的remaining由100變成了99
並且我們的last direction方向的值是none
那假設我們把call data換成1
remaining變成了98
而我們的方向變成了left
那麼向左移動
x值應該減1
回到這兒
我們運行一下
x變成了19
課下同學們可以根據今天的演示
去嘗試運行各個工具
像katana和tori
以及如何查詢
甚至我們可以使用dojo提供的
一些全端的SDK
去配合全端完成一些遊戲的開發
像dojoJS或者是unity
然後我們講一下
如何在dojo裡對合約進行單元測試
或者是集中測試
回到我們的合約
在source文件夾下
有一個test文件夾
打開這個testword文件
示例項目已經提供了一個測試文件
它包含兩個測試
而且在models下面也包含兩個測試
models下的測試比較簡單
主要是針對我們定義的model
進行簡單的合理化的測試
比如對position這個model
我們可以通過這個測試
去驗證它的as echo這個方法
我們將著重去講解一下
如何去寫集成測試
去模擬我們真實的
在調用合約暴露的函數的時候
所產生的結果
在已有四個測試的前提下
我們可以通過運行solo test
去看一下結果
那麼測試是通過了
在寫testing之前
我們首先需要確保
在scab tomo文件夾下面
我們引入了dojo carotest
因為我們需要使用
它提供的一些函數
回到test world
我們將使用namespace def
去定義我們要使用的namespace
dojo將為每個model
或者invent生成contract
生成的contract名稱
是結構體的名稱
分別以m下劃線或e下劃線打頭
這裡將它們全部印射到了
dojo下劃線starter這個namespace
需要注意的是
相同的模型或時間類型
可以多次註冊到不同的namespace
接著我們使用contract def treat
去定義system的合約
以及該system的權限和儲存化數據
這裡的權限是有寫入的權限
對dojo starter這個namespace下面的所有資源
我們已經註冊的model
都可以通過actions這個system進行寫入操作
因此我們actions下面的move和spawn方法
就可以對position和move這兩個model進行寫入
有了namespace的定義和contract的定義
我們就可以通過函數spawn test world
返回一個world實例
通過world實例
我們進行如何寫的操作
這裡繞過了system
直接對某個model的狀態進行改變
我們可以通過write model test
來繞過權限檢查
下面我帶大家做一個測試
我們可以自己再添加一個測試
test
我們可以test spawn這個函數
首先獲取默認的caller
.net contract address cost
定義namespace
獲取world實例
初始化system合約
以及權限控制
然後我們去拿actions這個system的contractor
.net contract address
等於world.ts
用add actions
.unwrap
拿到address以後
我們用action system等於
action dispatcher
然後傳入contractor address
接著我們可以使用action system.spawn
運行完成以後
我們需要去判斷我們運行結果
那麼我們用assert
先確認一下結果
當第一次spawn的時候
player的position的值
x和y都應該是10
他的move的值remaining是100
last direction是null
他move是true
那麼我們先read model move
move world read model
拿入caller
assert move
move的remaining
應該是等於100
否則那是move
move is wrong
assert move's last direction
應該等於direction
冒號冒號
null
否則
我們說last direction
is wrong
然後我們再去獲取position
等於world read model
caller
assert position
.y.x
應該等於10
否則x is wrong
assert position.y.y
也等於10
否則y is wrong
在開始的時候
我講過這個試例程序
是允許用戶多次執行spawn這個函數的
所以再次實行spawn以後
他的x和y會分別加10
那我們再執行一次
action system.spawn
這個函數
然後我們再獲取一下position
read model
caller
這時候position.y.x
應該等於20
x is wrong
assert position.y.y
等於20
否則y is wrong
好,接下來我們去運行一下這個測試
使用sozotest
遇到一個錯誤
那這裡light
然後是contract address const
contract address const
這是兩個冒號
好,現在是五個測試並且全部通過
我非常推薦大家去做面向測試的開發
保持代碼的正確性和健壯性
通過測試來模擬更多的邊界條件
那麼今天的課程就到這裡
謝謝大家的觀看,再見
