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