Skip to content

炮塔和子弹

喜欢我石墨雷光吗?

Mindustry 包含自动化与塔防(Tower Defense)元素。炮塔是防御体系的核心组件,也是自动化系统的资源消耗节点,并作为科技树推进的重要环节。炮塔发射的子弹类型(BulletType)是内容系统的重要组成部分,本节将介绍炮塔与子弹的相关机制。

炮塔的基本构成包括子弹类型(BulletType)、射击方式(ShootPattern)、炮塔绘制器(Drawer)中的绘制部件(DrawPart)、冷却剂系统以及方块基础属性,下文将逐一说明。 为了方便理解以下内容,这里提供一个最小模板:

java
new ItemTurret("tutorial-item-turret"){{
    requirements(Category.turret, with(Items.copper, 39));
    ammo(Items.copper, new BasicBulletType(1.5f, 9));
    shoot = new ShootPattern();
    drawer = new DrawTurret(){{
        parts.add(new RegionPart("-barrel"));
    }};
    consumePower(40f);
    coolant = consumeCoolant(0.1f);
}};
kotlin
ItemTurret("tutorial-item-turret").apply {
    requirements(Category.turret, with(Items.copper, 39))
    ammo(Items.copper, BasicBulletType(1.5f, 9))
    shoot = ShootPattern()
    drawer = DrawTurret().apply{
        parts.add(RegionPart("-barrel"))
    }
    consumePower(40f)
    coolant = consumeCoolant(0.1f)
}

子弹类型(BulletType)

子弹类型规定了子弹的类型、运动、大小、伤害、能力、目标、行为、功能、与其他子弹的交互、绘制和特效等。在此无法全部列举,如需完整了解可参阅源代码。子弹并不局限于传统意义的金属子弹,例如太空环境下的液体泡(SpaceLiquidBulletType),或没有贴图的 EmptyBulletType,以及激光、水珠、点激光等。非炮塔方块也能发射子弹,例如质量驱动器的 MassDriverBolt。整体而言,子弹是具有确定速度和寿命、由类炮塔方块发射的实体。

要新建一个子弹类型,只需要实例化一个 BulletType 的具体子类:

java
new BasicBulletType(1.5f, 9);
kotlin
BasicBulletType(1.5f, 9)

其中构造方法的两个参数分别为子弹速度和伤害。在 v6 版本之前,子弹类型作为独立的内容类(mindustry.content.BulletType)进行加载。当前版本中,该类虽已不再承担原有的加载功能,但子弹类型仍属于内容体系,是 Content 的子类。

BulletType 是子弹类型的抽象基类,所有具体子弹类型都从它派生。这意味着不能直接实例化 BulletType 本身。从功能上看,它自身不具备绘制贴图的能力,仅负责绘制尾迹(Trail)和配合炮塔绘制器工作。以下简要介绍一些重要字段:

1. 核心属性 (Core Properties)

这些是定义一个子弹最基本行为的字段。

  • lifetime: 子弹的生命周期,单位是游戏刻(ticks)。超过这个时间,子弹会自然消失(触发 despawn)。
  • lifeScaleRandMin, lifeScaleRandMax: 子弹生成时,其生命周期会乘以一个在这两个值之间随机选取的系数,用于增加随机性。
  • speed: 子弹的初始速度(单位/刻)。
  • velocityScaleRandMin, velocityScaleRandMax: 子弹生成时,其速度会乘以一个在这两个值之间随机选取的系数。
  • damage: 子弹的直接撞击伤害。
  • hitSize: 子弹的碰撞箱(Hitbox)大小,用于检测与实体(单位、建筑)的碰撞。
  • drawSize: 子弹的绘制大小和世界裁剪范围。如果子弹超出视口这个范围,就不会被绘制。
  • angleOffset, randomAngleOffset: angleOffset 是固定的角度偏移,randomAngleOffset 是随机的角度偏移范围。两者都会在子弹生成时加到其初始角度上。
  • drag: 子弹的拖拽/阻力系数。每帧速度会乘以 (1 - drag),使其逐渐减慢。
  • accel: 子弹的加速度(单位/刻²)。每帧速度会增加这个值。
  • layer: 子弹绘制的Z层(层级),用于控制渲染顺序(例如在单位上方还是下方)。
  • setDefaults: 如果为 true,引擎会自动设置一些合理的默认值(例如,如果有闪电效果,则默认状态效果为“电击”)。

2. 穿透与碰撞 (Piercing & Collision)

控制子弹如何与场景中的其他实体交互。

  • pierce: 子弹是否可以穿透单位。
  • pierceBuilding: 子弹是否可以穿透建筑。
  • pierceCap: 子弹最多可以穿透多少个实体。-1 表示无限穿透。
  • pierceDamageFactor: 子弹每穿透一个实体后,其伤害减少的系数。伤害减少值为 初始生命值 * pierceDamageFactor
  • removeAfterPierce: 如果为 true,当子弹伤害因穿透降至0或以下时,子弹会被移除。如果为 false,即使伤害为0也会继续飞行。
  • maxDamageFraction: 对单个目标的伤害上限。伤害不会超过 目标最大生命值 * maxDamageFraction
  • laserAbsorb: (通常用于穿透激光)是否可以被塑钢墙(Plastanium Wall)吸收。
  • optimalLifeFract: 生命百分比(0到1之间),在此时间点子弹处于最佳状态(用于连续武器的计算)。
  • collidesTiles: 是否与地形块(Tile)碰撞。
  • collidesTeam: 是否与同队(相同队伍)的实体碰撞。
  • collidesAir,collidesGround: 是否与空中或地面单位碰撞。
  • collides: 总开关,是否与任何东西发生碰撞。如果为 false,则忽略其他碰撞设置。
  • collideFloor: 是否与非表面的地板(如深水)碰撞。
  • collideTerrain: 是否与静态墙壁(如山脉)碰撞。
  • hittable: 该子弹是否可以被防御塔(Point Defense)击中。
  • reflectable: 该子弹是否可以被反射(例如被盾牌反射)。
  • absorbable: 该子弹是否可以被护盾吸收。
  • sticky: 子弹是否会“粘”在碰撞到的第一个实体上,并停止运动。
  • stickyExtraLifetime: 子弹粘附后,其生命周期会增加的时间。

