Skip to content

新增植物

植物本质上也是实体,所以它使用 IEntityConfig。和普通掉落物不同的是,植物还要处理生长状态、种子、收获物、温度压力范围,以及是否需要灌溉。

这一章用“植物本体 + 果实物品”的组合,走一遍完整注册流程。

文件结构

text
MyFirstPlant/
├── MyFirstPlantConfig.cs
├── MyFirstFruitConfig.cs
├── Patches.cs
└── STRINGS.cs

植物和果实最好分开写。植物负责长在地上,果实负责掉落、食用或加工。

果实配置

先写果实,因为植物注册时会引用它的 ID。

csharp
using UnityEngine;

namespace MyFirstPlant
{
    public class MyFirstFruitConfig : IEntityConfig
    {
        public const string ID = "MyFirstFruit";

        public GameObject CreatePrefab()
        {
            GameObject go = EntityTemplates.CreateLooseEntity(
                ID,
                STRINGS.ITEMS.FOOD.MYFIRSTFRUIT.NAME,
                STRINGS.ITEMS.FOOD.MYFIRSTFRUIT.DESC,
                1f,
                false,
                Assets.GetAnim("my_first_fruit_kanim"),
                "object",
                Grid.SceneLayer.Front,
                EntityTemplates.CollisionShape.RECTANGLE,
                0.8f,
                0.5f,
                true,
                0,
                SimHashes.Creature
            );

            EdiblesManager.FoodInfo foodInfo = new EdiblesManager.FoodInfo(
                ID,
                1000f * 800f,
                1,
                255.15f,
                277.15f,
                4800f,
                true
            );

            return EntityTemplates.ExtendEntityToFood(go, foodInfo);
        }

        public void OnPrefabInit(GameObject inst) { }
        public void OnSpawn(GameObject inst) { }
    }
}

1000f * 800f 表示 800 千卡。温度用开尔文,273.15f 才是 0 摄氏度。

植物本体

植物用 EntityTemplates.CreatePlacedEntity() 创建,因为它是放在世界格子里的实体,不是掉在地上的物品。

csharp
using System.Collections.Generic;
using TUNING;
using UnityEngine;

namespace MyFirstPlant
{
    public class MyFirstPlantConfig : IEntityConfig
    {
        public const string ID = "MyFirstPlant";
        public const string SEED_ID = "MyFirstPlantSeed";

        public GameObject CreatePrefab()
        {
            GameObject go = EntityTemplates.CreatePlacedEntity(
                ID,
                STRINGS.CREATURES.SPECIES.MYFIRSTPLANT.NAME,
                STRINGS.CREATURES.SPECIES.MYFIRSTPLANT.DESC,
                1f,
                Assets.GetAnim("my_first_plant_kanim"),
                "idle_empty",
                Grid.SceneLayer.BuildingFront,
                1,
                2,
                DECOR.BONUS.TIER0,
                NOISE_POLLUTION.NONE,
                SimHashes.Creature,
                new List<Tag> { GameTags.Plant },
                293.15f
            );

            EntityTemplates.ExtendEntityToBasicPlant(
                go,
                temperature_lethal_low: 248.15f,
                temperature_warning_low: 263.15f,
                temperature_warning_high: 313.15f,
                temperature_lethal_high: 343.15f,
                safe_elements: new[] { SimHashes.Oxygen, SimHashes.CarbonDioxide },
                pressure_sensitive: true,
                pressure_lethal_low: 0.05f,
                pressure_warning_low: 0.15f,
                crop_id: MyFirstFruitConfig.ID,
                max_age: 2400f,
                baseTraitId: "BasicPlantTrait"
            );

            go.AddOrGet<Crop>();
            go.AddOrGet<Growing>();
            go.AddOrGet<StandardCropPlant>();
            go.AddOrGet<BlightVulnerable>();

            GameObject seed = EntityTemplates.CreateAndRegisterSeedForPlant(
                go,
                DlcRestrictionsUtil.GetTransientHelperObjectFromAllowList(null),
                SeedProducer.ProductionType.Harvest,
                SEED_ID,
                STRINGS.CREATURES.SPECIES.SEEDS.MYFIRSTPLANT.NAME,
                STRINGS.CREATURES.SPECIES.SEEDS.MYFIRSTPLANT.DESC,
                Assets.GetAnim("seed_kanim"),
                "object",
                1,
                new List<Tag> { GameTags.CropSeed },
                SingleEntityReceptacle.ReceptacleDirection.Top,
                default(Tag),
                2,
                STRINGS.CREATURES.SPECIES.MYFIRSTPLANT.DOMESTICATEDDESC
            );

            EntityTemplates.CreateAndRegisterPreviewForPlant(
                seed,
                ID + "_preview",
                Assets.GetAnim("my_first_plant_kanim"),
                "place",
                1,
                2
            );

            return go;
        }

        public void OnPrefabInit(GameObject inst)
        {
            inst.GetComponent<PrimaryElement>().Temperature = 293.15f;
        }

        public void OnSpawn(GameObject inst) { }
    }
}

crop_id 决定植物成熟后产出什么。max_age 是生长周期,单位是游戏秒。

CreateAndRegisterSeedForPlant() 当前推荐签名需要一个 IHasDlcRestrictions 参数。示例里的 DlcRestrictionsUtil.GetTransientHelperObjectFromAllowList(null) 表示不限制 DLC;如果你的配置类自己实现了 IHasDlcRestrictions,也可以传 this

灌溉和施肥

如果植物需要消耗液体或气体,可以继续扩展:

csharp
EntityTemplates.ExtendPlantToIrrigated(
    go,
    new[]
    {
        new PlantElementAbsorber.ConsumeInfo
        {
            tag = GameTags.Water,
            massConsumptionRate = 0.01f
        }
    }
);

massConsumptionRate 是每秒消耗量。植物数值很容易写大,建议先用很小的值测试。

注册实体

植物和果实都要被 Assets 扫到。最常见做法是在 OnLoad 里调用:

csharp
using HarmonyLib;
using KMod;

namespace MyFirstPlant
{
    public class Mod : UserMod2
    {
        public override void OnLoad(Harmony harmony)
        {
            base.OnLoad(harmony);
        }
    }
}

只要 IEntityConfig 类在程序集里,游戏通常会自动发现。出问题时先检查类是否 public,以及 Mod 是否成功加载。

常见问题

植物不显示,多半是动画状态名错了。植物常用 idle_emptyidle_fullplace,具体要以当前 kanim 里的状态为准。

种子能生成但不能种,检查 CreateAndRegisterSeedForPlant() 的植物 ID、种子 ID、GameTags.CropSeed 和种植方向。

植物一落地就死,优先看温度、压力、safe_elements。尤其温度要写开尔文,不要把 20 摄氏度写成 20f