Skip to content
游戏世界地形纹理 · 机制与百科资料

火焰与爆炸 ​

普通火焰、营火和火药桶都会提供热量或破坏能力,但它们不是同一种机制。若混为一谈,容易造成木屋失火、掉落物被烧毁,或在狭窄矿道内被爆炸波及。

先在测试世界验证

爆炸会永久改变地形并可能引发连锁火灾。第一次使用某种火药桶、燃烧弹或电起爆结构时,应在创造测试世界远离建筑与动物验证,不要直接在主存档试验。

三种机制先分开 ​

机制主要用途核心风险
普通火焰燃烧可燃方块、伤害实体蔓延、烧毁建筑和掉落物
营火提供热量、照明并驱赶部分捕食者靠得太近会受热或着火,需要持续补充燃料
爆炸物快速破坏地形、机关或战斗用途方块破坏、火灾与连锁爆炸

营火是相对可控的热源,不等于可以在任何位置安全放置。普通火焰则可能由火柴、岩浆、雷击、燃烧弹药或爆炸引起,并沿可燃材料继续扩散。

火焰系统 ​

火焰是游戏中最具扩散性的危险源。一旦点燃,它会沿着可燃物向四周蔓延,烧毁木屋、森林乃至整个基地。但火焰也有规律可循——理解它的传播方式和可燃物数据,你就能有效防火。

火焰的诞生与存在形式 ​

  • 火焰方块编号为 104,它不是独立存在的方块,而是附着在可燃方块表面的火焰面。
  • 当火焰方块数据值为 0 且下方方块的火焰持续时间为 0 时,火焰方块会被移除(自然熄灭)。
  • 每个火焰点都维护一组燃烧计时器,分别对应四个水平方向和向下方烧穿。计时器的初始值等于方块的火焰持续时间,但有 ±25% 的随机波动,使每次火灾的蔓延速度都不完全相同。

火焰传播机制 ​

火焰传播有两种方式:直接烧穿和概率蔓延。

直接烧穿:当某个方向的燃烧计时器归零时,该方向相邻的可燃方块会被直接摧毁(变为空气),并在其周围生成新的火焰。新火焰的蔓延能力每次传播衰减 15%,初始为 1.0,当降至 0.25 以下时烧穿的方块不再生成新火焰。

概率蔓延:火焰在烧穿方块的同时,还会以一定概率向附近的方块扩散。关键规律:

  • 向上蔓延最快:正上方 1 格每次扫描有 4% 概率被引燃
  • 向下蔓延最慢:正下方 1 格每次扫描只有 2% 概率——向上蔓延速度是向下的两倍
  • 水平方向居中:侧面 1 格约 2% 概率
  • 最大蔓延范围:向上和水平方向最远 2.5 格,向下最远 1.5 格

烧毁操作每 5 游戏秒(约 4 现实秒)批量处理一次,届时所有排队等待烧毁的方块会被同时摧毁。

可燃物数据 ​

不同方块的火焰持续时间和爆炸抗性各不相同。火焰持续时间越长,方块烧穿所需的时间越久。

常见可燃方块一览:

方块火焰持续时间爆炸抗性说明
各种原木2020燃烧较慢,是建筑主要可燃物
木板2020与原木相同
各种树叶82燃烧快,极易引燃
草、花、黑麦62燃烧极快,火焰瞬间扩散
树苗82
仙人掌1010
木门123
木栅栏153
木楼梯/台阶155
工作台/箱子205
玻璃窗152受热碎裂
煤炭块99999950几乎永不烧尽
南瓜102
藤蔓61

注意:火焰持续时间为 0 的方块(如石头、泥土)不会被火焰点燃,火焰遇到它们会停止蔓延。

失火时怎么处理 ​

  1. 快速离开燃烧方块和可能倒塌、爆炸的区域。
  2. 使用水或移除仍在燃烧的方块切断火势;不要站在火焰中操作。
  3. 把未被点燃的燃料、火药桶和贵重掉落物移出传播方向。
  4. 火熄灭后检查屋顶、夹层、树叶和相邻木结构,防止遗漏火点。
  5. 雷雨期间不要立刻在高处修复可燃屋顶,先等待环境

被火焰杀死的动物可能留下熟肉,但皮革、羽毛、鞍具等其他掉落物也可能被烧毁。

火焰防护建议 ​

根据火焰传播规律,玩家可以采取以下措施:

  • 防火隔离带:在木建筑周围留出 3 格以上的非可燃区域(石头、泥土等),因为火焰最大水平蔓延范围为 2.5 格
  • 注意上方空间:火焰向上蔓延概率是向下的两倍,木屋顶上方不要有树叶等易燃物
  • 雨天安全:雨水会浇灭火焰,雨天火灾风险大幅降低
  • 水桶备用:水可以直接扑灭火焰方块

营火系统 ​

