Skip to content

验证环境

本页示例已使用 Build 740622 对应的本机 Managed DLL 完成编译检查;尚未在游戏内完成运行验证。

配方系统

新增物品、食物、材料后,玩家最常见的疑问是"怎么获得它"。配方系统让物品可以通过制造建筑产出——不管是烹饪台、碎石机、炼油厂还是你自己做的新建筑。

游戏中实际使用的是 ComplexRecipe 系统。老版 Recipe 已很少使用,本章只讲 ComplexRecipe

配方如何绑定建筑

配方不是挂到某个特定建筑上,而是声明一个 fabricators 列表。任何 ComplexFabricator 组件在初始化时会扫描所有配方,把自己的 PrefabTag 和配方的 fabricators 列表比对,匹配上了就显示。

理解这一点很重要:配方不会直接引用建筑类型,而是通过 fabricators Tag 与建筑的 PrefabTag 间接绑定。你可以做一个新配方,让它被所有原版烹饪台识别,只要把 "CookingStation" 加进 fabricators

最简配方

一个单输入、单输出的配方见可编译示例 examples/Recipe/Mod.cs

csharp
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 详解

RecipeElementComplexRecipe 的嵌套类,代表一种输入或输出。

基本构造(单个材料):

csharp
new ComplexRecipe.RecipeElement(Tag material, float amount)

带温度操作:

csharp
new ComplexRecipe.RecipeElement(
    Tag material,
    float amount,
    ComplexRecipe.RecipeElement.TemperatureOperation temperatureOperation,
    bool storeElement = false)

TemperatureOperation 选项:

含义
AverageTemperature产物温度 = 各输入原料的质量加权平均温度
Heated产物温度 = 制造设备设置的加热温度
Melted产物以液态形式产出(熔化温度)
Dehydrated产物脱水

多材料可选(任一满足即可):

当一个输入位允许多种材料时,用 Tag[]

csharp
new ComplexRecipe.RecipeElement(
    new Tag[] { TagManager.Create("ColdWheatSeed"), TagManager.Create("MyFruit") },
    2f)

这表示"可以用 2kg 冰霜小麦种子,也可以用 2kg 自定义果实"。游戏 UI 会把两种都列出来。

接入已有建筑

最常见的需求是——我做了新食物,想让它能在原版烹饪台做出来。

以烹饪台为例,补丁实现也位于 examples/Recipe/Mod.cs

csharp
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,配方就会被识别。

实际开发顺序

做自己的配方时,可以按下面的顺序排查:

  1. 先用原版输入和输出验证配方注册时机。
  2. 确认 fabricators Tag 与目标建筑的 PrefabTag 一致。
  3. 再替换成自己的物品或食物 ID。
  4. 最后调整数量、时间、温度和排序。

配方出现在工艺台,说明注册和 Tag 基本正确;原料消耗后没有产物,继续查结果 Tag 对应的实体是否已注册,以及输出储存是否能接受它。

当前可编译的最小项目是 examples/Recipe/。它只演示配方注册,不负责创建新的食物实体,所以不要把它误当成“新增食物”的完整项目。

自定义制造建筑

如果你做了自己的制造建筑并想支持配方,建筑上需要挂 ComplexFabricator 组件。下面只演示配方扫描所需的核心配置,不是一个完整可运行的制造建筑;实际项目还需要根据建筑类型配置对应的工作、存储和动画组件:

csharp
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 排序的字段:

csharp
recipe.sortOrder = 10;           // 数字越小越靠前
recipe.recipeCategoryID = "...";  // 构造函数自动生成,也可以手动改

ComplexRecipeManager.MakeRecipeID() 会根据制造建筑 ID、每个输入 Tag 和每个输出 Tag 拼接 recipe ID。不要依赖具体字符串格式;在当前 DLL 中,同一组参数会得到相同的 ID。

DLC 限制

如果配方只在某个 DLC 下生效:

csharp
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 秒。

产物数量不对RecipeElementamount 是"一份产物需要多少原料",不是"一次产出几个"。如果需要产出多个,把结果 amount 设置大于 1。

原料被消耗但不产出 — 检查结果数组是否为空、结果 Tag 对应的实体是否存在。制造建筑的输出储存通常会显示"等待输出"。