3. 效果与音效 (Effects & Sounds)

控制子弹创建、命中、消失时的视听效果。

  • hitEffect: 子弹击中某个目标时播放的效果(Effect)。
  • despawnEffect: 子弹生命周期结束时播放的效果。
  • shootEffect: 子弹被发射时播放的效果。
  • chargeEffect: (通常用于单发武器)开始充能时播放的效果。
  • smokeEffect: 发射时产生的额外烟雾效果。
  • hitSound,despawnSound: 击中目标和自然消失时播放的音效。
  • hitSoundPitch,hitSoundVolume: 击中音效的音调和音量。
  • hitColor: 用于命中(hit)和消失(despawn)效果的颜色。
  • hitShake,despawnShake: 击中目标和自然消失时造成的屏幕震动强度。

4. 发射器与武器属性 (Turret/Weapon Properties)

这些属性主要影响发射该子弹的武器(如炮塔)的行为。

  • inaccuracy: 发射时的额外不准确度(散射),单位是角度。
  • ammoMultiplier: 每份弹药(物品/液体)能发射多少颗这种子弹。
  • reloadMultiplier: 乘以炮塔的装填速度,得到最终的发射速率。
  • buildingDamageMultiplier: 对建筑造成的伤害倍率(乘以 damage)。
  • shieldDamageMultiplier: 对护盾造成的伤害倍率。
  • recoil: 发射时施加给发射者的后坐力。
  • killShooter: 发射这颗子弹是否会杀死发射者(用于自杀式攻击)。
  • targetBlocks,targetMissiles: (炮塔AI用)是否以区块和导弹为目标。
  • keepVelocity: 子弹的初始速度是否继承发射者的速度。
  • scaleLife: 是否根据到目标的距离缩放生命周期(用于 artillery 类武器)。
  • ignoreSpawnAngle: 如果为 true,创建子弹时传入的角度参数将被忽略(通常用于效果子弹,其角度由其他因素决定)。
  • createChance: 子弹被成功创建的概率(0到1之间)。
  • range: (由 calculateRange() 计算得出)子弹的理论最大射程。
  • maxRange,rangeOverride,rangeChange,extraRangeMargin,minRangeChange: 一系列复杂参数,用于覆盖、调整和计算炮塔的实际攻击范围。

5. 特殊效果与行为 (Special Effects & Behaviors)

子弹除了直接撞击外可以触发的其他效果。

  • splashDamage: 范围溅射伤害。
  • scaledSplashDamage: 溅射伤害是否根据单位碰撞箱大小进行“正确”的缩放。
  • splashDamageRadius: 溅射伤害的半径。
  • splashDamagePierce: 溅射伤害是否穿透地形。
  • knockback: 击中单位时造成的击退力。
  • impact: 击退方向是否遵循子弹的方向(true),而不是碰撞点与单位中心的方向(false)。
  • status,statusDuration: 击中时施加的状态效果及其持续时间。
  • healPercent,healAmount: 如果子弹是治疗弹,这两个参数决定治疗量(基于最大生命值的百分比和固定值)。
  • healColor,healEffect: 治疗时效果的颜色和效果类型。
  • lifesteal: 造成伤害后,治疗发射者的比例。
  • makeFire: 是否在击中点生成火焰。
  • instantDisappear: 子弹生成后是否立即消失(用于实现某些瞬时效果)。
  • despawnHit: 子弹自然消失时是否也播放命中效果(hit)。如果子弹有碎片、溅射等效果,此值会自动设为 true

6. 分裂子弹 (Fragmentation)

子弹在命中或消失时产生其他子弹。

  • fragBullet: 分裂出的子弹类型。
  • delayFrags: 是否将分裂子弹的创建延迟到下一帧(用于解决穿透子弹的复杂伤害计算问题)。
  • fragOnHit: 是否在击中时产生分裂子弹。
  • fragOnAbsorb: 是否在被护盾吸收时产生分裂子弹。
  • fragRandomSpread: 分裂子弹的随机角度扩散范围。
  • fragSpread: 分裂子弹之间的均匀角度间隔。
  • fragAngle: 分裂子弹的基础角度偏移。
  • fragBullets: 产生的分裂子弹数量。
  • fragVelocityMin,fragVelocityMax: 分裂子弹速度的随机范围(乘以基础速度)。
  • fragLifeMin,fragLifeMax: 分裂子弹生命周期的随机范围(乘以基础生命周期)。
  • fragOffsetMin,fragOffsetMax: 分裂子弹生成位置距离父子弹的随机偏移量。
  • pierceFragCap: 如果子弹可以穿透,它最多可以释放多少次分裂子弹。

7. 间隔子弹 (Interval Bullets)

子弹在飞行过程中定期发射其他子弹。

  • intervalBullet: 定期发射的子弹类型。
  • bulletInterval: 发射间隔时间(刻)。
  • intervalBullets: 每次间隔发射的子弹数量。
  • intervalRandomSpread: 间隔子弹的随机角度扩散。
  • intervalSpread: 多个间隔子弹之间的角度间隔。
  • intervalAngle: 间隔子弹的角度偏移。
  • intervalDelay: 开始发射间隔子弹前的初始延迟。

8. 生成单位 (Spawning Units)

子弹本身可以生成一个单位(如导弹),或在命中/消失时生成单位。

  • spawnUnit: 子弹本身被替换成的单位类型(例如,发射器射出的其实是一个导弹单位)。
  • despawnUnit: 在命中或消失时生成的单位类型。
  • despawnUnitChance: 生成该单位的概率。
  • despawnUnitCount: 生成单位的数量。
  • despawnUnitRadius: 生成单位位置相对于子弹的随机偏移半径。
  • faceOutwards: 生成的单位是否面朝外(远离子弹中心),而不是面朝子弹的方向。

9. 视觉部件与轨迹 (Visual Parts & Trail)