营火是玩家可控的火源,用于照明和烹饪。与自然火焰不同,营火不会蔓延,但需要持续添加燃料才能维持。理解营火的燃料机制,你就能高效利用每一块木材。

营火基础 ​

营火方块的尺寸存储在数据值中,范围为 0~15,其中 0 表示熄灭,15 表示最大火力。尺寸越大火焰粒子越大,从微弱火苗到旺盛篝火线性变化。

添加燃料 ​

玩家可以通过以下方式添加燃料:

  • 手持燃料右键点击营火
  • 将燃料物品投掷到营火上(投射物命中)

添加燃料时,系统按以下逻辑处理:

  1. 爆炸物检查:如果投入的物品是爆炸物(如火药桶),直接触发爆炸
  2. 灭火物检查:如果投入雪块、雪球或冰块,营火尺寸减小 1
  3. 燃料计算:如果物品有燃料热值(>0),每个燃料可增加的营火尺寸 = 燃料燃烧时间 ÷ 5(但燃烧时间超过 20 的只按 20 算)。小数部分按概率进位(如 2.3 步有 30% 概率变为 3 步)。

常见燃料效率对比:

燃料燃料热值燃料燃烧时间每个增加步数说明
煤炭块26004最持久的燃料(被限制为 20/5=4)
煤炭2804同样被限制为 4 步
硫磺2102
原木1204性价比高
木板1102
木棍151
树叶110.2几乎无用
草10.50.1可忽略
树苗10.50.1可忽略
仙人掌151
工作台/箱子151紧急时可拆家具当柴烧

提示:1 个原木 = 4 步营火增量,意味着 4 个原木就能把营火从 0 加满到 15。而煤炭块虽然燃烧时间长达 600,但受上限 20 的限制,每个也只能增加 4 步,与原木效率相同。因此原木是性价比最高的营火燃料。

营火衰减与熄灭 ​

营火会自然衰减,衰减有两种触发方式:

雨水浇灭:每 5 游戏秒检查一次,如果营火所在位置正在降雨(降水强度 > 0)且营火高度 >= 降水高度上限 - 1,营火尺寸减 1。这意味着露天的营火在雨天会逐渐熄灭。

自然衰减:每 6 次检查周期(即每 30 游戏秒,约 24 现实秒),无论天气如何,营火尺寸都会减 1。这是营火的自然消耗。

营火防雨

露天的营火会因为降雨而熄灭,可以在营火上面搭建遮蔽物来防止被雨水浇灭。

营火的正确定位 ​

营火适合早期取暖、烘干和照明。燃料可以通过投入可燃物补充,雨雪会让维持火势更困难。布置时至少做到:

  • 周围留出不可燃操作区;
  • 不挡住门口和唯一撤退通道;
  • 不把爆炸物当燃料投入;
  • 睡前评估剩余火势和室内温度,不假设它会整夜保持;
  • 体温过高时立刻拉开距离。

即使旁边有热源,露天、潮湿或不符合睡眠条件的位置仍可能无法安全休息。

爆炸系统 ​

爆炸是游戏中最具破坏力的瞬间事件。火药桶、雷管和燃烧桶都能引发爆炸,摧毁方块、伤害生物、甚至引发连环火灾。理解爆炸的压力传播和连锁机制,你才能安全使用爆炸物,也能在爆炸中幸存。

爆炸物种类 ​

游戏中的爆炸物分为两大类:火药桶和雷管。

火药桶数据:

名称爆炸压力是否引燃引信时间说明
小火药桶60否6~7 秒基础爆炸物
中火药桶360否6~7 秒中等威力
大火药桶2160否6~7 秒大规模破坏
小燃烧桶50是6~7 秒爆炸后引发火灾
中燃烧桶300是6~7 秒
大燃烧桶1800是6~7 秒

引信时间:从点燃到爆炸的现实秒数,在 6~7 秒之间随机。这段时间内会播放引信燃烧的嘶嘶声和火花粒子效果。

雷管:

名称爆炸压力是否引燃引信时间
雷管10否0.8~1.2 秒

雷管引信极短,几乎瞬间爆炸,通常用于电气电路触发的精确爆破。

投掷型爆炸武器 ​

与火药桶需要放置后引燃不同,炸弹和燃烧弹是投掷后自动定时爆炸的武器,适合战斗中快速使用。

火药桶是"放置型"爆炸物——你得先放好再点火。而炸弹和燃烧弹属于"投掷型",扔出去后5 秒自动爆炸,无需额外点火。此外,火药本身也可以投掷,但性质有所不同。

投掷型爆炸物数据:

名称分类爆炸压力是否引燃投掷速度最大堆叠特殊说明
炸弹武器60否1424投出后 5 秒自动爆炸,白色烟雾尾迹
燃烧弹武器50是1424投出后 5 秒自动爆炸,粉红色烟雾尾迹,爆炸后引发火灾
火药物品60否1440可投掷但不会自动爆炸,主要用作燃料或合成材料

炸弹 vs 火药桶对比:

