此教學介紹一個數據包製作的實例,閱讀此教學前請先確保自己掌握教學/製作數據包和教學/製作資源包的知識。了解命令(尤其是平坦化後的命令/execute、命令/data)、計分板、原始JSON文字等頁面也會有所幫助。
目標[]
動手製作一個數據包前,你應該有一個明確的目標。也許你已經閱讀過很多命令教學,但是卻不知道如何有機地整合這些知識。在本實例中,我們將製作一個數據包,使得玩家能夠得知他視線所指的蜂巢內有多少只成年蜜蜂,有多少只幼年蜜蜂。
本實例的GitHub地址:點擊這裏
開始[]
讓我們來分解一下需求吧。
「使得玩家能夠得知他視線所指的蜂巢內有多少只成年蜜蜂,有多少只幼年蜜蜂。」
「視線所指」[]
首先是視線所指。在17w50a中,Minecraft加入了局部座標這一概念。局部座標(玩家座標系,或者攝像機座標系)是一個以玩家頭部為原點,玩家的視線(注意,和世界座標系並不一定對齊)為「前軸」(局部座標的第三個參數),三維中兩個垂直於前軸,正方向分別指向玩家頭部的左方和上方的軸為「左軸」和「上軸」(局部座標的第一、二個參數)的空間系。舉例來説,^ ^ ^1
就是玩家視線前方一個方塊長度的位置。如果蜂巢就在玩家視線前方一個方塊長度的位置,那麼執行命令/data get block ^ ^ ^1
就可以得到這個蜂巢的數據了。但由於我們並不知道蜂巢到玩家頭部的距離,因此我們不能直接寫一個固定的座標,而是需要以某種方式得到蜂巢的位置。
「多少」[]
通常情況下,一隻蜂巢中至多只有3隻蜜蜂。因此,在這種小數量級的情況下,我們可以進行窮舉。
我們先看看蜂巢的方塊實體數據。
方塊實體[]
蜂巢和蜂箱有與之關聯的方塊實體,用於儲存附加資訊。
- 方塊實體數據
- FlowerPos:(可選)儲存花的位置,以便其他蜜蜂能夠找到。
- X:花的X座標。
- Y:花的Y座標。
- Z:花的Z座標。
- Bees:巢內目前存在的實體。
- :巢內的單個實體。
- MinOccupationTicks:該實體會在巢內滯留的最短時間(刻)。
- TicksInHive:該實體在巢內已滯留的時間(刻)。
- EntityData:巢內該實體的NBT數據。
- 見實體格式,不包含 UUID標籤。
- :巢內的單個實體。
- FlowerPos:(可選)儲存花的位置,以便其他蜜蜂能夠找到。
基岩版:
- 方塊實體數據
- 方塊實體共通標籤
- ShouldSpawnBees:生成新蜜蜂時值為true。
- Occupants:(可能不存在)蜂巢或蜂箱內的實體。
- :蜂巢或蜂箱內的具體實體。
- ActorIdentifier:蜂巢或蜂箱蜂箱內的具體單個實體。在原版遊戲下總會是
minecraft:bee<>
。[需要更多資訊] - TicksLeftToStay:實體離開蜂巢或蜂箱蜂箱前的時間(刻)。
- SaveData:蜂巢或蜂箱蜂箱內實體的NBT數據。
- 實體共通標籤
- 此實體種類獨有的標籤。
- ActorIdentifier:蜂巢或蜂箱蜂箱內的具體單個實體。在原版遊戲下總會是
- :蜂巢或蜂箱內的具體實體。
觀察到,蜂巢內的蜜蜂數據都儲存在一個 Bees串列裏面。因此,結合NBT路徑的知識,我們在判定時只需要讀取Bees[0]
、Bees[1]
、Bees[2]
就可以了。
「成年」「幼年」[]
我們再來看看蜜蜂的實體數據。由上,我們可以得知 Bees串列內的每一項都是一個蜜蜂的實體數據,因此直接在上述的NBT路徑之後接上EntityData
(即Bees[n].EntityData
)再直接接上蜜蜂對應的NBT路徑就可以了。
實體數據[]
蜜蜂有與之相聯繫的包含許多該生物屬性的存檔數據。 Java版:
- 實體數據值
- 實體共通標籤
- 活體共通標籤
- 生物共通標籤
- 可繁殖生物額外字段
- 可憤怒生物額外字段
- CannotEnterHiveTicks:離蜜蜂能再次進入蜂箱的刻數。
- CropsGrownSincePollination:蜜蜂一共促進了多少作物的生長,不超過10。此值用來限制蜜蜂促進生長作物的次數。
- FlowerPos:儲存其盤旋的花的座標。可能不存在。
- X:X座標。
- Y:Y座標。
- Z:Z座標。
- HasNectar:表示蜜蜂是否攜帶花粉。
- HasStung:表示蜜蜂是否蜇過玩家或生物。
- HivePos:其蜂箱的座標。可能不存在。
- X:X座標。
- Y:Y座標。
- Z:Z座標。
- TicksSincePollination:蜜蜂離開蜂箱後未攜帶花粉的時間。如果 FlowerPos存在,當此值超過2400遊戲刻(2分)時,蜜蜂會嘗試飛向對應座標。
基岩版:
在「可繁殖生物額外字段」中,我們可以發現, Age標籤決定了這隻蜜蜂是成年還是幼年,大於等於0為成年,小於0為幼年。
分析與小結[]
- 需要在數據包中實現視線投射。可以參考MCBBS上K_Bai的「超精準的射線追蹤碰撞檢測器」和MCBBS上Sethbling原版攝像機的解析。
- 由於蜂巢中最多三隻蜜蜂,所以直接窮舉能完成工作。對於製作數據包來説,窮舉有時候也是唯一的方法。
- 要結合遊戲內各個實體的NBT數據實現自己的功能。
編寫與實現[]
初始化[]
#<命名空間>:<名字>
表示<命名空間>下的<名字>標籤,<命名空間>:<名字>
函數表示<命名空間>下的<名字>函數,檔案名對應data/<命名空間>/<名字>.mcfunction
。新建一個資料夾,填寫好pack.mcmeta
,建立data
資料夾。在data
資料夾下建立beeutility
(本實例就以此為主要的命名空間)和minecraft/tags/functions
(一些函數需要被每刻執行和在地圖載入時執行)資料夾。
類似於編程中變數的概念,我們需要計分項儲存成年蜜蜂的數量、幼年蜜蜂的數量、總的蜜蜂的數量和蜜蜂的年齡,以便之後用命令處理和顯示出來。
在命名計分項的時候,為了避免和其他數據包衝突,我們通常在命名一個計分項的時候在前面會一個前綴。通常這個前綴是該數據包英文名的簡寫,本實例則以bu_
為例。
建立data/beeutility/functions
,建立load.mcfunction
函數,內容如下:
scoreboard objectives add bu_beeadult dummy
scoreboard objectives add bu_beebaby dummy
scoreboard objectives add bu_beeamount dummy
scoreboard objectives add bu_beeage dummy
回顧一下/scoreboard
的語法。
scoreboard objectives add <計分項> <準則> [<顯示名稱>]
其中,這些計分項都不會直接以計分板的形式顯示出來,所以沒必要設定顯示名稱。準則設定為dummy
,使得該計分項只能被命令改變。
同樣,你也可以建立一個unload.mcfunction
,在該函數中刪除以上四個計分項。
建立data/minecraft/tags/functions/load.json
,內容如下:
{
"replace": false,
"values": [
"beeutility:load"
]
}
這樣,將beeutility:load
函數設定為地圖載入時執行。格式參見標籤,下文不再贅述。
視線投射[]
現在,我們做「取得玩家所指的蜂巢」。由上,我們可以像下面這樣一步一步嘗試得出那個可能存在的蜂巢的座標。
在beeutility
命名空間下,建立tick.mcfunction
和ray.mcfunction
函數。
tick.mcfunction
execute as @a at @s anchored eyes run function beeutility:ray
ray.mcfunction
execute if entity @s[distance=..4] if block ~ ~ ~ #minecraft:beehives run function beeutility:query
execute if entity @s[distance=..4] unless block ~ ~ ~ #minecraft:beehives positioned ^ ^ ^0.005 run function beeutility:ray
其中beeutility:query
為接下來要建立的查詢函數。
可以發現,beeutility:ray
函數在特定條件下會反覆執行自己,也就是遞歸。這樣這個函數在被執行的1刻內就會執行完畢,不需要擔心效率問題。
將beeutility:tick
函數加入#minecraft:tick
,使得其每刻執行。beeutility:tick
中,/execute
先指定執行者為所有活着的玩家(使用更多目標選擇器語法來進一步篩選),將執行位置設定為選中的玩家,然後運用anchored
子命令,將執行位置對齊到玩家頭部朝向,再執行beeutility:ray
函數。
在beeutility:ray
中,首先判斷命令執行位置與執行實體(也就是一位選中的玩家)的距離是否小於4個方塊長度。若是,則繼續判斷執行位置的方塊是否在#minecraft:beehives
中,即這個方塊是不是一個蜂巢。若是,那麼執行beeutility:query
函數。以上過程有一個條件不滿足就會跳過,執行下一條命令。假設這是遊戲第一次執行這個函數,那麼由於執行位置就是玩家頭顱部(通常不會與蜂巢重合),所以遊戲不會執行查詢。然後,依然判斷命令執行位置與執行實體(還是那位選中的玩家)的距離是否小於4個方塊長度。若是,那麼判斷執行位置的方塊是否不滿足在#minecraft:beehives
中(unless
可以理解為if not
)。若不滿足,那麼將執行位置向玩家視線方向移動0.005個方塊長度,執行beeutility:ray
函數本身。
與一般編程語言不同的是,命令並沒有提供非常方便的else
,因此我們需要重複驗證很多條件。實際思路可能更類似於以下情況:
ray.mcfunction
- 如果執行位置到玩家頭部的距離小於等於4
- 如果是(then)
- 如果執行位置所在的方塊是一個蜂巢
- 如果是(then)
- 查詢這個蜂巢
- 否則(else)
- 將執行位置向玩家視線方向移動0.005個方塊長度,再次執行自己
- 如果是(then)
- 如果執行位置所在的方塊是一個蜂巢
- 否則(else)
- 執行完畢
- 如果是(then)
將這個類流程圖與實際的代碼對比一下,看看哪些是因為命令的原因而被拆開的,可能有助於理解。
説了這麼多,可能還是有人不理解這個函數在幹什麼,是嗎?
想像一下,將玩家視線想像為一根實在的光滑杆子(杆子不與除下述小球以外的物體發生碰撞),將這根杆子截取為4個方塊長度長,在杆子的末端有一個小擋板。在杆子的起點(玩家的眼睛)處套上一個小球,這個小球只會與蜂巢和杆子發生碰撞,然後讓球沿杆向前運動。小球會不停往前運動,直到它在途中遇到了一個蜂巢(此時就會在這個位置執行beeutility:query
函數),或者它到達了杆子的最末端的擋板處(此時整個beeutility:ray
函數執行完畢)。
與一般的物理學運動不同的是,這個小球在移動時不是連續的而已(類似於以這個小球為執行實體,以玩家視線方向為小球視線方向,每次移動就是在執行/tp ^ ^ ^0.005
)。
這樣,我們就實現了一個簡單的「視線投射」,也就是取得玩家所看方塊(本實例則是蜂巢)的座標。
有人可能會問,如果碰不到蜂巢會怎麼樣呢?碰不到的意思就是玩家沒有看着蜂巢,自然也就不存在要取得的蜂巢了。也許還會有人問0.005格是不是太慢了,見上,1刻內會把以上遞歸操作全部執行完畢,所以實際的平均速度非常快。
查詢與顯示[]
查詢[]
在beeutility
命名空間下,建立beeutility:query
函數。
execute store result score @s bu_beeamount run data get block ~ ~ ~ Bees
這條命令將執行結果儲存到玩家的計分項bu_beeamount
中,而執行的實際命令是取得執行位置方塊數據中的 Bees串列。該命令的執行結果為該串列的長度,即總共有多少只蜜蜂。
# region query "Bee 1"'s age
execute store result score @s bu_beeage if data block ~ ~ ~ Bees[0].EntityData.Age run data get block ~ ~ ~ Bees[0].EntityData.Age
execute if data block ~ ~ ~ Bees[0].EntityData.Age if score @s bu_beeage matches 0.. run scoreboard players add @s bu_beeadult 1
execute if data block ~ ~ ~ Bees[0].EntityData.Age if score @s bu_beeage matches ..-1 run scoreboard players add @s bu_beebaby 1
# endregion
以上程式碼區塊重複三次,剩下兩次分別將Bees[0]
替換為Bees[1]
和Bees[2]
即可。
以下將store
子命令放在每個分析過程的最後。
第一條命令,先判斷執行位置的方塊(蜂巢)數據中的第一隻蜜蜂是否存在 Age標籤,即判斷是否存在第一隻蜜蜂。若有,執行命令,取得第一隻蜜蜂的 Age標籤,該命令的執行結果即為這個標籤的值,將結果儲存到玩家的計分項bu_beeage
中。若無,則中斷。
第二條命令,判斷是否存在第一隻蜜蜂,若有,判斷玩家計分項bu_beeage
(也就是取得的 Age)是否大於等於0,若是,則將玩家的計分項bu_beeadult
加1。
第三條命令,判斷是否存在第一隻蜜蜂,若有,判斷玩家計分項bu_beeage
是否小於等於-1(即小於0),若是,則將玩家的計分項bu_beebaby
加1。
於是我們達成了目標——知道有多少只成年蜜蜂,有多少只幼年蜜蜂。現在要做的就是把結果顯示出來了。
顯示[]
這樣一來,顯示就簡單很多了——我們有/title
,有/tellraw
,也許還可以用/bossbar
。此處以/title <選擇器> actionbar
為例。
title @s actionbar ["",{"text":"[蜜蜂助手] ","color":"gold"},{"score":{"name":"*","objective":"bu_beeamount"}},"隻蜜蜂,成年的",{"score":{"name":"*","objective":"bu_beeadult"}},"隻,幼年的",{"score":{"name":"*","objective":"bu_beebaby"}},"隻"]
此時,玩家的快捷欄上方就會顯示蜂巢裡有多少蜜蜂,有多少只成年蜜蜂,有多少只幼年蜜蜂了。
有關文字的格式,請見原始JSON文字。
清理[]
需要注意的是,以上四個計分項需要及時清理掉,否則會影響到下一次查詢。
scoreboard players set @s bu_beeamount 0
scoreboard players set @s bu_beeadult 0
scoreboard players set @s bu_beebaby 0
scoreboard players set @s bu_beeage 0
潤色與改進[]
目前這個數據包在載入地圖後便會一直檢測所有玩家有沒有盯着蜂巢,有時這會導致極大的伺服器計算資源浪費。而如果你在一個有多語需求的伺服器使用這個數據包,也只有會中文的人能使用。下面介紹一下述詞和原始JSON文字。
篩選玩家[]
上面也提示了,説運用選擇器參數可以篩選玩家。如果不是一直檢測所有玩家的話,那麼就只能檢測有需要使用的人了。很容易想到,通常我們會使用玻璃樽對蜂巢收集蜜糖,因此可以只檢測手(以非慣用手為例)中拿着玻璃樽的玩家。
建立data/beeutility/predicates/hold_glass_bottle.json
述詞檔案,內容如下:
{
"condition": "minecraft:entity_properties",
"entity": "this",
"predicate": {
"equipment":{
"offhand":{
"item": "minecraft:glass_bottle"
}
}
}
}
此述詞會檢測應用該述詞的實體是否在非慣用手拿着玻璃樽。透過目標選擇器參數,我們可以應用該述詞,篩選出有需要使用該數據包的玩家。
將beeutility:tick
函數前面改為:
execute as @a[predicate=beeutility:hold_glass_bottle] at @s anchored eyes ...
就可以篩選了。
多語言[]
細心的你也許用/data
取得過不祥旗幟的數據,這個物品的名字是一個原始JSON文字,裏面含有 translate屬性,值為"block.minecraft.ominous_banner"
。這樣的值被稱之為本地化鍵名(舊稱翻譯關鍵字或者翻譯識別碼)。如果你解壓原版遊戲的.jar
檔案,你會發現上面的值就是assets/lang/en_us.json
裡的鍵。遊戲在解析 translate的時候先將值在資源包和外置資源檔案的assets/lang/<目前語言的語言代碼>.json
的鍵中尋找,再將對應的值(也就是實際的文字)實際顯示出來。如果目前語言沒有,就去en_us.json
裡找。若都沒有,就只能直接輸出識別碼。
原始JSON文字頁面還提到了一個 with屬性,就是和 translate搭配使用的。如果你自己翻過語言檔案,你就會發現一些文字中含有%s
、%n$s
之類的變數。這個時候, with屬性裏面的文字就會按順序替代這些變數。
新建一個資源包,填寫好pack.mcmeta
,建立assets/beeutility/lang
資料夾,在資料夾下建立語言檔案,以下以zh_cn.json
和en_us.json
為例。
en_us.json
{
"beeutility.actionbar": "%s %s",
"beeutility.beeutility": "[Bee utility]",
"beeutility.result": "%d bee(s), %d adult bee(s), %d baby bee(s)"
}
zh_hk.json
{
"beeutility.actionbar": "%s %s",
"beeutility.beeutility": "[蜜蜂助手]",
"beeutility.result": "%d 隻蜜蜂,成年的 %d 隻,幼年的 %d 隻"
}
也許你會很迷惑,為什麼會有一個"%s %s"
?這是因為有的語言可能需要調整顯示順序,對於這些語言我們可以將其設為%2$s %1$s
來改變順序。
現在,將上述/title
命令的原始JSON文字寫成這樣:
{
"translate": "beeutility.actionbar",
"with": [
{
"translate": "beeutility.beeutility",
"color":"gold"
},
{
"translate": "beeutility.result",
"with": [
{"score":{"name":"*","objective":"bu_beeamount"}},
{"score":{"name":"*","objective":"bu_beeadult"}},
{"score":{"name":"*","objective":"bu_beebaby"}}
]
}
]
}
現在,將資源包透過伺服器資源包分發出去就可以了。
更進一步?[]
既然已經用上資源包了,那麼我們也可以另做一個調用CustomModelData
的物品模型並把CustomModelData
應用到一個不可用原版方法獲得的自訂NBT的物品上,讓查看蜂巢的人進一步減少。同樣的,上述述詞也得做出相應的改變來檢測。
或者,你也可以不只只讀取蜜蜂個數,可以順便展示一些其他數據,等等等等。
下一步[]
這個數據包甚至沒有用到一個盔甲座和藥水效果雲,也沒有用到任何戰利品表和結構。試着在數據包中應用這些功能也許是個好選擇。
同樣,你也可以嘗試着和資源包結合,嘗試把上文的玻璃樽做成一個自訂物品。
你也可以學習其他已經做成的數據包,或者自己定好一個目標,並把它做出來。
另請參閱[]
- ruhuasiyu的數據包製作教學
- ruhuasiyu的《更多的合成》數據包,其中包含大量可學習借鑑的地方
組件 |
| ||
---|---|---|---|
數據包 | |||
教學 |
|