Skip to content

世界生成

新元素、新植物、新生物做出来以后,如果只能靠调试工具刷出来,玩家体验是不完整的。让它们自然出现在新地图里,需要接世界生成系统。

缺氧的世界生成分成三层:worldssubworldsfeatures。每一层嵌套引用,最终落实到具体元素和实体。Mod 按需要放对应层级的 yaml 文件即可。

资源位置

原版的世界生成资源在游戏目录下:

text
OxygenNotIncluded_Data/StreamingAssets/
└── worldgen/
    ├── biomes/        # 生态群系(地层构成)
    ├── features/      # 矿脉、空洞、遗迹等局部地形
    ├── subworlds/     # 子世界(群系、温度带、特色地形的组合)
    └── worlds/        # 完整世界(选出所有 subworld)

Mod 里不需要完整复制,只放你要新增或修改的部分:

text
MyWorldgenMod/
└── worldgen/
    ├── features/
    │   └── rift/
    │       └── MyCrystalPocket.yaml    # 自定义矿脉
    ├── subworlds/
    │   └── rift/
    │       └── CrystalRift.yaml        # 引用上面的 feature
    └── worlds/
        └── MyCrystalWorld.yaml         # 引用上面的 subworld

三层结构

worlds/MyWorld.yaml
  └─ 引用 subworlds/rift/CrystalRift.yaml
       └─ 引用 features/rift/MyCrystalPocket.yaml
            └─ 使用 element: AuroraCrystal(来自 elements/custom_elements.yaml)

每一层都是独立文件。上层通过引用路径找到下层,游戏在生成世界时逐层解析。

第一层:Feature(矿脉 / 局部地形)

Feature 是最小的世界生成单元——一团矿物、一个空洞、一片遗迹。如果只是想让你新注册的元素以矿脉形式出现,从这里开始最合适。

worldgen/features/rift/MyCrystalPocket.yaml

yaml
tags:
  - AllowExceedNodeBorders
shape: Blob
borders:
  - 1
  - 2
blobSize:
  min: 10
  max: 18
ElementChoiceGroups:
  RoomCenterElements:
    selectionMethod: WeightedResample
    choices:
      - element: AuroraCrystal
        weight: 1
        overrides:
          massOverride: 200
          temperatureOverride: 290
  RoomBorderChoices0:
    selectionMethod: WeightedResample
    choices:
      - element: Granite
        weight: 2
      - element: AuroraCrystal
        weight: 1
        overrides:
          massOverride: 200
          temperatureOverride: 290

关键字段:

  • element: AuroraCrystal — 必须和 custom_elements.yaml 里的 elementId 完全一致。
  • massOverride — 每格元素质量(kg)。固体一般 100 ~ 500
  • temperatureOverride — 开尔文温度。290 约等于 17°C。
  • shape: Blob — 不规则团块。其他选项包括 Circle(圆形)、Line(线状)等。
  • blobSize — 团块占据的格子数范围。
  • selectionMethod: WeightedResample — 按权重随机选。weight 越大越容易出现。

第二层:Subworld(子世界)

Feature 不会自己生成。必须被某个 subworld 引用。

worldgen/subworlds/rift/CrystalRift.yaml

yaml
biomeNoise: noise/Sandstone
temperatureRange: Room
density:
  min: 8
  max: 12
sampleBehaviour: PoissonDisk
biomes:
  - name: biomes/Sedimentary/Basic
    weight: 1
features:
  - type: features/rift/MyCrystalPocket
zoneType: Sandstone

关键字段:

  • features — 引用的 feature 路径列表。features/rift/MyCrystalPocket 对应 worldgen/features/rift/MyCrystalPocket.yaml
  • biomes — 这个 subworld 使用哪些群系。biomes/Sedimentary/Basic 对应 worldgen/biomes/Sedimentary/Basic.yaml(砂岩生态)。
  • temperatureRange — 温度范围。Room 是常温(约 20°C),还有 ColdHotVeryCold 等。
  • density — 子世界内部密度(影响 feature 和其他内容排布)。
  • sampleBehaviour: PoissonDisk — 采样方式,决定内容物怎么分布。