控制子弹的自定义外观。

  • parts: 一个 DrawPart 序列,用于为子弹添加复杂的自定义绘制部件。
  • trailColor: 轨迹的颜色。
  • trailChance: 每帧产生轨迹效果的概率。
  • trailInterval: 产生轨迹效果的固定间隔时间(刻)。如果 >0,则优先于 trailChance
  • trailMinVelocity: 产生轨迹效果所需的最小速度。
  • trailEffect: 产生的轨迹效果(通常是粒子效果)。
  • trailSpread: 轨迹效果的随机位置偏移。
  • trailParam: 传递给轨迹效果的参数(通常控制大小)。
  • trailRotation: trailParam 参数是否使用子弹的旋转角度。
  • trailLength: 轨迹网格的长度(渲染为一条带)。如果 >0,会启用另一种连续的轨迹渲染方式。
  • trailWidth: 轨迹网格的宽度。
  • trailSinMag,trailSinScl: 轨迹宽度的正弦波波动幅度和 scale。
  • trailInterp: 轨迹宽度随子弹生命周期变化的插值方式。

10. 运动模式 (Movement Patterns)

特殊的子弹运动行为。

  • circleShooter: 子弹是否尝试环绕发射者飞行。
  • circleShooterRadius: 环绕的目标半径。
  • circleShooterRadiusSmooth: 环绕时转向的平滑过渡系数
  • circleShooterRotateSpeed: 环绕旋转的速度乘数。
  • homingPower: 追踪目标的能力强度(转向速度)。
  • homingRange: 追踪传感器的范围。
  • homingDelay: 开始追踪前的延迟时间。
  • followAimSpeed: 子弹跟随发射者准星的速度(用于玩家控制的单位)。
  • weaveScale,weaveMag,weaveRandom: 控制子弹“蛇形”运动的参数(Scale,幅度,是否随机初始方向)。
  • rotateSpeed: 子弹速度向量自身的旋转速度(度/刻)。

11. 其他效果 (Other Effects)

  • lightning: 击中时产生的闪电链数量。
  • lightningColor: 闪电颜色。
  • lightningLength,lightningLengthRand: 闪电基础长度和随机额外长度。
  • lightningDamage: 闪电造成的伤害(如果为负数,则使用子弹的 damage)。
  • lightningCone,lightningAngle: 闪电的扩散锥形角度和基础角度偏移。
  • lightningType: 在闪电端点创建的子弹类型(用于二次攻击)。
  • incendAmount,incendSpread,incendChance: 生成火焰的数量、扩散范围、概率。
  • puddles,puddleRange,puddleAmount,puddleLiquid: 生成液体坑的数量、位置范围、液体量、液体类型。
  • suppressionRange,suppressionDuration,suppressionEffectChance,suppressColor: 抑制敌人方块回复的效果范围、持续时间、生效几率、效果颜色。
  • lightRadius,lightOpacity,lightColor: 子弹发出的动态光照的半径、透明度、颜色。

12. 杂项与内部字段 (Misc & Internal)

  • underwater: (高度实验性) 是否在水下渲染。
  • spawnBullets: 与此子弹同时创建的其他子弹(用于视觉效果,如枪口的多发子弹)。
  • spawnBulletRandomSpread: 上述同时创建的子弹的随机角度扩散。
  • displayAmmoMultiplier: 是否在游戏内统计信息中显示弹药倍率。
  • statLiquidConsumed: 如果 >0,这个值(除以 ammoMultiplier)会显示在统计信息中,用于液体弹药。
  • cachedDps: 内部缓存字段,用于存储估计的每秒伤害值(DPS),避免重复计算。

BulletType类的字段之间存在特定的搭配规则和互斥关系,需要通过测试来验证其实际效果。

关于代码内部的距离单位,有的是格,有的是世界单位,它们之间的换算关系是1格=8世界单位=32方块贴图像素

对于具有实体体积的子弹,BulletType的子类BasicBulletType通常已能满足基本需求,其他子类主要是在此基础上预设了不同的字段值。在原版中,这种不重写方法而仅通过预设字段来定义不同行为的方式称为模板(Template)。以下是一些常见的实体子弹类型:

  • BasicBulletType:常规子弹,例如“双管”使用的子弹;
  • BombBulletType:轰炸弹,例如“天垠”使用的子弹,命中后停留并造成范围伤害;
  • MissileBulletType:导弹,例如“蜂群”使用的子弹,会生成尾迹效果;
  • ArtilleryBulletType:炮弹,例如“冰雹”使用的子弹,可提前结束生命周期并带有尾迹特效;
  • FlakBulletType:高射炮弹,例如“分裂”使用的子弹,可在目标附近爆炸,无需直接命中;
  • LaserBoltBulletType:激光弹,例如“新星”使用的子弹,在基础贴图上叠加绘制两条彩色光线;
  • InterceptorBulletType:拦截弹,与PointDefenseBulletWeapon配合使用,当前版本中未实际应用;
  • EmpBulletType:电磁脉冲弹,例如“龙王”使用的子弹,可降低敌方建筑速度并对范围内单位与建筑造成伤害;
  • MassDriverBolt:质量驱动器抛射物,由质量驱动器发射,可造成伤害。其内容物会影响伤害值。在v149版本中,Anuke添加了使用质量驱动器击杀敌人的成就。

综上所述,BulletType系统虽然核心是为有实体、有弹道的抛射物(如炮弹、子弹)设计的,但其灵活的设计也允许它服务于另一类特殊的攻击形式。BulletType类的这种特殊用法称为“虚拟子弹”或“空子弹”模式,适用于需要子弹系统管理生命周期和触发逻辑,但不需要实际弹道运动的情况。通过将speed设为0、collide设为false,可以创建一个静止的子弹实体,然后通过其drawupdate方法实现远程绘制和伤害效果,常用于激光武器、持续光束、区域效果等非弹道型攻击。

  • PointLaserBulletType:用于采矿激光,例如“光辉”,其视觉效果与钻头光束类似;
  • PointBulletType:用于点防御激光,例如“裂解”,表现为绘制一条线段并消除命中的子弹;
  • LightningBulletType:用于释放闪电链,例如“电弧”;
  • ShrapnelBulletType:用于发射激光尖刺,例如“雷光”;
  • LaserBulletType:用于发射激光束,例如“蓝瑟”;
  • LiquidBulletType:用于发射液体球体,例如“波浪”;
  • RailBulletType:用于模拟轨道炮攻击,例如“厄兆”,表现为瞬间命中目标;
  • ExplosionBulletType:用于创建爆炸效果,例如“遏止”主炮的终结攻击;
  • SapBulletType:用于施加抑制效果,例如“血蛭”,表现为绘制紫色线条并施加状态效果;
  • ContinuousLaserBulletType:用于持续激光武器,例如“熔毁”,开火后可维持一段时间;
  • ContinuousLiquidBulletType:用于持续液体喷射武器,例如“升华”,开火后可无限期维持;
  • MultiBulletType:用于组合上述多种子弹类型的效果。

