[Skill Parity] 火墙 (Fire Wall) 施法后地面火焰视觉效果完全缺失 (isBlocked 坐标索引错误导致生成 0 个火墙粒子 & 动画 SubLoop 缺失) #439

Closed
opened 2026-09-24 08:38:35 +00:00 by troytt · 0 comments
Owner

问题描述 (Bug Description)

法师在施展技能 火墙 (Fire Wall, 技能 ID 51) 时,角色正常播放施法动作与手部施法特效 (fire_cast_2),法力值正常扣除,冷却时间(35 帧 / 1.4 秒)也正常进入倒计时,但在目标地面区域完全没有任何火墙的视觉效果和火焰实体生成。


缺陷代码定位与根因分析 (Root Cause Analysis)

1. 致命缺陷:碰撞网格索引计算错误导致火墙全部被判定为“在障碍物内”,生成 0 个火墙 Patch

在 src/scene/act-scene.ts (第 1612-1623 行):

const patches = calculateFireWallPatches(player.x, player.y, targetX, targetY, {
  count: 15,
  spacing: 24,
  isBlocked: (x, y) => {
    if (runtime?.grid) {
      const c = cellOf(runtime.grid, x, y)
      if (c.x < 0 || c.x >= runtime.grid.cellsX || c.y < 0 || c.y >= runtime.grid.cellsY) return true
      return runtime.grid.blocked[c.y * runtime.grid.gridWidth + c.x] !== 0
    }
    return false
  },
})
  • cellOf(runtime.grid, x, y) 计算出的是主瓦片坐标 (Tile/Cell Coordinates, 0..cellsX / 0..cellsY);
  • 但是 runtime.grid.gridWidth 是子瓦片网格宽度 (Sub-tile Width, 等于 cellsX * 5),runtime.grid.blocked 是一维的子瓦片数组(总大小为 (cellsX * 5) * (cellsY * 5));
  • 此处 c.y * runtime.grid.gridWidth + c.x 错误地将主瓦片坐标当成子瓦片坐标进行行偏移寻址,导致计算出的索引实际映射到了地图极左上角(前 \\frac{1}{25} 区域)的子瓦片!
  • 在 Diablo II 的实际地图(如罗格营地、鲜血荒地、石块旷野等)中,地图最外缘边界和角落绝大部分为 Void / 碰撞墙体(blocked !== 0);
  • 因此,isBlocked 对计算出的所有 15 个火墙 Patch 一律返回了 true(被阻挡);
  • 最终 calculateFireWallPatches 将所有点全部跳过(continue),返回空数组 patches = [];
  • engine.projectiles.push 实际上从未被执行,地面上没有产生任何一个 firewall 投射物实体,造成“火墙视觉效果完全没有”!

2. 动画缺失 SubLoop 机制 (起火 0..11 帧,循环 12..36 帧)

在 src/scene/act-scene.ts (第 4328-4343 行):

let frameIndex: number
if (missileArt.meta.name === 'meteorcenter') {
  ...
} else if (missileArt.meta.name === 'meteorfire' && frameCount === 37) {
  const duration = shot.fireDuration ?? (30 + (Math.max(1, shot.slvl ?? 1) - 1) * 15)
  const elapsed = shot.ageTicks ?? Math.max(0, duration - shot.ttl)
  const animStep = Math.floor((elapsed * missileArt.meta.animSpeed) / 16)
  frameIndex = animStep < 12 ? animStep : 12 + ((animStep - 12) % 25)
} else {
  const elapsed = shot.ageTicks ?? Math.max(0, 100 - shot.ttl)
  frameIndex = Math.floor((elapsed * missileArt.meta.animSpeed) / 16) % frameCount
}
  • 在原版 1.13c Missiles.txt (Row 69, firewall) 中:
    • CelFile: groundFireBig
    • AnimLen: 37,SubLoop: 1,SubStart: 12,SubStop: 36
  • firewall 与 meteorfire 拥有完全一致的动画生命周期:前 12 帧(0..11)为火焰从地底升腾窜出的升起动画;后续 25 帧(12..36)为火焰熊熊燃烧的持续循环。
  • 当前代码中对 firewall 走的是 else 分支的 % frameCount(直接对 37 取模),一旦生成,火���会在燃烧过程中每隔 37 帧重新“熄灭并重新从小火花升起一次”,破坏了连贯的烈焰之墙视觉效果。