特性炸弹 / 燃烧弹火药桶 / 燃烧桶
使用方式投掷后自动定时爆炸放置后需火焰/电路/爆炸引燃
引爆延迟固定 5 秒(投出后计时)6~7 秒引信(点燃后计时)
爆炸压力60 / 5060~2160 / 50~1800(分大中小三档)
撞击爆炸不会(落地后等计时器)会(火药桶带撞击爆炸行为)
可放置否(投掷物)是(可作为方块放置)
烟雾尾迹有(白色/粉红色)无
适用场景战斗中快速投掷定点爆破、陷阱、电路控制

炸弹的定时机制:炸弹投出后开始计时,5 秒后在当前位置爆炸。期间炸弹会拖曳烟雾尾迹(普通炸弹白色,燃烧弹粉红色),方便你判断爆炸时机。炸弹落地后不会像火药桶那样撞击爆炸,而是静静等待计时器归零。

燃烧弹的特殊之处:燃烧弹爆炸压力比炸弹略低(50 vs 60),但爆炸后会以 50% 概率引燃周围可燃方块,火焰扩散能力为满值(1.0),极易引发大面积火灾。适合用来烧毁木制建筑或森林。

火药的定位:火药虽然也有爆炸压力 60,但它只具有投掷行为,没有炸弹的定时爆炸行为,因此投掷后不会爆炸。火药的主要用途是作为燃料(热值 2,燃烧时间 10)和合成材料(制作火药桶、炸弹等)。

引爆方式 ​

爆炸物有以下几种引爆途径:

  1. 火焰引燃:当爆炸物相邻方块着火时,自动点燃引信
  2. 爆炸波及:当爆炸压力波及到爆炸物时,有 50% 概率点燃引信(前提是压力/爆炸抗性 > 0.01)
  3. 电气触发:火药桶可作为电气元件,通过电路信号远程引爆
  4. 投射物撞击:带有撞击爆炸行为的方块(如箭矢、子弹)命中时可直接触发

火药桶有不同规格,威力和影响范围随规格增大。它们可被火焰、弹药、其他爆炸或电气装置触发;电气起爆适合把玩家与爆心分开,但接线错误、区块状态和联机延迟仍可能造成误触发。

模型图用于区分尺寸与燃烧变体,不表示安全距离。火药桶越大或越集中,越需要扩大撤离范围。

不要把旧维基给出的固定“安全距离”当成保证。封闭空间、多个爆炸物、飞散燃烧方块、地形高度和版本参数都会改变风险。实际操作应使用更远距离、实体掩体和不与爆心相连的撤退路线。

为什么不推荐用爆炸物精细采矿

爆炸擅长清除体积,不擅长保证矿石回收。它可能摧毁矿物、破坏安全通道、打开岩浆或水体,并把燃烧碎片抛到视野外。需要取得资源时优先使用正确等级的镐;只有在明确接受材料损失、并完成测试后,才把爆炸用于大面积清障。

爆炸传播机制 ​

爆炸模拟从爆炸中心向外逐层扩散,每扩散一层压力就快速衰减。核心规律:

  • 传播停止条件:当压力衰减到约 1 时停止传播。爆炸压力越大,传播得越远——小火药桶(压力 60)的阈值约 1,大火药桶(压力 2160)的阈值约 6。
  • 压力衰减:随着波及的方块越来越多,压力快速衰减,第 7 轮之后还会额外加速衰减。这意味着爆炸破坏力随距离急剧下降。
  • 方块破坏判定:当某方块周围 6 个面的总压力超过其爆炸抗性时,方块被摧毁。同一位置的爆炸效果相对一致但有随机性——每个方块的抗性有 1~2 倍的随机波动,少数方块(约 12.5%)抗性甚至高达 3 倍。
  • 方块破坏判定:当某方块周围 6 个面的总压力超过其爆炸抗性时,方块被摧毁。同一位置的爆炸效果相对一致但有随机性——每个方块的抗性有 1~2 倍的随机波动,少数方块(约 12.5%)抗性甚至高达 3 倍。
  • 连锁爆炸概率:当方块被爆炸摧毁后,如果该方块本身是爆炸物,有概率被引爆——压力极大时 95%,一般情况 75%。这就是为什么火药桶要分散摆放,紧挨着放容易全部连锁引爆。

爆炸威力的方块破坏范围 ​

不同爆炸物的实际破坏范围取决于周围方块的爆炸抗性。以下是在纯泥土环境(爆炸抗性 25)中的近似破坏半径:

爆炸物爆炸压力近似破坏半径(格)
雷管10~1
小燃烧桶50~2
小火药桶60~2
中燃烧桶300~3
中火药桶360~4
大燃烧桶1800~6
大火药桶2160~7

注意:实际破坏范围受方块硬度影响极大。在石头环境(抗性 50)中,破坏半径约为泥土环境的一半;而在玻璃环境(抗性 2)中,破坏范围会大幅扩大。