以上就是原版中的主要子弹类型了。未来你也可以自己定制子弹类型,并进一步理解子弹的生命周期与背后的 ECS(Entity-Component-System)思想。

射击方式(ShootPattern)

  • ShootPattern:定义了发数shots、首发延迟firstShotDelay和间发延迟shotDelay。该模式为默认射击方式,通常设置为单发,也存在如“天灾”设置为两发的实例;
  • ShootBarrel:所有炮管依次发射,通过barrels设置炮管的x、y坐标和方向,例如“蜂群”。受炮塔结构限制,其炮管无法像单位的武器那样独立运作;
  • ShootAlternate:多个间距相等、沿x轴排列的炮管依次发射,通过barrels设置炮管数量,spread设置炮管间距。例如“双管”,以及多管炮塔如四管的“发散”和五管的“天谴”;
  • ShootSpread:多个间隔角度相等的子弹同时发射,通过spread设置间隔角度(角度制),例如“雷光”;
  • ShootSine:多个发射角度呈正弦周期变化的子弹同时发射,原版内容中未使用此模式;
  • ShootHelix:使发射出的子弹按正弦曲线运动,例如“天谴”;
  • ShootSummon:在一定区域范围内发射角度随机的子弹,例如“魔灵”;
  • ShootMulti:可组合上述多种射击模式。

上述“依次发射”是指,当shots设定的子弹数量大于炮管数量时,子弹会按炮管顺序循环发射。例如“蜂群”有三个炮管,若设置shots=4,则每次射击会依次使用三个炮管发射共四发子弹。

炮塔绘制器

炮塔和工厂一样拥有drawer字段,常与炮塔搭配的drawer是DrawTurret,用于将炮塔的完整贴图分解为多个可独立运动的DrawPart(绘制部件),通过控制每个部件的位移、旋转等参数,实现动态视觉效果,例如原版“魔灵”炮塔的分块动画。

首先介绍DrawTurret所需的贴图。DrawTurret使用的贴图均为可选,包括大小预览贴图-preview、液体层贴图-liquid、顶层贴图-top、热量贴图-heat和基座贴图-base。这些贴图仅在找到时被绘制,不会产生“ohno”贴图。对于流体层贴图,还需要指定liquidDraw来设置要绘制的流体。

DrawPart

mindustry.entities.part.DrawPart 是可独立运动的绘制部件抽象基类,DrawTurret 渲染时会为每个部件填充一组 PartParams(位置、旋转、热量、装填进度等)。你可以把它理解为“把炮塔贴图拆成多块,然后按开火过程驱动它们运动”的系统。

DrawPart 基础字段(所有部件通用)

字段说明
params全局复用的 PartParams 实例,供渲染流程临时写入数据。
turretShading是否使用炮塔阴影/着色,通常由引擎自动设置。
under是否绘制在炮塔本体下层。
weaponIndex单位武器部件索引,影响进度来源。
recoilIndex使用哪一个后坐力计数器,<0 表示基础后坐。

PartParams(绘制参数)字段一览

字段说明
warmup持续开火的升温进度(0~1)。
reload装填进度(刚开火为 1,装填完成为 0)。
smoothReload平滑后的装填进度。
heat发射后热量进度(1→0)。
recoil原始后坐力值。
life生命周期进度(仅部分弹体/单位部件使用)。
charge蓄能进度(0→1)。
x绘制坐标 X。
y绘制坐标 Y。
rotation绘制旋转角度。
sideOverride强制使用某一侧的渲染索引,-1 为默认。
sideMultiplier侧向渲染倍率(常用于镜像)。

PartMove(额外位移片段)字段一览

字段说明
progress位移片段跟随的进度。
x额外平移 X。
y额外平移 Y。
gx额外缩放 X。
gy额外缩放 Y。
rot额外旋转。

RegionPart

用于绘制贴图区域,是最常用的 DrawPart,适合炮管、装甲、炮口等“真实贴图”部件。
贴图命名:默认拼接 炮塔名 + suffix,并自动读取 -outline-heat-light;若 mirror=true,则读取 -r/-l-r-outline/-l-outline

RegionPart 字段一览

字段说明
suffix贴图名后缀(默认拼接在炮塔名后)。
name完整贴图名,设置后会覆盖默认拼接。
heat热量贴图区域(-heat)。
light发光贴图区域(-light)。
regions主贴图区域数组。
outlines描边贴图区域数组(-outline)。
mirror是否左右镜像(需 -l/-r 贴图)。
outline是否绘制描边。
replaceOutline是否用原贴图替换描边(原版用法)。
drawRegion是否绘制主贴图(可用于仅热量效果)。
heatLight热量贴图是否产生光照。
clampProgress是否把进度夹在 0~1。
progress位置/旋转跟随的进度。
growProgress缩放跟随的进度。
heatProgress热量透明度跟随的进度。
blending贴图混合模式。
layer绘制层级。
layerOffset层级偏移。
heatLayerOffset热量层级偏移。
turretHeatLayer使用炮塔热量层时的层级。
outlineLayerOffset描边层级偏移。
x基础位置 X。
y基础位置 Y。
xScl基础缩放 X。
yScl基础缩放 Y。
rotation基础旋转角度。
originX旋转原点 X 偏移。
originY旋转原点 Y 偏移。
moveX随进度平移 X。
moveY随进度平移 Y。
growX随进度缩放 X。
growY随进度缩放 Y。
moveRot随进度旋转角度。
heatLightOpacity热量光照强度。
color主贴图颜色。
colorTo主贴图渐变目标色。
mixColor混色颜色。
mixColorTo混色渐变目标色。
heatColor热量贴图颜色。
children子部件序列。
moves额外位移片段序列。

ShapePart

用于绘制几何图形(多边形/圆形/线框),常用于能量护环、魔法阵、简化装饰。

ShapePart 字段一览

