Skip to content

行星和区块

我们的征途是星辰大海!

Mindustry模组可分为原版拓展和新星球两种类型。添加新星球可以构建独立的模组叙事,不受原版星球设定的影响。行星系统是原版中为数不多的3D内容,其实现基于v6版本后重构的3D渲染模块,与底层图形计算紧密耦合。本部分将介绍行星、区块、天气和队伍的相关机制。

创建一个Planet

java
new Planet("tutorial-planet", Planets.sun, 1f, 3);
kotlin
Planet("tutorial-planet", Planets.sun, 1f, 3)

构造方法的第一个参数是行星的名称;第二个参数是行星的公转中心,若为null则无公转中心;第三个参数是行星的半径,Serpulo和Erekir的半径均为1;第四个参数是 星球网格(PlanetGrid)细分(Subdivision) 次数,当细分次数为0时,每个格子的中心是正二十面体的12个顶点,构成正十二面体,之后每次细分都将顶点(Corner)转化为六边形格子(Tile)。细分次数为1时,星球网格包含12个正五边形和20个正六边形,共32个区块。

行星可以设置渲染图,但通常不是必需功能。也可以设置小图标(icon字段),用于星球选择界面和核心数据库的显示。

properties
planet.tutorial-mod-tutorial-planet.name = 演示行星
planet.tutorial-mod-tutorial-planet.description = 绕着太阳转。原版甚至没有给行星做过描述。
planet.tutorial-mod-tutorial-planet.details = 这里有人生活过。
properties
planet.tutorial-mod-tutorial-planet.name = Tutorial Planet
planet.tutorial-mod-tutorial-planet.description = Orbits the sun. There is no description of a planet in vanilla.
planet.tutorial-mod-tutorial-planet.details = Here are they.

关于Planet各字段的含义如下:

Planet类定义了游戏中的行星天体,包含其轨道、几何网格、地表分区、生成规则以及战役相关的各种属性,是星系系统、科技树和战役模式的核心组成部分。

  • mesh:用于渲染行星的网格;客户端专用,在load()中创建。
  • cloudMesh:用于渲染行星云层的网格;若无云层则为null
  • gridMesh:用于渲染行星网格轮廓的网格;在服务器端或gridnull时为null
  • position:行星在全局坐标系中的位置;在宇宙更新前为(0,0,0)。
  • grid:行星上区块(Sector)使用的网格;若行星不可登陆则为null
  • generator:生成行星地形的生成器;对于无需登陆的行星可为null
  • sectors:区块序列;直接映射到网格中的格子(Tile)。
  • orbitSpacing:与同一行星系的上一颗行星之间的间距。
  • radius:行星球体的半径(不包含卫星)。
  • camRadius:摄像机半径偏移。
  • minZoom:摄像机最小缩放值。
  • maxZoom:摄像机最大缩放值。
  • drawOrbit:是否绘制轨道圆环。
  • atmosphereRadIn:大气层内半径调整参数。
  • atmosphereRadOut:大气层外半径调整参数。
  • clipRadius:裁剪半径;小于0时自动计算。
  • totalRadius:该行星及其所有子行星的总半径。
  • orbitTime:行星绕太阳公转一周的时间(单位:秒);即一年。
  • rotateTime:行星自转一周的时间(单位:秒);即一天。
  • orbitOffset:随机轨道角度偏移,防止所有行星初始在一条直线上。
  • sectorApproxRadius:单个区块的近似半径。
  • tidalLock:该行星是否相对于其父星体潮汐锁定(始终以同一面朝向父星体)。
  • accessible:该行星是否在行星访问UI中列出。
  • defaultEnv:该行星上区块的默认环境标志。
  • defaultAttributes:默认环境属性。
  • updateLighting:是否模拟昼夜循环。
  • lightSrcFrom:昼夜循环中光源起始强度。
  • lightSrcTo:昼夜循环中光源结束强度。
  • lightDstFrom:昼夜循环中漫射光起始强度。
  • lightDstTo:昼夜循环中漫射光结束强度。
  • startSector:默认显示的起始区块索引,用于地图对话框。
  • sectorSeed:该行星上区块基础生成的种子;-1表示基于ID使用随机种子。
  • launchCapacityMultiplier:发射时核心物品容量的倍率。
  • bloom:是否启用泛光(Bloom)渲染效果。
  • visible:该行星是否显示。
  • landCloudColor:着陆时显示的云层颜色。
  • lightColor:(对于恒星)照射其他行星的光的颜色;对子行星无效。
  • atmosphereColor:(可登陆行星的)大气层颜色。
  • iconColor:行星列表中显示的图标颜色。
  • hasAtmosphere:该行星是否有大气层。
  • allowLaunchSchematics:是否允许用户为该地图指定自定义发射原理图。
  • allowLaunchLoadout:是否允许用户指定携带到此地图的资源。
  • allowSectorInvasion:是否模拟来自敌方基地的区块入侵。
  • allowLegacyLaunchPads:若为true,可启用旧版发射台。
  • clearSectorOnLose:若为true,区块失败时清除其存档。
  • enemyBuildSpeedMultiplier:敌方建造速度倍率;仅应用于战役(非标准规则)。
  • enemyInfiniteItems:若为true,敌方队伍总是拥有无限物品。
  • enemyCoreSpawnReplace:若为true,该行星上的敌方核心被生成点替换(用于入侵)。
  • prebuildBase:若为true,着陆时核心半径内的方块将被移除并以冲击波形式“重建”。
  • allowWaves已废弃。
  • allowLaunchToNumbered:若为false,玩家无法登陆到该行星的编号区块。
  • allowCampaignRules:若为true,允许玩家在行星UI中更改难度/规则。
  • icon:在行星选择对话框中显示的图标(字符串,因为在加载时可绘制对象为空)。
  • launchMusic:在行星对话框中选择此行星时播放的音乐。
  • defaultCore:发射时默认的核心方块。
  • parent:该行星绕其公转的父星体;若为null,则该行星被视为位于太阳系中心。
  • solarSystem:该行星所在太阳系的根父行星。
  • children:围绕此行星运行的所有子行星(按半径升序排列)。
  • techTree:在此行星打开科技树时显示的默认根节点。
  • launchCandidates:可从此行星发射到达的行星列表。
  • allowSelfSectorLaunch:行星际加速器是否可以向该行星表面的“任何”程序化区块发射。
  • autoAssignPlanet:若为true,该行星科技树中的所有内容将把此行星分配给其shownPlanets
  • unlockedOnLand:在此行星着陆时解锁的内容(通常是行星特定的)。
  • meshLoader:加载行星网格的提供器(客户端专用);默认为一个简单的球体网格。
  • cloudMeshLoader:加载行星云层网格的提供器(客户端专用);默认为null
  • gridMeshLoader:加载行星网格轮廓网格的提供器(客户端专用)。
  • updateGroup:允许与该行星同时更新的行星集合(用于后台计算)。
  • campaignRules:该行星战役的全局难度/修改器设置。
  • campaignRuleDefaults:应用于规则的默认值。
  • ruleSetter:在该行星的任何区块加载游戏时设置规则的Consumer。
  • showRtsAIRule:若为true,可以自定义RTS AI。
  • loadPlanetData:若为true,行星数据从planets/{name}.json加载;仅在原版中经过测试/功能正常。

行星的绘制

为了使行星被绘制出来,需要设置行星的形状并将其交由显卡渲染。行星的形状本质上是一个多面体,代表行星多面体的对象的基接口是mindustry.graphic.GenericMesh(注意与arc.graphic.Mesh区分,Arc对3D没有支持)。可用的PlanetMesh包括:

  • HexMesh:首先使用上文的“细分”算法(通常细分度大于星球网格),然后调用 星球生成器(Generator) 获取每个格子的高度和颜色。使用此PlanetMesh不需要传入其他参数,但必须为星球设置generator
  • ShaderSphereMesh:当前版本的默认PlanetMesh(早期版本为HexMesh),需要输入一个着色器(Shader)。着色器负责将宇宙三维坐标转换为屏幕二维坐标,并在转换前对坐标和颜色进行处理;
  • NoiseMesh:使用噪声生成表面高度和颜色变化的星球,可调节的参数包括细分度、噪声(Noise) 的基本参数、星球使用的颜色等。此Mesh是JSON行星的默认PlanetMesh;
  • SunMesh:使用噪声生成表面颜色变化的星球,高度恒为零;
  • MultiMesh:组合多个PlanetMesh,以实现多层效果。各个PlanetMesh为简单叠加,渲染时仅显示最终多面体的最外层轮廓;
  • 小行星:由若干个NoiseMesh在随机方向上通过MultiMesh组合而成,具体逻辑位于Planets::makeAsteroid中。
JSON下的小行星

如果你不是JSON用户,你只需要强行使用Planet#makeAsteroid()方法就可以了,它会给你自动用噪声组装一个mesh,但如果使用JSON就没有这么多好事了,不过你仍然可以自己组装合适的MultiMesh,下摘取giermesh以供参考:

json
{
  "type": "MultiMesh",
  "meshes": [
    {
      "type": "NoiseMesh",
      "seed": -5,
      "divisions": 2,
      "radius": 0.12,
      "octaves": 2,
      "persistence": 0.55,
      "scale": 0.45,
      "mag": 14.0,
      "color1": "6d635cff",
      "color2": "444c5100",
      "coct": 3,
      "cper": 0.6,
      "cscl": 0.38,
      "cthresh": 0.4
    },
    {
      "type": "MatMesh",
      "mesh": {
        "type": "NoiseMesh",
        "seed": -4,
        "divisions": 1,
        "radius": 0.039694287,
        "octaves": 2,
        "persistence": 0.6,
        "scale": 0.38,
        "mag": 20.0,
        "color1": "6d635cff",
        "color2": "444c5100",
        "coct": 3,
        "cper": 0.6,
        "cscl": 0.38,
        "cthresh": 0.4
      },
      "mat": {
        "trans": [-0.41581467, 0.0043853936, -0.43292877]
      }
    },
    {
      "type": "MatMesh",
      "mesh": {
        "type": "NoiseMesh",
        "seed": -3,
        "divisions": 1,
        "radius": 0.04112552,
        "octaves": 2,
        "persistence": 0.6,
        "scale": 0.38,
        "mag": 20.0,
        "color1": "6d635cff",
        "color2": "444c5100",
        "coct": 3,
        "cper": 0.6,
        "cscl": 0.38,
        "cthresh": 0.4
      },
      "mat": {
        "trans": [-1.1926273, -0.5223744, -0.34610972]
      }
    },
    {
      "type": "MatMesh",
      "mesh": {
        "type": "NoiseMesh",
        "seed": -2,
        "divisions": 1,
        "radius": 0.034535024,
        "octaves": 2,
        "persistence": 0.6,
        "scale": 0.38,
        "mag": 20.0,
        "color1": "6d635cff",
        "color2": "444c5100",
        "coct": 3,
        "cper": 0.6,
        "cscl": 0.38,
        "cthresh": 0.4
      },
      "mat": {
        "trans": [0.6332749, -0.6608225, -0.93765724]
      }
    },
    {
      "type": "MatMesh",
      "mesh": {
        "type": "NoiseMesh",
        "seed": -1,
        "divisions": 1,
        "radius": 0.05146175,
        "octaves": 2,
        "persistence": 0.6,
        "scale": 0.38,
        "mag": 20.0,
        "color1": "6d635cff",
        "color2": "444c5100",
        "coct": 3,
        "cper": 0.6,
        "cscl": 0.38,
        "cthresh": 0.4
      },
      "mat": {
        "trans": [0.6968424, 0.41930753, -0.54544216]
      }
    },
    {
      "type": "MatMesh",
      "mesh": {
        "type": "NoiseMesh",
        "seed": 0,
        "divisions": 1,
        "radius": 0.0382008,
        "octaves": 2,
        "persistence": 0.6,
        "scale": 0.38,
        "mag": 20.0,
        "color1": "6d635cff",
        "color2": "444c5100",
        "coct": 3,
        "cper": 0.6,
        "cscl": 0.38,
        "cthresh": 0.4
      },
      "mat": {
        "trans": [-1.0368634, -0.03162155, -0.5795313]
      }
    },
    {
      "type": "MatMesh",
      "mesh": {
        "type": "NoiseMesh",
        "seed": 1,
        "divisions": 1,
        "radius": 0.022329422,
        "octaves": 2,
        "persistence": 0.6,
        "scale": 0.38,
        "mag": 20.0,
        "color1": "6d635cff",
        "color2": "444c5100",
        "coct": 3,
        "cper": 0.6,
        "cscl": 0.38,
        "cthresh": 0.4
      },
      "mat": {
        "trans": [-0.27299184, -0.30318034, 0.9343648]
      }
    },
    {
      "type": "MatMesh",
      "mesh": {
        "type": "NoiseMesh",
        "seed": 2,
        "divisions": 1,
        "radius": 0.033677075,
        "octaves": 2,
        "persistence": 0.6,
        "scale": 0.38,
        "mag": 20.0,
        "color1": "6d635cff",
        "color2": "444c5100",
        "coct": 3,
        "cper": 0.6,
        "cscl": 0.38,
        "cthresh": 0.4
      },
      "mat": {
        "trans": [-0.47120374, -0.5294042, 0.7750685]
      }
    }
  ]
}

