|
| 1 | +# 新增建筑 |
| 2 | + |
| 3 | +这一章做一个真正会出现在建造菜单里的建筑。建筑和普通物品不一样,它要处理占格、材料、建造时间、电力、端口、储存、逻辑信号等内容,所以代码通常会拆成两部分: |
| 4 | + |
| 5 | +- `IBuildingConfig`:告诉游戏这个建筑长什么样、占几格、需要什么材料。 |
| 6 | +- Harmony Patch:把建筑放进建造菜单,并解锁到某个科技里。 |
| 7 | + |
| 8 | +下面的写法参考了 `NOIMods/AutomaticHarvest`,但会删掉自动收获器自己的业务逻辑,只保留新增建筑最常用的骨架。 |
| 9 | + |
| 10 | +## 文件结构 |
| 11 | + |
| 12 | +```text |
| 13 | +MyFirstBuilding/ |
| 14 | +├── MyFirstBuildingConfig.cs |
| 15 | +├── Patches.cs |
| 16 | +└── STRINGS.cs |
| 17 | +``` |
| 18 | + |
| 19 | +`MyFirstBuildingConfig.cs` 管建筑本体,`Patches.cs` 负责注册入口,`STRINGS.cs` 放名称和描述。 |
| 20 | + |
| 21 | +## 建筑配置 |
| 22 | + |
| 23 | +新增建筑需要继承 `IBuildingConfig`。最少要实现三个阶段: |
| 24 | + |
| 25 | +- `CreateBuildingDef()`:定义尺寸、动画、材料、建造规则。 |
| 26 | +- `ConfigureBuildingTemplate()`:给建筑预制体加组件。 |
| 27 | +- `DoPostConfigureComplete()`:在建筑完成配置后补运行组件。 |
| 28 | + |
| 29 | +```csharp |
| 30 | +using TUNING; |
| 31 | +using UnityEngine; |
| 32 | + |
| 33 | +namespace MyFirstBuilding |
| 34 | +{ |
| 35 | + public class MyFirstBuildingConfig : IBuildingConfig |
| 36 | + { |
| 37 | + public const string ID = "MyFirstBuilding"; |
| 38 | + |
| 39 | + public override BuildingDef CreateBuildingDef() |
| 40 | + { |
| 41 | + BuildingDef def = BuildingTemplates.CreateBuildingDef( |
| 42 | + ID, |
| 43 | + 1, |
| 44 | + 1, |
| 45 | + "my_first_building_kanim", |
| 46 | + 30, |
| 47 | + 60f, |
| 48 | + BUILDINGS.CONSTRUCTION_MASS_KG.TIER2, |
| 49 | + MATERIALS.REFINED_METALS, |
| 50 | + 1600f, |
| 51 | + BuildLocationRule.OnFloor, |
| 52 | + DECOR.NONE, |
| 53 | + NOISE_POLLUTION.NONE |
| 54 | + ); |
| 55 | + |
| 56 | + def.Floodable = false; |
| 57 | + def.Entombable = true; |
| 58 | + def.Overheatable = false; |
| 59 | + def.AudioCategory = "Metal"; |
| 60 | + def.DefaultAnimState = "off"; |
| 61 | + def.ObjectLayer = ObjectLayer.Building; |
| 62 | + |
| 63 | + return def; |
| 64 | + } |
| 65 | + |
| 66 | + public override void ConfigureBuildingTemplate(GameObject go, Tag prefabTag) |
| 67 | + { |
| 68 | + go.AddOrGet<Operational>(); |
| 69 | + go.AddOrGet<CopyBuildingSettings>(); |
| 70 | + } |
| 71 | + |
| 72 | + public override void DoPostConfigureComplete(GameObject go) |
| 73 | + { |
| 74 | + go.AddOrGet<LogicOperationalController>(); |
| 75 | + } |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +先不要急着往里面塞功能。建筑能被注册、能建出来、能正常保存读取以后,再加自己的组件会稳很多。 |
| 81 | + |
| 82 | +## 加储存 |
| 83 | + |
| 84 | +如果建筑需要存东西,直接加 `Storage`: |
| 85 | + |
| 86 | +```csharp |
| 87 | +public override void ConfigureBuildingTemplate(GameObject go, Tag prefabTag) |
| 88 | +{ |
| 89 | + Storage storage = go.AddOrGet<Storage>(); |
| 90 | + storage.capacityKg = 1000f; |
| 91 | + storage.storageFilters = STORAGEFILTERS.SOLIDS; |
| 92 | + storage.showCapacityStatusItem = true; |
| 93 | + storage.showCapacityAsMainStatus = true; |
| 94 | + |
| 95 | + storage.SetDefaultStoredItemModifiers(new[] |
| 96 | + { |
| 97 | + Storage.StoredItemModifier.Preserve |
| 98 | + }); |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +`capacityKg` 是容量,`storageFilters` 是允许放入的标签。比如只收种子可以用 `GameTags.Seed`,只收食物可以用 `GameTags.Edible`。 |
| 103 | + |
| 104 | +## 加电力 |
| 105 | + |
| 106 | +建筑需要耗电时,在 `CreateBuildingDef()` 里打开电力输入: |
| 107 | + |
| 108 | +```csharp |
| 109 | +def.RequiresPowerInput = true; |
| 110 | +def.EnergyConsumptionWhenActive = 120f; |
| 111 | +def.SelfHeatKilowattsWhenActive = 1f; |
| 112 | +def.PowerInputOffset = new CellOffset(0, 0); |
| 113 | +``` |
| 114 | + |
| 115 | +然后在 `DoPostConfigureComplete()` 里补 `EnergyConsumer`: |
| 116 | + |
| 117 | +```csharp |
| 118 | +public override void DoPostConfigureComplete(GameObject go) |
| 119 | +{ |
| 120 | + go.AddOrGet<Operational>(); |
| 121 | + go.AddOrGet<EnergyConsumer>(); |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +如果忘了 `EnergyConsumer`,建筑可能显示有电力口,但运行状态不对。 |
| 126 | + |
| 127 | +## 放进建造菜单 |
| 128 | + |
| 129 | +建筑配置写好以后,还需要告诉游戏:把它放到哪个分类里。 |
| 130 | + |
| 131 | +```csharp |
| 132 | +using HarmonyLib; |
| 133 | + |
| 134 | +namespace MyFirstBuilding |
| 135 | +{ |
| 136 | + public static class Patches |
| 137 | + { |
| 138 | + [HarmonyPatch(typeof(GeneratedBuildings), "LoadGeneratedBuildings")] |
| 139 | + public static class GeneratedBuildings_LoadGeneratedBuildings_Patch |
| 140 | + { |
| 141 | + public static void Prefix() |
| 142 | + { |
| 143 | + ModUtil.AddBuildingToPlanScreen("Base", MyFirstBuildingConfig.ID); |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + [HarmonyPatch(typeof(Db), "Initialize")] |
| 148 | + public static class Db_Initialize_Patch |
| 149 | + { |
| 150 | + public static void Postfix() |
| 151 | + { |
| 152 | + Db.Get().Techs.Get("BasicRefinement") |
| 153 | + .unlockedItemIDs |
| 154 | + .Add(MyFirstBuildingConfig.ID); |
| 155 | + } |
| 156 | + } |
| 157 | + } |
| 158 | +} |
| 159 | +``` |
| 160 | + |
| 161 | +`"Base"` 是建造菜单分类,`"BasicRefinement"` 是科技 ID。想放到别的分类或科技里,可以先找一个原版建筑,看看它所在的分类和解锁科技,再照着填。 |
| 162 | + |
| 163 | +## 本地化文本 |
| 164 | + |
| 165 | +建筑菜单需要名称、描述和效果: |
| 166 | + |
| 167 | +```csharp |
| 168 | +namespace MyFirstBuilding |
| 169 | +{ |
| 170 | + public static class STRINGS |
| 171 | + { |
| 172 | + public static class BUILDINGS |
| 173 | + { |
| 174 | + public static class PREFABS |
| 175 | + { |
| 176 | + public static class MYFIRSTBUILDING |
| 177 | + { |
| 178 | + public static LocString NAME = "我的第一个建筑"; |
| 179 | + public static LocString DESC = "一个用于测试建筑注册流程的小建筑。"; |
| 180 | + public static LocString EFFECT = "可以建造、选择,并接入后续自定义逻辑。"; |
| 181 | + } |
| 182 | + } |
| 183 | + } |
| 184 | + } |
| 185 | +} |
| 186 | +``` |
| 187 | + |
| 188 | +注意类名通常使用大写 ID。也就是说 `MyFirstBuildingConfig.ID = "MyFirstBuilding"` 时,文本路径一般是 `STRINGS.BUILDINGS.PREFABS.MYFIRSTBUILDING`。 |
| 189 | + |
| 190 | +## 常见问题 |
| 191 | + |
| 192 | +建筑出现在菜单但不能建,多半是材料分类或科技解锁写错了。先把材料改成 `MATERIALS.ALL_METALS` 或 `MATERIALS.RAW_MINERALS` 测试。 |
| 193 | + |
| 194 | +建筑建出来后状态很怪,检查 `Operational`、`EnergyConsumer`、`Storage` 这些组件是不是放在了正确阶段。 |
| 195 | + |
| 196 | +动画不显示,先确认 `.anim`、`.build`、`.png` 都打进 `anim/assets`,并且 `Assets.GetAnim()` 使用的是 `xxx_kanim`。 |
0 commit comments