字段说明
circle是否绘制圆形。
hollow是否绘制空心(线框)。
sides多边形边数。
radius基础半径。
radiusTo目标半径(插值)。
stroke基础描边宽度。
strokeTo目标描边宽度(插值)。
x基础位置 X。
y基础位置 Y。
rotation基础旋转角度。
moveX随进度平移 X。
moveY随进度平移 Y。
moveRot随进度旋转角度。
rotateSpeed自转速度。
color基础颜色。
colorTo目标颜色(渐变)。
mirror是否镜像绘制。
clampProgress是否把进度夹在 0~1。
progress进度来源。
layer绘制层级。
layerOffset层级偏移。

HaloPart

用于绘制“环绕式”图形:多个形状围绕中心旋转,常见于仪式感或科技感炮塔。

HaloPart 字段一览

字段说明
hollow是否空心。
tri是否使用三角光片模式。
shapes围绕形状数量。
sides单个形状边数。
radius单个形状半径。
radiusTo目标半径(插值)。
stroke描边宽度。
strokeTo目标描边宽度(插值)。
triLength三角光片长度。
triLengthTo目标三角长度(插值)。
haloRadius环半径。
haloRadiusTo目标环半径(插值)。
x基础位置 X。
y基础位置 Y。
shapeRotation单个形状基础旋转。
moveX随进度平移 X。
moveY随进度平移 Y。
shapeMoveRot随进度形状旋转。
haloRotateSpeed环自转速度。
haloRotation环基础旋转角度。
rotateSpeed单个形状自转速度。
color基础颜色。
colorTo目标颜色(渐变)。
mirror是否镜像绘制。
clampProgress是否把进度夹在 0~1。
progress进度来源。
layer绘制层级。
layerOffset层级偏移。

FlarePart

用于绘制“光芒/星芒”效果,由多组三角形叠加构成,适合充能、激发类视觉。

FlarePart 字段一览

字段说明
sides光芒数量。
radius基础长度。
radiusTo目标长度(插值)。
stroke光芒宽度。
innerScl内层光芒缩放。
innerRadScl内层光芒长度缩放。
x基础位置 X。
y基础位置 Y。
rotation基础旋转。
rotMove随进度旋转。
spinSpeed自旋速度。
followRotation是否跟随炮塔旋转。
color1外层颜色。
color2内层颜色。
clampProgress是否把进度夹在 0~1。
progress进度来源。
layer绘制层级。

EffectSpawnerPart

用于在一个矩形范围内刷出粒子/特效,适合火花、能量溢散等效果。

EffectSpawnerPart 字段一览

字段说明
x生成区域中心 X。
y生成区域中心 Y。
width生成区域宽度。
height生成区域高度。
rotation生成区域旋转角度。
mirror是否镜像生成。
effectChance生成概率。
effectRot固定旋转角度。
effectRandRot随机旋转幅度。
effect特效类型。
effectColor特效颜色。
useProgress是否让概率随进度变化。
progress进度来源。
debugDraw是否绘制调试红框。

HoverPart

用于绘制“悬浮环/扫描圈”一类的脉动线框效果,常用于支撑感与能量感装饰。

HoverPart 字段一览

字段说明
radius环半径。
x基础位置 X。
y基础位置 Y。
rotation基础旋转角度。
phase脉动周期。
stroke最大线宽。
minStroke最小线宽。
circles环数量。
sides多边形边数。
color颜色。
mirror是否镜像绘制。
layer绘制层级。
layerOffset层级偏移。

PartProgress

PartProgress 是“进度驱动器”,决定部件随什么参数变化。下面表格列出全部可用项。

名称类型说明
reload内置进度刚开火为 1,装填完成为 0。
smoothReload内置进度平滑后的装填进度。
warmup内置进度持续开火时升到 1,停火后回落。
charge内置进度蓄能进度(0→1)。
recoil内置进度原始后坐力值。
heat内置进度发射后升温,再冷却回 0。
life内置进度生命周期进度(仅部分弹体/单位部件使用)。
time内置进度当前 Time.time 值。
constant(value)工具方法固定进度值。
get(p)接口方法取进度值,通常由引擎调用。
getClamp(p)工具方法获取并夹到 0~1(默认夹取)。
getClamp(p, clamp)工具方法可选择是否夹取。
inv()链式方法取反(1 - 值)。
slope()链式方法斜率形状(Mathf.slope)。
clamp()链式方法强制夹到 0~1。
add(amount)链式方法加上常数。
add(other)链式方法与另一个进度相加。
delay(amount)链式方法延迟开始。
curve(offset, duration)链式方法指定区间映射。
sustain(offset, grow, sustain)链式方法生长-维持-衰减曲线。
shorten(amount)链式方法压缩时长。
compress(start, end)链式方法在指定区间内压缩。
blend(other, amount)链式方法与另一个进度线性混合。
mul(other)链式方法与另一个进度相乘。
mul(amount)链式方法乘以常数。
min(other)链式方法取较小值。
sin(offset, scl, mag)链式方法正弦叠加(带偏移)。
sin(scl, mag)链式方法正弦叠加。
absin(scl, mag)链式方法绝对正弦叠加。
mod(amount)链式方法取模循环。
loop(time)链式方法按时间循环到 0~1。
apply(other, func)链式方法PartFunc 组合两个进度。
curve(interp)链式方法应用插值函数。

示例:让炮管在开火后回缩,并在末段加一点呼吸抖动:

java
var part = new RegionPart("-barrel");
part.progress = PartProgress.recoil;
part.moveY = -3f;
part.moves.add(new PartMove(PartProgress.recoil.delay(0.6f), 0f, -0.6f, 0f));

此处我们以魔灵为例,结合代码解析以上内容:

java
malign = new PowerTurret("malign"){{
    requirements(Category.turret, with(Items.carbide, 200, Items.beryllium, 1000, Items.silicon, 500, Items.graphite, 500, Items.phaseFabric, 200));

    var haloProgress = PartProgress.warmup;
    Color haloColor = Color.valueOf("d370d3"), heatCol = Color.purple;
    float haloY = -15f, haloRotSpeed = 1.5f;

    var circleProgress = PartProgress.warmup.delay(0.9f);
    var circleColor = haloColor;
    float circleY = 25f, circleRad = 11f, circleRotSpeed = 3.5f, circleStroke = 1.6f;

    shootSound = Sounds.shootMalign;
    loopSound = Sounds.loopMalign;
    loopSoundVolume = 1.3f;

    shootType = new FlakBulletType(8f, 70f){{
        sprite = "missile-large";

        lifetime = 40f;
        width = 12f;
        height = 22f;

        hitSize = 7f;
        shootEffect = Fx.shootSmokeSquareBig;
        smokeEffect = Fx.shootSmokeDisperse;
        ammoMultiplier = 1;
        hitColor = backColor = trailColor = lightningColor = circleColor;
        frontColor = Color.white;
        trailWidth = 3f;
        trailLength = 12;
        hitEffect = despawnEffect = Fx.hitBulletColor;
        buildingDamageMultiplier = 0.3f;

        trailEffect = Fx.colorSpark;
        trailRotation = true;
        trailInterval = 3f;

        homingPower = 0.17f;
        homingDelay = 19f;
        homingRange = 160f;

        explodeRange = 100f;
        explodeDelay = 0f;

        flakInterval = 20f;
        despawnShake = 3f;

        intervalBullet = new LightningBulletType() {{
            lightningColor = circleColor;
            lightningCone = 15f;
            lightningLength = 35;
            lightningLengthRand = 5;
            damage = 18f;
        }};

        fragBullet = new LaserBulletType(65f){{
            colors = new Color[]{haloColor.cpy().a(0.4f), haloColor, Color.white};
            buildingDamageMultiplier = 0.25f;
            width = 19f;
            hitEffect = Fx.hitLancer;
            sideAngle = 175f;
            sideWidth = 1f;
            sideLength = 40f;
            lifetime = 22f;
            drawSize = 400f;
            length = 120f;
            pierceCap = 2;
            optimalLifeFract = 1f;
        }};

        intervalBullets = 1;
        fragSpread = fragRandomSpread = intervalRandomSpread = 0f;
        bulletInterval = 20f;

        splashDamage = 0f;
        hitEffect = Fx.hitSquaresColor;
        collidesGround = true;
    }};

    size = 5;
    drawer = new DrawTurret("reinforced-"){{
        parts.addAll(

        //summoning circle
        new ShapePart(){{
            progress = circleProgress;
            color = circleColor;
            circle = true;
            hollow = true;
            stroke = 0f;
            strokeTo = circleStroke;
            radius = circleRad;
            layer = Layer.effect;
            y = circleY;
        }},

        new ShapePart(){{
            progress = circleProgress;
            rotateSpeed = -circleRotSpeed;
            color = circleColor;
            sides = 4;
            hollow = true;
            stroke = 0f;
            strokeTo = circleStroke;
            radius = circleRad - 1f;
            layer = Layer.effect;
            y = circleY;
        }},

        //outer squares

        new ShapePart(){{
            progress = circleProgress;
            rotateSpeed = -circleRotSpeed;
            color = circleColor;
            sides = 4;
            hollow = true;
            stroke = 0f;
            strokeTo = circleStroke;
            radius = circleRad - 1f;
            layer = Layer.effect;
            y = circleY;
        }},

        //inner square
        new ShapePart(){{
            progress = circleProgress;
            rotateSpeed = -circleRotSpeed/2f;
            color = circleColor;
            sides = 4;
            hollow = true;
            stroke = 0f;
            strokeTo = 2f;
            radius = 3f;
            layer = Layer.effect;
            y = circleY;
        }},

        //spikes on circle
        new HaloPart(){{
            progress = circleProgress;
            color = circleColor;
            tri = true;
            shapes = 3;
            triLength = 0f;
            triLengthTo = 5f;
            radius = 6f;
            haloRadius = circleRad;
            haloRotateSpeed = haloRotSpeed / 2f;
            shapeRotation = 180f;
            haloRotation = 180f;
            layer = Layer.effect;
            y = circleY;
        }},

        //actual turret
        new RegionPart("-mouth"){{
            heatColor = heatCol;
            heatProgress = PartProgress.warmup;

            moveY = -8f;
        }},
        new RegionPart("-end"){{
            moveY = 0f;
        }},

        new RegionPart("-front"){{
            heatColor = heatCol;
            heatProgress = PartProgress.warmup;

            mirror = true;
            moveRot = 33f;
            moveY = -4f;
            moveX = 10f;
        }},
        new RegionPart("-back"){{
            heatColor = heatCol;
            heatProgress = PartProgress.warmup;

            mirror = true;
            moveRot = 10f;
            moveX = 2f;
            moveY = 5f;
        }},

        new RegionPart("-mid"){{
            heatColor = heatCol;
            heatProgress = PartProgress.recoil;

            moveY = -9.5f;
        }},

        new ShapePart(){{
            progress = haloProgress;
            color = haloColor;
            circle = true;
            hollow = true;
            stroke = 0f;
            strokeTo = 2f;
            radius = 10f;
            layer = Layer.effect;
            y = haloY;
        }},
        new ShapePart(){{
            progress = haloProgress;
            color = haloColor;
            sides = 3;
            rotation = 90f;
            hollow = true;
            stroke = 0f;
            strokeTo = 2f;
            radius = 4f;
            layer = Layer.effect;
            y = haloY;
        }},
        new HaloPart(){{
            progress = haloProgress;
            color = haloColor;
            sides = 3;
            shapes = 3;
            hollow = true;
            stroke = 0f;
            strokeTo = 2f;
            radius = 3f;
            haloRadius = 10f + radius/2f;
            haloRotateSpeed = haloRotSpeed;
            layer = Layer.effect;
            y = haloY;
        }},

        new HaloPart(){{
            progress = haloProgress;
            color = haloColor;
            tri = true;
            shapes = 3;
            triLength = 0f;
            triLengthTo = 10f;
            radius = 6f;
            haloRadius = 16f;
            haloRotation = 180f;
            layer = Layer.effect;
            y = haloY;
        }},
        new HaloPart(){{
            progress = haloProgress;
            color = haloColor;
            tri = true;
            shapes = 3;
            triLength = 0f;
            triLengthTo = 3f;
            radius = 6f;
            haloRadius = 16f;
            shapeRotation = 180f;
            haloRotation = 180f;
            layer = Layer.effect;
            y = haloY;
        }},

        new HaloPart(){{
            progress = haloProgress;
            color = haloColor;
            sides = 3;
            tri = true;
            shapes = 3;
            triLength = 0f;
            triLengthTo = 10f;
            shapeRotation = 180f;
            radius = 6f;
            haloRadius = 16f;
            haloRotateSpeed = -haloRotSpeed;
            haloRotation = 180f / 3f;
            layer = Layer.effect;
            y = haloY;
        }},

        new HaloPart(){{
            progress = haloProgress;
            color = haloColor;
            sides = 3;
            tri = true;
            shapes = 3;
            triLength = 0f;
            triLengthTo = 4f;
            radius = 6f;
            haloRadius = 16f;
            haloRotateSpeed = -haloRotSpeed;
            haloRotation = 180f / 3f;
            layer = Layer.effect;
            y = haloY;
        }}
        );

        Color heatCol2 = heatCol.cpy().add(0.1f, 0.1f, 0.1f).mul(1.2f);
        for(int i = 1; i < 4; i++){
            int fi = i;
            parts.add(new RegionPart("-spine"){{
                outline = false;
                progress = PartProgress.warmup.delay(fi / 5f);
                heatProgress = PartProgress.warmup.add(p -> (Mathf.absin(3f, 0.2f) - 0.2f) * p.warmup);
                mirror = true;
                under = true;
                layerOffset = -0.3f;
                turretHeatLayer = Layer.turret - 0.2f;
                moveY = 9f;
                moveX = 1f + fi * 4f;
                moveRot = fi * 60f - 130f;

                color = Color.valueOf("bb68c3");
                heatColor = heatCol2;
                moves.add(new PartMove(PartProgress.recoil.delay(fi / 5f), 1f, 0f, 3f));
            }});
        }
    }};

    velocityRnd = 0.15f;
    heatRequirement = 144f;
    maxHeatEfficiency = 1f;
    warmupMaintainTime = 120f;
    consumePower(40f);
    unitSort = UnitSorts.strongest;
    shoot = new ShootSummon(0f, 0f, circleRad, 20f);

    minWarmup = 0.96f;
    shootWarmupSpeed = 0.08f;

    shootY = circleY - 5f;

    outlineColor = Pal.darkOutline;
    envEnabled |= Env.space;
    reload = 3.5f;
    range = 410;
    trackingRange = range * 1.4f;
    shootCone = 100f;
    scaledHealth = 370;
    rotateSpeed = 2.6f;
    recoil = 0.5f;
    recoilTime = 30f;
    shake = 3f;
}};