常见方块爆炸抗性:

方块爆炸抗性抗爆等级
玻璃2极脆
雪块2极脆
树叶2极脆
营火1极脆
火把5脆弱
木栅栏3脆弱
泥土/沙子/沙砾25普通
冰25普通
石灰岩/砂岩50坚固
水/岩浆50坚固
花岗岩75坚固
砖块75坚固
玄武岩80坚固
鹅卵石140非常坚固
石砖160非常坚固
铁块2000极度坚固
钻石块8000近乎无敌
基岩∞无敌

爆炸伤害 ​

爆炸对生物和玩家的伤害与周围压力成正比:压力越大伤害越高,且生物质量越大受到的伤害越小(穿盔甲的玩家更耐炸)。爆炸还会产生击退效果,把生物推开。

爆炸与火焰的联动 ​

爆炸与火焰系统存在密切联动:

爆炸引发火灾:在爆炸的后处理阶段,对于未被摧毁但受到压力波及的可燃方块,有概率被点燃:

爆炸类型点燃概率点燃后火焰扩散能力
燃烧桶爆炸50%1.0(满扩散)
普通爆炸20%0.3(弱扩散)

点燃条件:只要方块可燃且承受了足够的爆炸压力,就可能被点燃。即使是普通爆炸,也有可能在废墟中引发火灾。

火焰引爆爆炸物:当爆炸物相邻方块着火时,自动点燃引信,6~7 秒后爆炸。这是最常见的连锁爆炸触发方式——先放火,再爆炸。

爆炸弹射物 ​

爆炸摧毁方块时,会生成弹射物(飞溅的方块碎片):

  • 弹射物数量随爆炸规模增加,但概率递减(超过 25 个后概率开始下降)
  • 弹射物带有随机速度和旋转,落地后可能变为可拾取物品或直接消失
  • 燃烧桶爆炸生成的弹射物有概率变为燃烧弹射物,落地后点燃周围方块
  • 50% 的弹射物会拖曳烟雾尾迹,燃烧弹射物的烟雾呈粉红色

爆炸音效分级 ​

爆炸音效根据总压力值分为 7 个等级,压力越大音效传播越远:

总压力音效噪音范围(格)
> 2,000,000Explosion7130
> 400,000Explosion6100
> 80,000Explosion570
> 16,000Explosion450
> 3,500Explosion340
> 80Explosion235
> 0Explosion130

噪音:爆炸会产生噪音,吸引附近的生物。压力越大的爆炸吸引范围越广,最大可达 130 格——这意味着一个大爆炸可能惊动整个区域的生物。

电路起爆检查表 ​

  1. 起爆电源默认保持断开,最后一步才连接。
  2. 导线与按钮远离爆炸影响区,并保留物理掩体。
  3. 先用灯替代电雷管测试信号方向、延迟和复位。
  4. 确认没有相邻线路、传感器或雷雨造成意外输入。
  5. 联机服由一人统一倒计时,确认附近玩家和坐骑已经撤离。

写给开发者 ​

资料暂存

本小节内容暂时存放于此,后续会移动至"MOD开发"章节。

开发概览

火焰方块行为(SubsystemFireBlockBehavior.cs) ​

FireData 类:存储每个火焰点的运行时数据。Point 为火焰坐标;Time0~Time3 为四个水平方向的燃烧计时器;Time5 为向下烧穿计时器;FireExpandability 为火焰扩散能力。

csharp
// 火焰方块编号为 104
public override int[] HandledBlocks => [104];

// 火焰蔓延概率的初始化(在 Load 方法中)
// 遍历 -2~2 的 x、-1~2 的 y、-2~2 的 z 偏移
for (int i = -2; i <= 2; i++) {       // i = X 偏移
    for (int j = -1; j <= 2; j++) {   // j = Y 偏移
        for (int k = -2; k <= 2; k++) { // k = Z 偏移
            if (i != 0 || j != 0 || k != 0) {
                // num = 最大距离:向下(j<0)为 1.5,向上/水平为 2.5
                float num = j < 0 ? 1.5f : 2.5f;
                if (MathF.Sqrt(i*i + j*j + k*k) <= num) {
                    // num2 = 水平距离 sqrt(i² + k²)
                    float num2 = MathF.Sqrt(i*i + k*k);
                    // num3 = 垂直因子:向上为 0.5*j,向下为 -j
                    float num3 = j > 0 ? 0.5f * j : -j;
                    // 蔓延概率 = 0.02 / (水平距离 + 垂直因子)
                    m_expansionProbabilities[new Point3(i, j, k)] = 0.02f / (num2 + num3);
                }
            }
        }
    }
}
csharp
// 燃烧计时器初始化
// face = 方向索引(0=+Z, 1=+X, 2=-Z, 3=-X, 5=-Y)
// GetFireDuration = 方块的火焰持续时间
// m_random.Float(0.75f, 1.25f) = 随机系数,使传播时间有 ±25% 的波动
public void InitializeFireDataTime(FireData fireData, int face) {
    Point3 point = CellFace.FaceToPoint3(face);
    int cellValue = SubsystemTerrain.Terrain.GetCellValue(
        fireData.Point.X + point.X, fireData.Point.Y + point.Y, fireData.Point.Z + point.Z);
    Block block = BlocksManager.Blocks[Terrain.ExtractContents(cellValue)];
    switch (face) {
        case 0: fireData.Time0 = block.GetFireDuration(cellValue) * m_random.Float(0.75f, 1.25f); break;
        case 1: fireData.Time1 = block.GetFireDuration(cellValue) * m_random.Float(0.75f, 1.25f); break;
        case 2: fireData.Time2 = block.GetFireDuration(cellValue) * m_random.Float(0.75f, 1.25f); break;
        case 3: fireData.Time3 = block.GetFireDuration(cellValue) * m_random.Float(0.75f, 1.25f); break;
        case 5: fireData.Time5 = block.GetFireDuration(cellValue) * m_random.Float(0.75f, 1.25f); break;
    }
}
csharp
// Update 方法中的火焰传播逻辑(简化版)
// 每次扫描检查所有火焰点,减少计时器
// num4 = 火焰方块的数据值(位标志)
// m_lastScanDuration = 上次扫描到现在的时间间隔

