验证环境
本页已在游戏 Build 740622、.NET Standard 2.1、Mod API 2 下验证。
配方系统
新增物品、食物、材料后,玩家最常见的疑问是"怎么获得它"。配方系统让物品可以通过制造建筑产出——不管是烹饪台、碎石机、炼油厂还是你自己做的新建筑。
游戏中实际使用的是 ComplexRecipe 系统。老版 Recipe 已很少使用,本章只讲 ComplexRecipe。
配方如何绑定建筑
配方不是挂到某个特定建筑上,而是声明一个 fabricators 列表。任何 ComplexFabricator 组件在初始化时会扫描所有配方,把自己的 PrefabTag 和配方的 fabricators 列表比对,匹配上了就显示。
理解这一点很重要:配方和建筑是完全解耦的。你可以做一个新配方,让它被所有原版烹饪台识别,只要把 "CookingStation" 加进 fabricators。
最简配方
一个单输入、单输出的配方见可编译示例 examples/Recipe/Mod.cs:
using System.Collections.Generic;
using HarmonyLib;
using KMod;
using UnityEngine;
namespace ONITutorial.Recipe
{
public sealed class Mod : UserMod2
{
public override void OnLoad(Harmony harmony)
{
base.OnLoad(harmony);
Debug.Log("[ONITutorial.Recipe] Loaded");
}
[HarmonyPatch(typeof(CookingStationConfig), "ConfigureRecipes")]
private static class ConfigureRecipesPatch
{
private static void Postfix()
{
ComplexRecipe.RecipeElement[] ingredients =
{
new ComplexRecipe.RecipeElement(TagManager.Create("RawEgg"), 1f)
};
ComplexRecipe.RecipeElement[] results =
{
new ComplexRecipe.RecipeElement(
TagManager.Create("CookedEgg"),
1f,
ComplexRecipe.RecipeElement.TemperatureOperation.Heated,
false)
};
string recipeId = ComplexRecipeManager.MakeRecipeID(
"CookingStation", ingredients, results);
new ComplexRecipe(recipeId, ingredients, results)
{
time = 20f,
description = "A recipe registered by the tutorial example.",
nameDisplay = ComplexRecipe.RecipeNameDisplay.Result,
fabricators = new List<Tag> { TagManager.Create("CookingStation") },
sortOrder = 50
};
}
}
}
}关键点:
ComplexRecipe构造函数自动注册配方到ComplexRecipeManager,不需要手动调用Add。TemperatureOperation.Heated表示产物会加热到制造设备的温度。nameDisplay = Result表示 UI 里显示产物名,不是原料名。fabricators里的 Tag 必须和建筑的KPrefabID.PrefabTag一致。常用原版建筑 ID 是"CookingStation"、"GourmetCookingStation"、"RockCrusher"、"Apothecary"等。
RecipeElement 详解
RecipeElement 是 ComplexRecipe 的嵌套类,代表一种输入或输出。
基本构造(单个材料):
new ComplexRecipe.RecipeElement(Tag material, float amount)带温度操作:
new ComplexRecipe.RecipeElement(
Tag material,
float amount,
ComplexRecipe.RecipeElement.TemperatureOperation temperatureOperation,
bool storeElement = false)TemperatureOperation 选项:
| 值 | 含义 |
|---|---|
AverageTemperature | 产物温度 = 各输入原料的质量加权平均温度 |
Heated | 产物温度 = 制造设备设置的加热温度 |
Melted | 产物以液态形式产出(熔化温度) |
Dehydrated | 产物脱水 |
多材料可选(任一满足即可):
当一个输入位允许多种材料时,用 Tag[]:
new ComplexRecipe.RecipeElement(
new Tag[] { TagManager.Create("ColdWheatSeed"), TagManager.Create("MyFruit") },
2f)这表示"可以用 2kg 冰霜小麦种子,也可以用 2kg 自定义果实"。游戏 UI 会把两种都列出来。
接入已有建筑
最常见的需求是——我做了新食物,想让它能在原版烹饪台做出来。
以烹饪台为例,补丁实现也位于 examples/Recipe/Mod.cs:
using System.Collections.Generic;
using HarmonyLib;
using KMod;
using UnityEngine;
namespace ONITutorial.Recipe
{
public sealed class Mod : UserMod2
{
public override void OnLoad(Harmony harmony)
{
base.OnLoad(harmony);
Debug.Log("[ONITutorial.Recipe] Loaded");
}
[HarmonyPatch(typeof(CookingStationConfig), "ConfigureRecipes")]
private static class ConfigureRecipesPatch
{
private static void Postfix()
{
ComplexRecipe.RecipeElement[] ingredients =
{
new ComplexRecipe.RecipeElement(TagManager.Create("RawEgg"), 1f)
};
ComplexRecipe.RecipeElement[] results =
{
new ComplexRecipe.RecipeElement(
TagManager.Create("CookedEgg"),
1f,
ComplexRecipe.RecipeElement.TemperatureOperation.Heated,
false)
};
string recipeId = ComplexRecipeManager.MakeRecipeID(
"CookingStation", ingredients, results);
new ComplexRecipe(recipeId, ingredients, results)
{
time = 20f,
description = "A recipe registered by the tutorial example.",
nameDisplay = ComplexRecipe.RecipeNameDisplay.Result,
fabricators = new List<Tag> { TagManager.Create("CookingStation") },
sortOrder = 50
};
}
}
}
}如果不想 patch ConfigureRecipes,也可以在 Db.Initialize 后注册。只要在 ComplexRecipeManager.PostProcess() 之前构造了 ComplexRecipe,配方就会被识别。
自定义制造建筑
如果你做了自己的制造建筑并想支持配方,建筑上需要挂 ComplexFabricator 组件:
public override void ConfigureBuildingTemplate(GameObject go, Tag prefabTag)
{
ComplexFabricator fabricator = go.AddOrGet<ComplexFabricator>();
fabricator.sideScreenStyle = ComplexFabricatorSideScreen.StyleSetting.ListQueueHybrid;
fabricator.duplicantOperated = true;
fabricator.heatedTemperature = 308.15f;
}配方的 fabricators 里写上这个建筑的 PrefabTag 即可。ComplexFabricator.GetRecipes() 会自动扫描所有匹配的配方。
排序和分类
配方有两个影响 UI 排序的字段:
recipe.sortOrder = 10; // 数字越小越靠前
recipe.recipeCategoryID = "..."; // 构造函数自动生成,也可以手动改ComplexRecipeManager.MakeRecipeID() 生成的 recipe ID 格式是 "FabricatorID_InputHash_OutputHash",用来保证同一建筑同一输入输出的配方 ID 不变。
DLC 限制
如果配方只在某个 DLC 下生效:
new ComplexRecipe(recipeId, ingredients, results,
requiredDlcIds: new string[] { "EXPANSION1_ID" })
{
// ...
}
// 或同时声明必须和禁止的 DLC:
new ComplexRecipe(recipeId, ingredients, results,
requiredDlcIds: new string[] { "EXPANSION1_ID" },
forbiddenDlcIds: null)
{
// ...
}不需要 DLC 限制时,用两参数的基础构造即可。
常见问题
配方注册了但建筑里看不到 — 先检查 fabricators 里的 Tag 和建筑的 PrefabTag 是否一致。注意有些建筑用 TagManager.Create("xxx") 创建的 Tag,不是直接用字符串。
配方时间写错了 — time 单位是游戏秒。原版烹饪台标准时间是 TUNING.FOOD.RECIPES.STANDARD_COOK_TIME(约 30 秒),小型配方约 15 秒。
产物数量不对 — RecipeElement 的 amount 是"一份产物需要多少原料",不是"一次产出几个"。如果需要产出多个,把结果 amount 设置大于 1。
原料被消耗但不产出 — 检查结果数组是否为空、结果 Tag 对应的实体是否存在。制造建筑的输出储存通常会显示"等待输出"。