创建一个Turret

有了这些东西,是时候创建一个炮塔了。

java
new ItemTurret("tutorial-item-turret"){{
    requirements(Category.turret, with(Items.copper, 39));
    ammo(Items.copper, new BasicBulletType(1.5f, 9));
    shoot = new ShootPattern();
    drawer = new DrawTurret(){{
        parts.add(new RegionPart("-barrel"));
    }};
    consumePower(40f);
    coolant = consumeCoolant(0.1f);
}};
kotlin
ItemTurret("tutorial-item-turret").apply {
    requirements(Category.turret, with(Items.copper, 39))
    ammo(Items.copper, BasicBulletType(1.5f, 9))
    shoot = ShootPattern()
    drawer = DrawTurret().apply{
        parts.add(RegionPart("-barrel"))
    }
    consumePower(40f)
    coolant = consumeCoolant(0.1f)
}

炮塔类型具有明确的专用性,这主要源于不同子弹类型在功能实现上的显著差异。 炮塔的基类包括BaseTurretReloadTurretTurret,其主要功能涵盖射程range(单位为像素,1格=8像素)、冷却时间reload、子弹容量maxAmmo的设置,以及上文在BulletType中已提及的部分字段。

常见的炮塔类型包括:

  • ItemTurret:物品炮塔,使用ammo(...)方法设置弹药与子弹类型;
  • LiquidTurret:流体炮塔,使用ammo(...)方法设置弹药与子弹类型;
  • PowerTurret:电力炮塔,使用consumePower方法设置电力消耗,通过shootType字段设置子弹类型;
  • LaserTurret:激光炮塔(例如“熔毁”而非“蓝瑟”),通常建议使用ContinuousLaserBulletType作为子弹类型,并通过shootDuration字段设置持续时间;
  • ContinuousTurret:连续炮塔(例如“光辉”),开火后可持续射击,需配合具有连续效果的子弹类型;
  • ContinuousLiquidTurret:连续流体炮塔(例如“升华”),使用ammo(...)方法设置弹药与子弹类型,需配合具有连续效果的子弹类型。

部分方块虽归类于炮塔建造栏,但其功能与常规炮塔存在差异:

  • TractorBeamTurret:牵引光束类
  • PointDefenseTurret:点防御类
  • BuildTurret:建造塔类

使用 ammo 方法声明子弹类型的示例如下:

java
ammo(Items.copper,  new BasicBulletType(3.5f, 18),
     Items.lead, new FlakBulletType(4.2f, 3))

在炮塔建筑中,当物品进入炮塔的一瞬间就会变成弹药。

炮塔类的部分字段如下:

1. 核心逻辑与计时器 (Core Logic & Timers)

这些字段控制炮塔的基础运行逻辑和内部计时。

  • logicControlCooldown (static final): 一个静态常量。当炮塔被逻辑模块控制后,需要经过这个时间(2秒 * 60帧/秒 = 120帧)才会恢复正常AI。
  • timerTarget: 一个内部计时器ID,用于“寻找目标”这个动作的冷却。timers++ 表示它从父类继承了一个计时器数组,并分配了一个新的索引。
  • targetInterval: 尝试寻找新目标的间隔时间(帧)。即使没有目标,也会定期执行搜索。
  • newTargetInterval: 当炮塔已有有效目标时,尝试寻找新目标的间隔时间。如果为 -1,则使用 targetInterval

2. 弹药系统 (Ammunition System)

控制炮塔如何消耗和存储弹药。

  • maxAmmo: 炮塔内最大可储存的弹药单位数。
  • ammoPerShot: 每次射击消耗的弹药单位数。
  • consumeAmmoOnce: 如果为 true,无论一次射击发射多少发子弹(例如散射),都只消耗 ammoPerShot 份弹药。如果为 false,则每发子弹都会消耗弹药。
  • heatRequirement: (对于需要热量的炮塔)开火所需的最低热量值。-1 表示不需要热量。
  • maxHeatEfficiency: (对于需要热量的炮塔)最大热量效率乘数。热量越高,效率(通常是伤害或射速)越高,直到这个上限。