// 方向 0(+Z)的燃烧逻辑
if ((num4 & 1) != 0) {
    value.Time0 -= m_lastScanDuration;  // 计时器递减
    if (value.Time0 <= 0f) {
        // 计时器归零,烧毁 +Z 方向的方块
        // 新火焰扩散能力 = 原扩散能力 × 0.85
        QueueBurnAway(x, y, z + 1, value.FireExpandability * 0.85f);
    }
    // 概率蔓延:对每个可能的偏移方向
    foreach (var expansionProbability in m_expansionProbabilities) {
        // 随机数 < 概率 × 时间 × 扩散能力 → 蔓延
        if (m_random.Float(0f, 1f) < expansionProbability.Value * m_lastScanDuration * value.FireExpandability) {
            m_toExpand[new Point3(
                x + expansionProbability.Key.X,
                y + expansionProbability.Key.Y,
                z + 1 + expansionProbability.Key.Z
            )] = value.FireExpandability * 0.85f;  // 新扩散能力衰减 15%
        }
    }
}
csharp
// 烧毁处理(每 5 游戏秒执行一次)
// m_toBurnAway = 待烧毁方块队列
// m_toExpand = 待蔓延方块队列
if (m_subsystemTime.PeriodicGameTimeEvent(5.0, 0.0)) {
    foreach (var item in m_toBurnAway) {
        // 摧毁方块(变为空气)
        SubsystemTerrain.ChangeCell(item.Key.X, item.Key.Y, item.Key.Z, Terrain.ReplaceContents(0));
        // 如果扩散能力 > 0.25,向周围 5 个面扩散火焰
        if (item.Value > 0.25f) {
            for (int i = 0; i < 5; i++) {
                Point3 point = CellFace.FaceToPoint3(i);
                SetCellOnFire(item.Key.X + point.X, item.Key.Y + point.Y, item.Key.Z + point.Z, item.Value);
            }
        }
    }
    // 处理蔓延队列
    foreach (var item2 in m_toExpand) {
        SetCellOnFire(item2.Key.X, item2.Key.Y, item2.Key.Z, item2.Value);
    }
    m_toBurnAway.Clear();
    m_toExpand.Clear();
}

营火方块行为(SubsystemCampfireBlockBehavior.cs) ​

