植物深度教程
基础植物教程能做出“种下去、长出来、收获”的流程。进阶植物要处理更多行为:灌溉、休眠、枯萎、特殊收获、范围效果、图鉴文本和自定义状态机。
这一章重点讲植物系统常见扩展点:灌溉、休眠、枯萎、特殊收获、范围效果和自定义状态机。
生长参数
核心仍然是 ExtendEntityToBasicPlant():
EntityTemplates.ExtendEntityToBasicPlant(
plant,
temperature_lethal_low: 179.15f,
temperature_warning_low: 189.15f,
temperature_warning_high: 373.15f,
temperature_lethal_high: 383.15f,
safe_elements: new[]
{
SimHashes.Oxygen,
SimHashes.ContaminatedOxygen,
SimHashes.CarbonDioxide
},
pressure_sensitive: true,
pressure_lethal_low: 0f,
pressure_warning_low: 0.15f,
crop_id: MyFruitConfig.ID,
max_age: 2400f,
baseTraitId: "MyPlantOriginal",
baseTraitName: STRINGS.CREATURES.SPECIES.MYPLANT.NAME);max_age 是生长周期,crop_id 是收获物 ID。温度全部用开尔文。
灌溉
消耗液体或气体用 ExtendPlantToIrrigated():
EntityTemplates.ExtendPlantToIrrigated(
plant,
new[]
{
new PlantElementAbsorber.ConsumeInfo
{
tag = GameTags.Water,
massConsumptionRate = 0.033333335f
}
});0.033333335f 约等于每秒 33.3g。数值太大时,农砖供给看起来会像坏了一样。先用小数值跑通。
作物产量
作物类型要注册进 CROPS.CROP_TYPES:
[HarmonyPatch(typeof(Db), "Initialize")]
public static class Db_Initialize_Patch
{
public static void Postfix()
{
CROPS.CROP_TYPES.Add(new Crop.CropVal(
MyFruitConfig.ID,
300f,
5,
true));
}
}参数大致是作物 ID、生长周期、产量、是否可食用。不同版本签名可能变动,编译不过就对照当前源码里的 Crop.CropVal。
枯萎和休眠
植物可以接 BlightVulnerable:
plant.AddOrGet<BlightVulnerable>();如果没有做枯萎动画,可以先不要加。加了组件却没有对应动画,植物可能在枯萎阶段显示异常。
休眠逻辑由 Growing 状态机内部处理,通过 GameHashes.CropSleep 和 GameHashes.CropWakeUp 事件驱动。自定义状态机接入休眠可以读取 Growing.StatesInstance:
private static bool IsSleeping(MyPlant.StatesInstance smi)
{
Growing.StatesInstance growing = smi.master.GetSMI<Growing.StatesInstance>();
return growing != null && growing.IsStalledByCustomCondition();
}状态跳转:
alive.idle
.EventTransition(GameHashes.CropSleep, alive.sleeping, IsSleeping);
alive.sleeping
.EventTransition(GameHashes.CropWakeUp, alive.idle, smi => !IsSleeping(smi));自定义植物状态机
当原版 StandardCropPlant 不够用时,可以写自己的植物状态机:
public class MyPlant : StateMachineComponent<MyPlant.StatesInstance>
{
[MyCmpGet] public Growing growing;
[MyCmpGet] public Harvestable harvestable;
[MyCmpGet] public Crop crop;
protected override void OnSpawn()
{
base.OnSpawn();
smi.StartSM();
}
protected override void OnCleanUp()
{
smi.StopSM("OnCleanUp");
base.OnCleanUp();
}
}状态:
public class States : GameStateMachine<States, StatesInstance, MyPlant>
{
public State idle;
public State fruiting;
public State harvest;
public override void InitializeStates(out StateMachine.BaseState defaultState)
{
defaultState = idle;
idle
.PlayAnim("grow", KAnim.PlayMode.Paused)
.Update((smi, dt) => smi.RefreshGrowthAnim(), UpdateRate.SIM_4000ms)
.EventTransition(GameHashes.Grow, fruiting,
smi => smi.master.growing.ReachedNextHarvest());
fruiting
.PlayAnim("idle_full", KAnim.PlayMode.Loop)
.Enter(smi => smi.master.harvestable.SetCanBeHarvested(true))
.EventTransition(GameHashes.Harvest, harvest);
harvest
.PlayAnim("harvest", KAnim.PlayMode.Once)
.Enter(smi => smi.SpawnFruit())
.Exit(smi => smi.master.harvestable.SetCanBeHarvested(false))
.OnAnimQueueComplete(idle);
}
}这只是骨架。完整植物还要处理死亡、枯萎、休眠、被连根拔起等状态。先把主链路跑通,再补边缘状态。
特殊收获
如果想根据环境改变产物,可以在收获时重新配置作物:
public void SpawnFruit()
{
string cropId = IsHotEnough()
? HotFruitConfig.ID
: ColdFruitConfig.ID;
Crop.CropVal cropVal = CROPS.CROP_TYPES.Find(crop => crop.cropId == cropId);
master.crop.Configure(cropVal);
master.crop.SpawnConfiguredFruit(null);
}类似的特殊植物可以在收获阶段根据环境条件选择产物。
范围效果
植物如果要影响周围,可以加 RangeVisualizer 方便玩家理解范围:
RangeVisualizer visualizer = plant.AddOrGet<RangeVisualizer>();
visualizer.OriginOffset = new Vector2I(0, 0);
visualizer.RangeMin.x = -4;
visualizer.RangeMin.y = -4;
visualizer.RangeMax.x = 4;
visualizer.RangeMax.y = 4;
visualizer.BlockingTileVisible = true;真正的范围逻辑不要每帧扫。用 ISim4000ms 或状态机里的限频 Update():
public void Sim4000ms(float dt)
{
int center = Grid.PosToCell(gameObject);
// 扫附近格子,改变温度、气体或给复制人加效果。
}图鉴和文本
植物要补植物文本和种子文本:
Strings.Add($"STRINGS.CREATURES.SPECIES.{ID.ToUpper()}.NAME", "氧树");
Strings.Add($"STRINGS.CREATURES.SPECIES.{ID.ToUpper()}.DESC", "一种会影响周围环境的植物。");
Strings.Add($"STRINGS.CREATURES.SPECIES.SEEDS.{SEED_ID.ToUpper()}.NAME", "氧树种子");如果要进图鉴,还要看 CodexCache 和植物注册流程。先保证游戏内名称、种子、收获物都正常,再做图鉴。
常见坑
温度写错单位是植物教程第一大坑。20f 不是 20 摄氏度,是 -253.15 摄氏度。
自定义状态机不要和 StandardCropPlant 同时抢同一套状态。要么用原版标准植物,要么明确替换成自己的状态机。
Harvestable.SetCanBeHarvested(true) 后,收获完成一定要关掉。不然复制人可能一直尝试收获。
特殊产物要先注册进 CROPS.CROP_TYPES,否则 crop.Configure() 找不到配置。
范围效果要限频。植物数量一多,全图扫描会非常明显地拖慢游戏。