3. 静态数据表定义与素材定义不一致

  • 在 src/game/skills.ts 第 1019-1039 行中,firewall 的静态定义为 celFile: 'FireWall', animLen: 15, id: 83;
  • 而原版 1.13c Missiles.txt 中,火墙为 Id: 69, CelFile: groundFireBig, AnimLen: 37,且在 src/render/missiles-meta.ts 中已正确定义了 FIREWALL_META(celFile: 'groundFireBig', framesPerDirection: 37);
  • getMissileTxtData('firewall') 与 FIREWALL_META 之间存在字段不一致,需予以对齐统一。

修复建议 (Proposed Fix)

  1. 修正碰撞判定函数:
    在 src/scene/act-scene.ts 中,将手写的错误索引替换为现成的标准化判定方法:
    const patches = calculateFireWallPatches(player.x, player.y, targetX, targetY, {
      count: 15,
      spacing: 24,
      isBlocked: (x, y) => {
        if (runtime?.grid) {
          return isMissileBlockedAt(runtime.grid, x, y)
        }
        return false
      },
    })
    
  2. 支持 groundFireBig / firewall 的 SubLoop 动画:
    在 drawMissileProjectile 中,将 firewall 与 meteorfire 一起纳入 37 帧(0..11 升起,12..36 循环)的标准火焰循环渲染管线中。
  3. 常量对齐:
    将 skills.ts 中 firewall 的 id(修正为 69)、celFile(修正为 groundFireBig)与 animLen(修正为 37)对齐。