csharp
// 营火尺寸范围:0(熄灭)~ 15(最大)
// 营火粒子大小随尺寸线性插值
public void AddCampfireParticleSystem(int value, int x, int y, int z) {
    int num = Terrain.ExtractData(value);  // num = 营火尺寸(0~15)
    if (num > 0) {
        // size = 粒子大小,从 0.2 到 0.5 线性插值
        float size = MathUtils.Lerp(0.2f, 0.5f, num / 15f);
        FireParticleSystem fireParticleSystem = new(new Vector3(x, y, z) + v, size, 256f);
        m_subsystemParticles.AddParticleSystem(fireParticleSystem);
        m_particleSystemsByCell[new Point3(x, y, z)] = fireParticleSystem;
    }
}
csharp
// 添加燃料的核心逻辑
public bool AddFuel(int x, int y, int z, int value, int count) {
    if (Terrain.ExtractData(SubsystemTerrain.Terrain.GetCellValue(x, y, z)) > 0) {
        Block block = BlocksManager.Blocks[Terrain.ExtractContents(value)];
        // 1. 先检查是否为爆炸物
        if (Project.FindSubsystem<SubsystemExplosions>(true).TryExplodeBlock(x, y, z, value)) {
            return true;
        }
        // 2. 雪/雪球/冰块 → 减小营火
        if (block is SnowBlock || block is SnowballBlock || block is IceBlock) {
            return ResizeCampfire(x, y, z, -1, true);
        }
        // 3. 有燃料热值 → 计算增加步数
        if (block.GetFuelHeatLevel(value) > 0f) {
            // num2 = 增加步数 = 数量 × min(燃料燃烧时间, 20) / 5
            float num2 = count * MathUtils.Min(block.GetFuelFireDuration(value), 20f) / 5f;
            int num3 = (int)num2;           // 整数部分
            float num4 = num2 - num3;        // 小数部分
            if (m_random.Float(0f, 1f) < num4) num3++;  // 概率进位
            if (num3 > 0) {
                return ResizeCampfire(x, y, z, num3, true);
            }
            return true;
        }
    }
    return false;
}
csharp
// 营火尺寸调整
public bool ResizeCampfire(int x, int y, int z, int steps, bool playSound) {
    int num = Terrain.ExtractData(cellValue);  // 当前尺寸
    if (num > 0) {
        // 新尺寸 = clamp(当前尺寸 + 步数, 0, 15)
        int num2 = Math.Clamp(num + steps, 0, 15);
        if (num2 != num) {
            SubsystemTerrain.ChangeCell(x, y, z, Terrain.ReplaceData(cellValue, num2));
            if (playSound) {
                if (steps >= 0) {
                    // 添加燃料:播放放置音效
                    m_subsystemAmbientSounds.SubsystemAudio.PlaySound("Audio/BlockPlaced", 1f, 0f, ...);
                } else {
                    // 衰减/熄灭:播放嘶嘶声
                    m_subsystemAmbientSounds.SubsystemAudio.PlayRandomSound("Audio/Sizzles", 1f, 0f, ...);
                }
            }
            return true;
        }
    }
    return false;
}
csharp
// 营火自然衰减与雨水浇灭
public virtual void Update(float dt) {
    // 每 5 游戏秒检查一次
    if (m_subsystemTime.PeriodicGameTimeEvent(5.0, 0.0)) {
        m_updateIndex++;
        foreach (Point3 key in m_particleSystemsByCell.Keys) {
            // 获取降水信息
            PrecipitationShaftInfo precipitationShaftInfo = m_subsystemWeather.GetPrecipitationShaftInfo(key.X, key.Z);
            // 条件1:正在下雨且营火暴露在雨中
            // 条件2:每 6 次检查(30 游戏秒)自然衰减一次
            if ((precipitationShaftInfo.Intensity > 0f && key.Y >= precipitationShaftInfo.YLimit - 1)
                || m_updateIndex % 6 == 0) {
                m_toReduce.Add(key);
            }
        }
        foreach (Point3 item in m_toReduce) {
            ResizeCampfire(item.X, item.Y, item.Z, -1, true);  // 尺寸减 1
        }
        m_toReduce.Clear();
    }
}

爆炸物方块行为(SubsystemExplosivesBlockBehavior.cs) ​

csharp
// 点燃引信
public bool IgniteFuse(int x, int y, int z) {
    int cellContents = m_subsystemTerrain.Terrain.GetCellContents(x, y, z);
    if (BlocksManager.Blocks[cellContents] is GunpowderKegBlock) {
        // 火药桶:引信时间 6~7 秒
        AddExplosive(new Point3(x, y, z), m_random.Float(6f, 7f));
        return true;
    }
    if (BlocksManager.Blocks[cellContents] is DetonatorBlock) {
        // 雷管:引信时间 0.8~1.2 秒
        AddExplosive(new Point3(x, y, z), m_random.Float(0.8f, 1.2f));
        return true;
    }
    return false;
}
csharp
// 引信倒计时与引爆
public virtual void Update(float dt) {
    foreach (ExplosiveData explosiveData in array) {
        // 引信时间递减
        explosiveData.TimeToExplosion -= dt;
        if (explosiveData.TimeToExplosion <= 0f) {
            // 引信归零,触发爆炸
            m_subsystemExplosions.TryExplodeBlock(
                explosiveData.Point.X, explosiveData.Point.Y, explosiveData.Point.Z, cellValue);
        }
    }
}
csharp
// 邻居方块变化时检查火焰
public override void OnNeighborBlockChanged(int x, int y, int z, int neighborX, int neighborY, int neighborZ) {
    // 如果相邻方块着火,点燃引信
    if (m_subsystemFireBlockBehavior.IsCellOnFire(x, y, z)) {
        IgniteFuse(x, y, z);
    }
}

// 被爆炸波及时有概率连锁引爆
public override void OnExplosion(int value, int x, int y, int z, float damage) {
    Block block = BlocksManager.Blocks[Terrain.ExtractContents(value)];
    if (block.GetExplosionPressure(value) > 0f
        && MathUtils.Saturate(damage / block.ExplosionResilience) > 0.01f
        && m_random.Float(0f, 1f) < 0.5f) {  // 50% 概率连锁引爆
        IgniteFuse(x, y, z);
    }
}