3. 射击精度与弹道 (Shooting Accuracy & Ballistics)

控制子弹发射时的随机性和行为。

  • inaccuracy: 子弹的角度随机性(散射),单位是度。
  • velocityRnd: 子弹速度的随机 fraction。例如 0.1 表示速度会有 ±10% 的随机波动。
  • scaleLifetimeOffset: 一个 fraction,会乘以某个值然后加到子弹的生命周期上。(不确定:具体乘以的是子弹的原始生命周期还是另一个值?代码中需确认 lifeScale 的计算)
  • shootCone: 炮塔开火的容忍角度。如果炮塔当前旋转角度与目标角度之差小于此值,即使未完全对准也会开火。
  • shootX, shootY: 子弹生成的相对坐标(相对于炮塔中心)。shootY = Float.NEGATIVE_INFINITY 是一个常见的默认值,通常意味着如果未设置,则会使用炮塔本身的高度或其他逻辑来计算。
  • xRand: 在X轴(水平轴)上的随机偏移量,用于给子弹生成位置增加随机性。

4. 目标选择与范围 (Targeting & Range)

控制炮塔如何寻找和锁定目标。

  • drawMinRange: 如果为 true,在显示炮塔范围时也会绘制最小范围圈。
  • trackingRange: 跟踪范围。在此范围内的目标会被炮塔发现并跟踪(旋转炮身),但不会开火。必须小于或等于射程。
  • minRange: 最小射程。在此范围内的目标不会被攻击(主要用于 Artillery- artillery 类武器)。
  • targetAir, targetGround: 是否以空中或地面单位为目标。
  • targetBlocks: 是否以敌方建筑为目标。
  • targetHealing: 如果为 true,此炮塔会以友方(需要治疗的)建筑为目标(例如用于治疗炮塔)。
  • targetUnderBlocks: 如果为 false,则不会以“下层”方块(如下方的传送带)为目标。
  • predictTarget: 是否预测移动中目标的位置(提前量计算)。
  • unitSort: 一个排序函数(Sortf),用于在多个可用目标中选择优先攻击哪个。UnitSorts.closest 是默认的“最近优先”。
  • unitFilter: 一个过滤函数(Boolf<Unit>),用于判断哪些单位可以被攻击。u -> true 表示默认所有单位都可以。
  • buildingFilter: 一个过滤函数(Boolf<Building>),用于判断哪些建筑可以被攻击。默认逻辑是:如果 targetUnderBlocksfalse 且建筑是“下层子弹”类型,则过滤掉。

5. 射击控制与冷却 (Firing Control & Cooldown)

控制炮塔的射击节奏、预热和冷却。

  • minWarmup: 最低预热值。只有当炮塔的 warmup 值(从0到1)达到或超过此值时,才能开火。
  • accurateDelay: 如果为 true,炮塔在具有 firstShotDelay 的情况下,会精确计算延迟以命中移动目标。
  • moveWhileCharging: (firstShotDelay > 0 时)如果为 false,炮塔在充能/准备射击时无法旋转。
  • reloadWhileCharging: (firstShotDelay > 0 时)如果为 false,炮塔在充能/准备射击时无法装填弹药。
  • warmupMaintainTime: 停止射击后,预热值保持不衰减的时间。
  • shoot: 一个 ShootPattern 对象,定义了射击模式(例如单发、散射、脉冲等)。这是控制多子弹发射的核心。
  • alwaysShooting: 如果为 true,只要炮塔有弹药,它就会一直射击,无视范围内是否有目标或任何控制信号。常用于装饰性或特殊效果的炮塔。
  • cooldownTime: 视觉上的“热量区域”冷却所需的时间(帧)。

6. 玩家控制与显示 (Player Control & Display)

与玩家交互和UI显示相关的设置。

  • playerControllable: 玩家是否可以直接手动控制这个炮塔(例如点击并攻击特定目标)。
  • displayAmmoMultiplier: 是否在炮塔的UI状态中显示弹药效率乘数(对于某些不直接使用弹药的炮塔可能不相关)。

7. 效果与音效 (Effects & Sounds)

控制炮塔射击时的视听反馈。

  • heatColor: 炮塔过热时绘制的热量区域的颜色。
  • shootEffect, smokeEffect: 射击效果和烟雾效果的覆盖。如果为 null,则使用子弹类型中定义的效果。
  • ammoUseEffect: 必定播放的效果,在消耗弹药时触发。
  • shootSound: 发射单发子弹时的声音。
  • chargeSound: 当 shoot.firstShotDelay > 0 时,开始充能/准备时播放的声音。
  • loopSound: 炮塔处于活动状态时(例如预热值 > 0)循环播放的声音。应谨慎使用,避免性能问题
  • loopSoundVolume: 活动循环音效的基础音量。
  • soundPitchMin, soundPitchMax: 射击音效音高的随机范围,用于增加变化。
  • ammoEjectBack: 弹药弹出效果在Y轴(向下)方向的偏移量,模拟弹壳向后抛出。

8. 视觉与动画 (Visuals & Animation)

控制炮塔的视觉表现,如后坐力、震动等。

  • shootWarmupSpeed: 预热值增加或减少的插值速度。
  • linearWarmup: 预热值的增长是线性的(true)还是遵循某种曲线(false)。
  • recoil: 每次射击时,炮身向后移动的视觉距离。
  • recoils: 额外的后坐力计数器数量。-1 通常表示使用默认值(可能是1)。(不确定:具体如何影响视觉效果?可能是为多个炮管独立设置后坐力)
  • recoilTime: 后坐力动画恢复原状所需的时间(帧)。如果为 -1,则使用装填时间(reload)。
  • recoilPow: 应用于后坐力动画的幂曲线,控制运动的效果(例如先快后慢)。
  • elevation: 炮塔阴影的视觉高度(Elevation)。-1 表示使用默认值。
  • shake: 每次射击时造成的屏幕震动强度。
  • drawer: 一个 DrawBlock 对象,负责处理这个炮塔的所有绘制逻辑。DrawTurret() 是标准的炮塔绘制器。