第三层:World(完整世界)

World 把所有 subworld 组合起来,定义它们在整张地图上的分布。

worldgen/worlds/MyCrystalWorld.yaml

yaml
subworldFiles:
  - name: subworlds/sandstone/SandstoneStart
  - name: subworlds/rift/CrystalRift
    minCount: 4
  - name: subworlds/magma/Bottom

unknownCellsAllowedSubworlds:
  - tagcommand: Default
    command: Replace
    subworldNames:
      - subworlds/rift/CrystalRift
      - subworlds/sandstone/Sandstone

关键字段:

  • subworldFiles — 这个世界会出现的所有 subworld 列表。minCount 控制最少出现次数。
  • unknownCellsAllowedSubworlds — 地图上未分配区域填充时,用哪些 subworld 来补。

如果只写了 feature 和 subworld,但 World 没有引用它,地图里仍然不会出现新内容。

直接混进 Biome 地层

如果想让新元素像砂岩、铜矿那样成为普通地层,可以修改 biome 文件:

worldgen/biomes/Sedimentary/Basic.yaml(需要修改原文件或在 Mod 里覆盖):

yaml
TerrainBiomeLookupTable:
  add:
    Basic:
    - content: SandStone
      bandSize: 0.2
    - content: Algae
      bandSize: 0.4
    - content: AuroraCrystal
      bandSize: 0.05
      overrides:
        massOverride: 200
        temperatureOverride: 290
    - content: Oxygen
      bandSize: 1.3

bandSize 是地层厚度占比。0.02 ~ 0.08 适合稀有矿物,太大了会像主岩层一样铺满。

POI 模板

模板(Points of Interest)是固定布局的预制房间,比如遗迹、初始基地、固定矿点:

yaml
- element: AuroraCrystal
  mass: 200
  temperature: 290

模板里直接写元素和格子位置,不依赖 feature / subworld 链。适合做遗迹、固定生成点,缺点是不随机分布。

调试世界生成

世界生成最难调试——它只在生成新世界时运行,旧存档不会凭空刷新。

生成固体元素测试:

csharp
GameObject prefab = Assets.GetPrefab(ModElements.AuroraCrystal.CreateTag());
GameUtil.KInstantiate(prefab, position, Grid.SceneLayer.Ore).SetActive(true);

生成液体/气体测试:

csharp
SimMessages.AddRemoveSubstance(
    cell,
    ModElements.AuroraCrystalGas,
    CellEventLogger.Instance.ElementEmitted,
    1f,              // 质量(kg)
    300f,            // 温度(K)
    byte.MaxValue,   // 病菌索引
    0);              // 病菌数量

生成检查清单:

  • feature yaml 是否复制到 Mod 输出目录的 worldgen/features/ 下。
  • subworld 是否引用了 feature,路径是否对得上。
  • world 是否引用了 subworld,minCount 是否大于 0。
  • element 名是否和 custom_elements.yaml 里的 elementId 完全一致。
  • 温度单位是开尔文,不是摄氏度。
  • 测试一定要开新档

最常见的问题是写了一整套 feature + subworld + world,但少了一层引用导致生成失败。建议先做一个简单的 Blob feature,在已有的 SandstoneStart subworld 里引用它试试,确认元素能刷出来再扩展。

常见问题

新元素在地图里完全没有 — 逐层检查引用链:world → subworld → feature → element ID。通常某一层的路径拼错了。

生成了但质量不对massOverride 是每格 kg。如果没写 massOverride,默认值和元素定义的 defaultMass 有关。检查 yaml 缩进——overrides 块必须和 choices 同级。

元素生成了但温度是 0K(绝对零度)temperatureOverride 没生效,或者温度写了摄氏度值(比如 20 当成 20°C,实际被当作 20K)。

世界生成报错 — 看 Player.log,搜索目标元素名或 feature 路径。日志通常会告诉你哪个 yaml 解析失败。

想覆盖原版地图但不想动 Steam 源文件 — Mod 的 worldgen 目录文件会合并到原版资源里,同名文件 Mod 版优先。创建新的 world 文件引用你的 subworld 即可,不需要改原版 world 文件。