爆炸系统核心(SubsystemExplosions.cs) ​

csharp
// 尝试引爆方块
public bool TryExplodeBlock(int x, int y, int z, int value) {
    Block block = BlocksManager.Blocks[Terrain.ExtractContents(value)];
    // 获取方块的爆炸压力和是否引燃
    float explosionPressure = block.GetExplosionPressure(value);
    bool explosionIncendiary = block.GetExplosionIncendiary(value);
    if (explosionPressure > 0f) {
        AddExplosion(x, y, z, explosionPressure, explosionIncendiary, false);
        return true;
    }
    return false;
}
csharp
// 爆炸模拟核心算法
public void SimulateExplosion(int x, int y, int z, float pressure, bool isIncendiary) {
    // 最小传播阈值 = max(0.13 × sqrt(压力), 1)
    float num = MathUtils.Max(0.13f * MathF.Pow(pressure, 0.5f), 1f);
    
    // BFS 传播
    int explosionPower = 0;  // 累计已处理点数
    int num3 = 0;            // 迭代轮数
    while (list.Count > 0 || list2.Count > 0) {
        explosionPower += list.Count;
        num3++;
        // 第 7 轮后额外衰减
        float num4 = 5f * MathUtils.Max(num3 - 7, 0);
        // 本轮压力 = 初始压力 / (累计点数^0.66 + 额外衰减)
        float num5 = pressure / (MathF.Pow(explosionPower, 0.66f) + num4);
        if (num5 >= num) {  // 压力仍大于阈值,继续传播
            foreach (ProcessPoint item in list) {
                float num7 = num5 + m_pressureByPoint.Get(item.X, item.Y, item.Z);
                m_pressureByPoint.Set(item.X, item.Y, item.Z, num7);
                // 向 6 个方向继续传播...
            }
        }
    }
}
csharp
// 方块破坏判定
public void TryAddPoint(int x, int y, int z, int axis, float currentPressure,
    bool isIncendiary, List<ProcessPoint> toProcess, SparseSpatialArray<bool> processed) {
    
    int cellValue = m_subsystemTerrain.Terrain.GetCellValue(x, y, z);
    Block block = BlocksManager.Blocks[Terrain.ExtractContents(cellValue)];
    
    // 基于坐标哈希的随机系数(1.0~2.0)
    int explosionPower = (int)(MathUtils.Hash((uint)(x + 913 * y + 217546 * z)) % 100u);
    float num3 = MathUtils.Lerp(1f, 2f, explosionPower / 100f);
    if (explosionPower % 8 == 0) num3 *= 3f;  // 12.5% 概率 ×3
    
    // 周围 6 面的总压力
    float num4 = m_pressureByPoint.Get(x-1,y,z) + m_pressureByPoint.Get(x+1,y,z)
               + m_pressureByPoint.Get(x,y-1,z) + m_pressureByPoint.Get(x,y+1,z)
               + m_pressureByPoint.Get(x,y,z-1) + m_pressureByPoint.Get(x,y,z+1);
    
    // 破坏条件:总压力 / (爆炸抗性 × 随机系数) > 1
    float num5 = MathUtils.Max(block.GetExplosionResilience(cellValue) * num3, 1f);
    float num6 = num4 / num5;
    
    if (num6 > 1f) {
        // 方块被摧毁
        m_subsystemTerrain.DestroyCell(0, x, y, z, Terrain.MakeBlockValue(0), true, true);
        // 连锁爆炸:压力/抗性 > 5 时 95% 概率,否则 75%
        float probability = num6 > 5f ? 0.95f : 0.75f;
        if (m_random.Bool(probability)) {
            TryExplodeBlock(x, y, z, cellValue);
        }
    }
}
csharp
// 爆炸后处理:火焰引燃与伤害计算
public virtual void PostprocessExplosions(bool playExplosionSound) {
    // 对受压力波及但未被摧毁的方块
    foreach (var item2 in m_surroundingPressureByPoint.ToDictionary()) {
        int cellValue = m_subsystemTerrain.Terrain.GetCellValue(item2.Key.X, item2.Key.Y, item2.Key.Z);
        Block block = BlocksManager.Blocks[Terrain.ExtractContents(cellValue)];
        
        // 火焰引燃概率
        float probability = item2.Value.IsIncendiary ? 0.5f : 0.2f;
        // 条件:方块可燃 且 压力/抗性 > 0.2
        if (block.GetFireDuration(cellValue) > 0f
            && item2.Value.Pressure / block.GetExplosionResilience(cellValue) > 0.2f
            && m_random.Bool(probability)) {
            // 引燃方块:燃烧桶扩散能力 1.0,普通爆炸 0.3
            m_subsystemFireBlockBehavior.SetCellOnFire(
                item2.Key.X, item2.Key.Y, item2.Key.Z,
                item2.Value.IsIncendiary ? 1f : 0.3f);
        }
    }
    
    // 对所有生物计算冲击力和伤害
    foreach (ComponentBody body in m_subsystemBodies.Bodies) {
        CalculateImpulseAndDamage(body, null, out Vector3 impulse, out float damage);
        body.UnderExplosion(impulse, damage);
    }
}
csharp
// 爆炸伤害与冲击力计算
public virtual void CalculateImpulseAndDamage(Vector3 position, float mass,
    float? obstaclePressure, out Vector3 impulse, out float damage) {
    
    // 遍历周围 3×3×3 的压力点
    float num = 0f;      // 总压力
    Vector3 zero = Vector3.Zero;  // 压力方向
    
    for (int i = -1; i <= 1; i++)
        for (int j = -1; j <= 1; j++)
            for (int k = -1; k <= 1; k++) {
                float num5 = /* 获取该点压力 */;
                zero += num5 * Vector3.Normalize(/* 远离爆炸中心的方向 */);
                num += num5;
            }
    
    // 质量因子 = max(sqrt(质量), 1)
    float num6 = MathUtils.Max(MathF.Pow(mass, 0.5f), 1f);
    
    // 冲击力 = 5.56 × 方向 × sqrt(方向长度) / 质量因子
    impulse = 5.5555553f * Vector3.Normalize(zero) * MathF.Sqrt(zero.Length()) / num6;
    // 伤害 = 2.59 × sqrt(总压力) / 质量因子
    damage = 2.59259248f * MathF.Sqrt(num) / num6;
}