参数要合理搭配

需要注意的是,行星的radius是用于计算逻辑上的区块大小的,和mesh的radius没有互相影响的关系。如果你发现你的星球渲染不出来,则可能是因为mesh的radius过小(你看不见)或过大(视角卡在星球内部)导致的。

此处需要介绍 噪声(Noise) 这一概念。噪声是一种算法,能够在给定输入(如坐标)时产生伪随机但连续变化的输出值。其特性包括:对于相同的输入,噪声总是返回相同的值;当使用相同种子时,对于邻近的输入,返回值会平滑过渡;而不同种子则会产生完全不同的输出序列。由于这些特性,噪声常被用于程序化生成地形等场景。此外,在某些游戏机制中,需要一种在时间和空间上看似随机,但在不同客户端和服务器之间保持确定性的伪随机值,例如多变体环境方块的变体,噪声也可用于此类需求。

Mindustry主要使用Simplex噪声算法。噪声算法通常定义了一个多维(二至四维)的数值空间,通过调整参数来控制输出值的特征。以下是Simplex噪声中一些常用参数的含义:

名称用途
种子(Seed)不同的种子能让生成的噪声空间完全不同
比例(Scale,scl调节噪声值中随空间的变化率。注意,Arc和Unity正好相反,Arc中的Scale实际上应该取倒数,因此实际上对应的是Unity中的频率(Frequency)
scl越大变化越平滑,越小变化越崎岖
倍频(Octave,oct调节噪声的叠加次数。越大边缘越陡峭
持久(Persistence,per调节噪声的叠加强度。越小边缘越陡峭
阈值(Thresh)超过此值才执行某动作。噪声值是非负的。不要让scl超过thresh
至于星球的云层,通常使用HexSkyMesh,其构造器参数与前述内容一致。需要说明的是,云层的实际渲染半径为星球的radius加上HexSkyMesh构造器指定的radius,与星球meshradius无关。

在定义所需的mesh后,应设置meshLoadercloudMeshLoader,而非直接赋值给meshcloudMeshmeshLoader是一个类型为Prov<GenericMesh>的lambda表达式,通常按以下方式编写:

java
meshLoader = () -> new HexMesh(this, 6);

数字区块的生成

如果希望行星可登陆,需要为区块添加预设地图(SectorPreset),或为行星设置生成器(Generator)。若需与Serpulo或Erekir的生成方式保持一致,可直接使用SerpuloPlanetGeneratorErekirPlanetGenerator;小行星则可选用AsteroidGenerator。Serpulo的生成器参数固定,Erekir与小行星生成器允许调整与矿物生成相关的噪声阈值及概率参数。需要注意的是,若需在生成器中包含自定义矿物,通常需要参考现有生成器逻辑进行相应扩展。

星球生成器的实现较为复杂,与行星相关的类之间存在较高的耦合度。因此,本阶段建议调用原版中已有的生成器。

物品与方块

你需要为物品的shownPlanets中添加你的星球,才能使有此材料的方块可以在你的星球上建造。你需要为你科技树的根节点设置planet字段,才能让解锁科技时消耗的是星球上的资源。

环境方块

在新星球上需要配置新的环境方块,所有环境方块都位于mindustry.world.blocks.environment包中。常用的环境方块类型包括静态墙StaticWall、环境摆件Prop、地板Floor、矿物OreBlock等。以Remove开头的类通常用于地图编辑器功能,不属于常规的环境方块。

环境方块的主要特性是静态性。它们没有实体,不参与游戏逻辑更新,且渲染结果会被缓存。环境方块所需的贴图资源应放置在sprites/blocks/environment目录下,否则会显示为Wrong Texture Folder贴图,这与1-2节中提到的图集页机制有关。此外,由于错误贴图(内部标识为error)位于main图集页,当环境方块找不到对应贴图时,也会渲染出Wrong Texture Folder

矿物包含一系列与生成相关的字段,例如oreScaleoreThreshold,这些字段仅影响自定义地图的默认生成器,而不会影响战役模式中的星球生成器。因此,若需在战役星球生成器中包含自定义矿物,需对相应的生成器进行修改。

创建一个SectorPreset

java
new SectorPreset("testSector", Planets.serpulo, 15);
kotlin
SectorPreset("testSector", Planets.serpulo, 15)
properties
sector.tutorial-mod-testSector.name = 演示区块
sector.tutorial-mod-testSector.description = 旅程的起点。
sector.tutorial-mod-testSector.details = 旅起最佳。
properties
sector.tutorial-mod-testSector.name = Turorial Sector
sector.tutorial-mod-testSector.description = The commence of our tutorial.
sector.tutorial-mod-testSector.details =

区块的命名遵循小驼峰(camelCase)规范,这有助于在代码中保持标识符的一致性。现版本中,区块已支持自定义图标,你可通过添加相应贴图资源来实现。具体操作方式与先前其他内容的贴图添加流程一致。此外,要使预设区块生效,还需在 assets/maps 目录下提供对应的地图文件。例如,对于名为 testSector 的区块,应准备一个名为 testSector.msav 的地图存档文件。

创建一个Weather

天气是一种环境效果,会在屏幕上显示粒子或雨滴,对单位施加状态效果或位移,或降低环境亮度。

java
new RainWeather("hailing");
new ParticleWeather("gray-pall");
kotlin
RainWeather("hailing")
ParticleWeather("gray-pall")
properties
weather.tutorial-mod-hailing.name = 冰雹
weather.tutorial-mod-gray-pall.name = 灰棘迷雾
properties
weather.tutorial-mod-hailing.name = Hail
weather.tutorial-mod-gray-pall.name = Gray Pall

获取一个Team

队伍是原版内容之一,原版已注册全部256个队伍,且其构造方法已隐藏,因此使用时直接获取现有队伍即可,不建议通过反射构造新的队伍。此外,Team的实例位于mindustry.game包中,其结构类似于枚举类。

名称用途
Team.derelict废墟队伍
Team.sharded玩家队伍
Team.cruxSerpulo的敌方队伍
Team.malisErekir的敌方队伍
Team.neoplastic瘤液敌方队伍

TeamEntry用于在核心数据库中显示队伍信息,当前版本未在其他场景中使用。

ContentType总结

到这里我们已经遇到了原版基本所有内容类型了,列表如下:

字段名说明
itemItem为基类,代表物品,当前版本无子类,属于可解锁内容
blockBlock为基类,代表方块,功能主要通过子类方法实现,也包含drawer等组件,属于可解锁内容
mech_UNUSED已废止,代表机甲,在v6版本中机甲机制被移除
bulletBulletType为基类,代表子弹类型,功能主要通过子类方法实现,在v7版本中移除了自身的load()方法,独立性降低,不支持按名称索引
liquidLiquid为基类,代表流体,在v7版本前仅表示液体,但名称沿用至今,属于可解锁内容
statusStatusEffect为基类,代表状态效果,当前版本无子类,属于可解锁内容
unitUnitType为基类,代表单位,当前版本的子类仅作为模板使用,属于可解锁内容
weatherWeather为基类,代表天气,功能主要通过子类方法实现,属于可解锁内容
effect_UNUSED已废止,代表特效,在classic版本中已被移除
sectorSectorPreset为基类,代表预设区块,当前版本无子类,属于可解锁内容
在v5版本中此项名为zone,代表区域,属于可解锁内容
loadout_UNUSED已废止,并非当前的装载蓝图或物资,在classic版本中已被移除
typeid_UNUSED已废止,代表实体类型,旧的实体系统在v6版本中被移除
errorErrorContent为基类,代表错误内容
planetPlanet为基类,代表行星,当前版本无子类,属于可解锁内容
ammo_UNUSED已废止,代表弹药类型,在v127(v6.5)版本中将AmmoType由类改为接口后删除
teamTeamEntry为基类,代表队伍,当前版本无子类,属于可解锁内容
unitCommandUnitCommand为基类,代表单位命令,v8版本添加,当前版本无子类,支持按名称索引
unitStanceUnitStance为基类,v8版本添加,代表单位姿态,支持按名称索引

需要注意的是,虽然该类从创建之初便标注“不得重排”,但其内部顺序在v6版本前至少经历了五次调整。实际上,该类中各值的顺序与源代码中部分自动生成的文件高度耦合,这导致当前版本已无法轻易删除其中的废止项。

上文提到的“可解锁”“可索引”直接对应内容系统的基类:

  • Content:游戏“内容”的基类。提供short类型的id,并自动向Vars.content中注册,可以通过id索引。
  • MappableContent:可映射内容。提供包含modName前缀的唯一名称name,可以在Vars.content中通过名称索引。
  • UnlockableContent:可解锁内容。提供localizedNamedescriptiondetails,支持Bundle;支持完整图标和emoji;可在核心数据库中添加统计信息;拥有解锁状态,可以添加到科技树中,也可以绑定到星球。