:實(shí)現(xiàn)給雞擠奶與混沌碎片合成)
在一檔名為《石頭世界》的系列內(nèi)容第 4 季第 14 集里“給雞擠奶還要制作成混沌碎片”聽起來(lái)像一句整活臺(tái)詞。但把它當(dāng)成玩法需求交給 Minecraft 模組開發(fā)者時(shí)這句話包含的信息量并不小玩家要和雞交互、交互結(jié)果要產(chǎn)出一種特殊物品、這個(gè)物品還要進(jìn)入合成鏈變成“混沌碎片”。這里不討論劇情只把標(biāo)題當(dāng)作需求輸入實(shí)現(xiàn)一個(gè)可運(yùn)行的 Forge 1.20.1 模組原型并通過(guò)這次開發(fā)把實(shí)體交互、物品注冊(cè)、事件監(jiān)聽、NBT 冷卻、配方 JSON 和配置外置的完整鏈路串起來(lái)。這類需求最大的特征是原始描述很短可執(zhí)行細(xì)節(jié)幾乎為零。開發(fā)者的任務(wù)不是照著標(biāo)題寫代碼而是先確定一套合理假設(shè)再讓假設(shè)變成可驗(yàn)證的流程。本文使用的假設(shè)是玩家手持玻璃瓶右鍵雞消耗一個(gè)玻璃瓶后獲得“雞奶瓶”雞奶瓶和一塊普通石頭在工作臺(tái)里合成“混沌碎片”。1. 先把“給雞擠奶做成混沌碎片”拆解成可執(zhí)行需求任何一個(gè)看似荒誕的需求落到代碼里都要先拆成可執(zhí)行動(dòng)作。標(biāo)題里的“給雞擠奶”是行為層“制作成混沌碎片”是結(jié)果層。中間還有一個(gè)隱含狀態(tài)擠出來(lái)的奶要先變成一種中間物品否則“制作”沒有材料來(lái)源。1.1 標(biāo)題里其實(shí)有兩套玩法如果只看“給雞擠奶”首先要回答四個(gè)問(wèn)題玩家用什么物品觸發(fā)這個(gè)交互。交互是否消耗物品。交互是否有次數(shù)限制或冷卻。這個(gè)交互是否只對(duì)雞生效還是對(duì)一類動(dòng)物生效。再看“還要制作成混沌碎片”又要回答另外幾個(gè)問(wèn)題混沌碎片是一個(gè)物品、一個(gè)方塊還是一種特殊貨幣。碎片是擠奶時(shí)直接掉落還是需要二次合成。如果二次合成使用的配方是什么。合成之后雞奶瓶是否消耗。原版 Minecraft 里雞只負(fù)責(zé)下蛋不存在“擠奶”這個(gè)行為。右鍵雞時(shí)原版邏輯不會(huì)給出任何奶類結(jié)果。所以這個(gè)需求必須通過(guò)事件系統(tǒng)攔截實(shí)體交互在玩家手持指定物品右鍵雞時(shí)取消原版邏輯并執(zhí)行自定義邏輯。這里采用一種最小閉環(huán)把雞奶瓶設(shè)計(jì)為普通物品而不是真正的流體桶。普通 Item 足夠驗(yàn)證行為鏈路真正的流體桶需要額外實(shí)現(xiàn) Fluid、桶物品和流體渲染會(huì)讓原型復(fù)雜很多。1.2 需求拆成四個(gè)模塊為了讓開發(fā)過(guò)程不混亂可以把整個(gè)需求拆成四個(gè)模塊。實(shí)體交互模塊負(fù)責(zé)判斷“玩家手持玻璃瓶右鍵雞”這個(gè)動(dòng)作是否成立以及是否處于冷卻狀態(tài)。物品模塊負(fù)責(zé)注冊(cè)雞奶瓶和混沌碎片并設(shè)置堆疊數(shù)量、物品 ID、顯示名稱等屬性。合成模塊負(fù)責(zé)定義“雞奶瓶 石頭 混沌碎片”的配方讓碎片能通過(guò)工作臺(tái)制作。狀態(tài)與配置模塊負(fù)責(zé)記錄玩家上次擠奶的時(shí)間并把冷卻時(shí)間、產(chǎn)出數(shù)量等參數(shù)放到配置文件里避免每次調(diào)整都要重新編譯。這四個(gè)模塊不是并列關(guān)系而是依賴關(guān)系事件模塊產(chǎn)生雞奶瓶物品模塊提供雞奶瓶合成模塊消費(fèi)雞奶瓶配置模塊約束事件模塊的觸發(fā)頻率。1.3 先定技術(shù)路線數(shù)據(jù)包、Forge 還是 Fabric| 方案 | 能實(shí)現(xiàn)什么 | 不能實(shí)現(xiàn)什么 | 適合場(chǎng)景 || --- | --- | --- | --- | | 純數(shù)據(jù)包 | 物品標(biāo)簽、配方、進(jìn)度、部分戰(zhàn)利品 | 新增自定義物品、攔截實(shí)體交互、寫入玩家 NBT | 已經(jīng)存在基礎(chǔ)物品只是調(diào)整配方 | | Fabric | 自定義物品、事件監(jiān)聽、服務(wù)端邏輯 | 需要掌握 Fabric Loader 和對(duì)應(yīng) API | 團(tuán)隊(duì)已經(jīng)使用 Fabric 生態(tài) | | Forge | 自定義物品、事件監(jiān)聽、NBT 操作、配置系統(tǒng) | 需要處理較重的 MDK 結(jié)構(gòu) | 社區(qū)資料多適合教學(xué)和快速驗(yàn)證 |這里選擇 Forge 1.20.1原因是它的PlayerInteractEvent、DeferredRegister、ForgeConfigSpec都屬于比較穩(wěn)定的 API網(wǎng)上資料也多。對(duì)于“給雞擠奶”這種需要新增交互邏輯的需求純數(shù)據(jù)包做不了因?yàn)閿?shù)據(jù)包沒有辦法在玩家右鍵雞時(shí)執(zhí)行自定義邏輯。1.4 先想清楚物品是普通 Item 還是流體容器標(biāo)題里提到“奶”容易讓人下意識(shí)去實(shí)現(xiàn)流體。但一個(gè)最小原型不應(yīng)該一上來(lái)就做流體。合理做法是先把“雞奶”建模成一個(gè)普通物品名叫“雞奶瓶”堆疊數(shù)量設(shè)為 16。它只是一個(gè)中間材料不參與真正桶裝液體的傾倒、放置和填充邏輯。這樣做的取舍是視覺上不夠真實(shí)雞奶瓶不能倒出來(lái)?yè)Q來(lái)的是開發(fā)鏈路短能快速驗(yàn)證“交互、產(chǎn)出、合成”這個(gè)主干。后續(xù)需要真實(shí)流體時(shí)再基于這個(gè)結(jié)構(gòu)擴(kuò)展 Fluid 和 BucketItem。2. 環(huán)境準(zhǔn)備與項(xiàng)目骨架先鎖版本再寫事件2.1 需要準(zhǔn)備的環(huán)境模組開發(fā)中大多數(shù)問(wèn)題都出在版本不匹配。寫代碼之前先確認(rèn) JDK、Minecraft、Forge 和 Gradle 的版本關(guān)系。環(huán)境項(xiàng)推薦值說(shuō)明JDK17Minecraft 1.20.1 使用 Java 17 編譯和運(yùn)行開發(fā) IDEIntelliJ IDEA方便導(dǎo)入 Gradle 項(xiàng)目Minecraft1.20.1社區(qū)模組兼容性較高Forge MDK47.x以 Forge 官網(wǎng)或 Maven 列表為準(zhǔn)GradleMDK 自帶 wrapper不需要單獨(dú)安裝下載 Forge MDK 后先不要急著寫代碼。打開gradle.properties把版本號(hào)固定下來(lái)再啟動(dòng)gradlew導(dǎo)入項(xiàng)目。如果原始材料沒有給出明確版本落地前要先確認(rèn)依賴版本。不同 Minecraft 主版本對(duì)應(yīng)不同 Forge API代碼風(fēng)格也會(huì)不同尤其是事件注冊(cè)和配置系統(tǒng)的寫法。2.2 Gradle 配置要點(diǎn)在gradle.properties中寫入以下內(nèi)容minecraft_version1.20.1 forge_version47.1.0 mappings_channelofficial mappings_version1.20.1其中mappings_channelofficial表示使用 Mojang 官方映射。對(duì)新手來(lái)說(shuō)官方映射名稱和代碼里看到的Chicken、Item、Player一致閱讀和調(diào)試都比較直觀。build.gradle關(guān)鍵段落如下plugins { id net.minecraftforge.gradle version [6.0,6.2) } dependencies { implementation fg.deobf(net.minecraftforge:forge:${minecraft_version}-${forge_version}) }fg.deobf會(huì)自動(dòng)處理 Minecraft 和 Forge 的反混淆依賴開發(fā)者不需要手動(dòng)管理運(yùn)行時(shí)庫(kù)。2.3 項(xiàng)目目錄結(jié)構(gòu)一個(gè)最簡(jiǎn)單的 Forge 模組項(xiàng)目結(jié)構(gòu)如下stoneworld-mod/ ├── build.gradle ├── gradle.properties └── src/main/ ├── java/com/stoneworld/ │ ├── StoneWorldMod.java │ ├── item/ModItems.java │ ├── event/ChickenMilkHandler.java │ └── config/StoneWorldConfig.java └── resources/ ├── META-INF/mods.toml ├── assets/stoneworld/ │ ├── lang/zh_cn.json │ └── textures/item/ └── data/stoneworld/ └── recipes/資源文件的路徑非常關(guān)鍵。配方文件必須放在data/stoneworld/recipes/下語(yǔ)言文件必須放在assets/stoneworld/lang/zh_cn.json下。路徑一旦寫錯(cuò)不會(huì)直接報(bào)編譯錯(cuò)誤而是運(yùn)行時(shí)配方便靜默失效這是模組開發(fā)中最容易浪費(fèi)時(shí)間的坑之一。3. 實(shí)現(xiàn)核心交互給雞擠奶并產(chǎn)出一瓶雞奶3.1 先把兩個(gè)物品注冊(cè)進(jìn)去使用 Forge 的DeferredRegister注冊(cè)物品這是當(dāng)前推薦寫法。它會(huì)在模組加載階段統(tǒng)一注冊(cè)物品同時(shí)保證物品 ID 不會(huì)沖突。public class ModItems { public static final DeferredRegisterItem ITEMS DeferredRegister.create(ForgeRegistries.ITEMS, StoneWorldMod.MOD_ID); public static final RegistryObjectItem CHICKEN_MILK_BOTTLE ITEMS.register(chicken_milk_bottle, () - new Item(new Item.Properties().stacksTo(16))); public static final RegistryObjectItem CHAOS_FRAGMENT ITEMS.register(chaos_fragment, () - new Item(new Item.Properties().stacksTo(64))); public static void register(IEventBus bus) { ITEMS.register(bus); } }stacksTo(16)是因?yàn)檫@瓶奶被定位為中間材料數(shù)量太多會(huì)顯得不合理混沌碎片作為最終合成材料堆疊到 64 比較順手。如果后續(xù)想讓物品擁有特殊提示文字或稀有度再單獨(dú)繼承Item類并重寫方法。StoneWorldMod里的注冊(cè)代碼如下Mod(StoneWorldMod.MOD_ID) public class StoneWorldMod { public static final String MOD_ID stoneworld; public StoneWorldMod() { IEventBus bus FMLJavaModLoadingContext.get().getModEventBus(); ModItems.register(bus); ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, StoneWorldConfig.SPEC); } }3.2 監(jiān)聽玩家與雞的交互Forge 提供 Player