爆炸音效分级(SubsystemExplosions.cs) ​

csharp
// 根据总压力选择爆炸音效和噪音范围
if (pressure > 2000000f) {
    m_subsystemAudio.PlaySound("Audio/Explosion7", 1f, ...);  // 最大爆炸
    m_subsystemNoise.MakeNoise(position, 1f, 130f);  // 噪音范围 130 格
}
else if (pressure > 400000f) {
    m_subsystemAudio.PlaySound("Audio/Explosion6", 1f, ...);
    m_subsystemNoise.MakeNoise(position, 1f, 100f);
}
else if (pressure > 80000f) {
    m_subsystemAudio.PlaySound("Audio/Explosion5", 1f, ...);
    m_subsystemNoise.MakeNoise(position, 1f, 70f);
}
else if (pressure > 16000f) {
    m_subsystemAudio.PlaySound("Audio/Explosion4", 1f, ...);
    m_subsystemNoise.MakeNoise(position, 1f, 50f);
}
else if (pressure > 3500f) {
    m_subsystemAudio.PlaySound("Audio/Explosion3", 1f, ...);
    m_subsystemNoise.MakeNoise(position, 1f, 40f);
}
else if (pressure > 80f) {
    m_subsystemAudio.PlaySound("Audio/Explosion2", 1f, ...);
    m_subsystemNoise.MakeNoise(position, 1f, 35f);
}
else if (pressure > 0f) {
    m_subsystemAudio.PlaySound("Audio/Explosion1", 1f, ...);  // 最小爆炸
    m_subsystemNoise.MakeNoise(position, 1f, 30f);
}

Block.cs 虚方法定义 ​

以下虚方法定义了方块与火焰/爆炸系统交互的接口。子类可覆盖这些方法来自定义行为。

csharp
// 火焰持续时间:方块被火焰烧穿所需的时间(游戏秒)
// 值为 0 表示方块不可燃
public float FireDuration;
public virtual float GetFireDuration(int value) => FireDuration;

// 燃料热值:作为营火燃料时的热量等级
// 值为 0 表示不能作为燃料
public float FuelHeatLevel;
public virtual float GetFuelHeatLevel(int value) => FuelHeatLevel;

// 燃料燃烧时间:作为营火燃料时能维持的燃烧时间(游戏秒)
public float FuelFireDuration;
public virtual float GetFuelFireDuration(int value) => FuelFireDuration;

// 爆炸抗性:方块抵抗爆炸的能力
// 值为 Infinity 表示不可被爆炸摧毁
public float ExplosionResilience;
public virtual float GetExplosionResilience(int value) => ExplosionResilience;

// 默认爆炸压力:方块被引爆时产生的爆炸压力
// 值为 0 表示方块不可爆炸
public float DefaultExplosionPressure;
public virtual float GetExplosionPressure(int value) => DefaultExplosionPressure;

// 默认是否引燃:爆炸是否附带火焰引燃效果
public bool DefaultExplosionIncendiary;
public virtual bool GetExplosionIncendiary(int value) => DefaultExplosionIncendiary;

// 爆炸是否保留掉落物:爆炸摧毁该方块时是否保留掉落物
public bool ExplosionKeepsPickables;

参考资料 ​

参与共建发现错误或有建议?两种零门槛方式,无需本地环境

玩家共建文档 · 原版、插件版、联机版与插件联机版请注意区分