验证环境
本页示例已使用 Build 740622 对应的本机 Managed DLL 完成编译检查;尚未在游戏内完成运行验证。
配方系统
新增物品、食物、材料后,玩家最常见的疑问是"怎么获得它"。配方系统让物品可以通过制造建筑产出——不管是烹饪台、碎石机、炼油厂还是你自己做的新建筑。
游戏中实际使用的是 ComplexRecipe 系统。老版 Recipe 已很少使用,本章只讲 ComplexRecipe。
配方如何绑定建筑
配方不是挂到某个特定建筑上,而是声明一个 fabricators 列表。任何 ComplexFabricator 组件在初始化时会扫描所有配方,把自己的 PrefabTag 和配方的 fabricators 列表比对,匹配上了就显示。
理解这一点很重要:配方不会直接引用建筑类型,而是通过 fabricators Tag 与建筑的 PrefabTag 间接绑定。你可以做一个新配方,让它被所有原版烹饪台识别,只要把 "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,配方就会被识别。
实际开发顺序
做自己的配方时,可以按下面的顺序排查:
- 先用原版输入和输出验证配方注册时机。
- 确认
fabricatorsTag 与目标建筑的PrefabTag一致。 - 再替换成自己的物品或食物 ID。
- 最后调整数量、时间、温度和排序。
配方出现在工艺台,说明注册和 Tag 基本正确;原料消耗后没有产物,继续查结果 Tag 对应的实体是否已注册,以及输出储存是否能接受它。
当前可编译的最小项目是 examples/Recipe/。它只演示配方注册,不负责创建新的食物实体,所以不要把它误当成“新增食物”的完整项目。
自定义制造建筑
如果你做了自己的制造建筑并想支持配方,建筑上需要挂 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() 会根据制造建筑 ID、每个输入 Tag 和每个输出 Tag 拼接 recipe ID。不要依赖具体字符串格式;在当前 DLL 中,同一组参数会得到相同的 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 对应的实体是否存在。制造建筑的输出储存通常会显示"等待输出"。