### 问题描述 (Bug Description) 法师在施展技能 **火墙 (Fire Wall, 技能 ID 51)** 时,角色正常播放施法动作与手部施法特效 (`fire_cast_2`),法力值正常扣除,冷却时间(35 帧 / 1.4 秒)也正常进入倒计时,但在目标地面区域**完全没有任何火墙的视觉效果和火焰实体生成**。 --- ### 缺陷代码定位与根因分析 (Root Cause Analysis) #### 1. 致命缺陷:碰撞网格索引计算错误导致火墙全部被判定为“在障碍物内”,生成 0 个火墙 Patch 在 `src/scene/act-scene.ts` (第 1612-1623 行): ```typescript const patches = calculateFireWallPatches(player.x, player.y, targetX, targetY, { count: 15, spacing: 24, isBlocked: (x, y) => { if (runtime?.grid) { const c = cellOf(runtime.grid, x, y) if (c.x < 0 || c.x >= runtime.grid.cellsX || c.y < 0 || c.y >= runtime.grid.cellsY) return true return runtime.grid.blocked[c.y * runtime.grid.gridWidth + c.x] !== 0 } return false }, }) ``` - `cellOf(runtime.grid, x, y)` 计算出的是**主瓦片坐标 (Tile/Cell Coordinates, 0..cellsX / 0..cellsY)**; - 但是 `runtime.grid.gridWidth` 是**子瓦片网格宽度 (Sub-tile Width, 等于 cellsX * 5)**,`runtime.grid.blocked` 是一维的子瓦片数组(总大小为 `(cellsX * 5) * (cellsY * 5)`); - 此处 `c.y * runtime.grid.gridWidth + c.x` 错误地将主瓦片坐标当成子瓦片坐标进行行偏移寻址,导致计算出的索引实际映射到了地图极左上角(前 $\\frac{1}{25}$ 区域)的子瓦片! - 在 Diablo II 的实际地图(如罗格营地、鲜血荒地、石块旷野等)中,地图最外缘边界和角落绝大部分为 Void / 碰撞墙体(`blocked !== 0`); - 因此,`isBlocked` 对计算出的所有 15 个火墙 Patch 一律返回了 `true`(被阻挡); - 最终 `calculateFireWallPatches` 将所有点全部跳过(`continue`),返回空数组 `patches = []`; - `engine.projectiles.push` 实际上**从未被执行**,地面上没有产生任何一个 `firewall` 投射物实体,造成“火墙视觉效果完全没有”! --- #### 2. 动画缺失 SubLoop 机制 (起火 0..11 帧,循环 12..36 帧) 在 `src/scene/act-scene.ts` (第 4328-4343 行): ```typescript let frameIndex: number if (missileArt.meta.name === 'meteorcenter') { ... } else if (missileArt.meta.name === 'meteorfire' && frameCount === 37) { const duration = shot.fireDuration ?? (30 + (Math.max(1, shot.slvl ?? 1) - 1) * 15) const elapsed = shot.ageTicks ?? Math.max(0, duration - shot.ttl) const animStep = Math.floor((elapsed * missileArt.meta.animSpeed) / 16) frameIndex = animStep < 12 ? animStep : 12 + ((animStep - 12) % 25) } else { const elapsed = shot.ageTicks ?? Math.max(0, 100 - shot.ttl) frameIndex = Math.floor((elapsed * missileArt.meta.animSpeed) / 16) % frameCount } ``` - 在原版 1.13c `Missiles.txt` (Row 69, `firewall`) 中: - `CelFile: groundFireBig` - `AnimLen: 37`,`SubLoop: 1`,`SubStart: 12`,`SubStop: 36` - `firewall` 与 `meteorfire` 拥有完全一致的动画生命周期:前 12 帧(0..11)为火焰从地底升腾窜出的升起动画;后续 25 帧(12..36)为火焰熊熊燃烧的持续循环。 - 当前代码中对 `firewall` 走的是 `else` 分支的 `% frameCount`(直接对 37 取模),一旦生成,火���会在燃烧过程中每隔 37 帧重新“熄灭并重新从小火花升起一次”,破坏了连贯的烈焰之墙视觉效果。 --- #### 3. 静态数据表定义与素材定义不一致 - 在 `src/game/skills.ts` 第 1019-1039 行中,`firewall` 的静态定义为 `celFile: 'FireWall', animLen: 15, id: 83`; - 而原版 1.13c `Missiles.txt` 中,火墙为 `Id: 69, CelFile: groundFireBig, AnimLen: 37`,且在 `src/render/missiles-meta.ts` 中已正确定义了 `FIREWALL_META`(`celFile: 'groundFireBig', framesPerDirection: 37`); - `getMissileTxtData('firewall')` 与 `FIREWALL_META` 之间存在字段不一致,需予以对齐统一。 --- ### 修复建议 (Proposed Fix) 1. **修正碰撞判定函数**: 在 `src/scene/act-scene.ts` 中,将手写的错误索引替换为现成的标准化判定方法: ```typescript const patches = calculateFireWallPatches(player.x, player.y, targetX, targetY, { count: 15, spacing: 24, isBlocked: (x, y) => { if (runtime?.grid) { return isMissileBlockedAt(runtime.grid, x, y) } return false }, }) ``` 2. **支持 `groundFireBig` / `firewall` 的 SubLoop 动画**: 在 `drawMissileProjectile` 中,将 `firewall` 与 `meteorfire` 一起纳入 37 帧(0..11 升起,12..36 循环)的标准火焰循环渲染管线中。 3. **常量对齐**: 将 `skills.ts` 中 `firewall` 的 `id`(修正为 69)、`celFile`(修正为 `groundFireBig`)与 `animLen`(修正为 37)对齐。
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: troytt/diablo2-web#439
No description provided.