fix(missile): 修复闪电 (Lightning #49) 与连锁闪电 (Chain Lightning #53) 投射物偏短缺陷,恢复原版贯穿长光柱与跃动电弧视觉效果 #434

Closed
opened 2026-09-24 08:12:47 +00:00 by troytt · 1 comment
Owner

1. 缺陷概述 (Issue Summary)

在当前网页端实现中,女巫的 闪电 (Lightning, Skill ID: 49) 与 连锁闪电 (Chain Lightning, Skill ID: 53) 投射物在视觉呈现上存在明显的表现力不足:

  • 当前施放闪电或连锁闪电时,屏幕上仅出现一个**极短的单节电光飞弹(约 112px 长的孤立切片)**像飞镖一样在屏幕上穿过,显得非常短小单薄;
  • 原版暗黑破坏神 II (v1.13c) 中,闪电是一道自施法者手部贯穿全屏至 750px (25码) 的巨型连续电光巨柱 (Continuous Lightning Beam);连锁闪电在跳跃弹射时,目标之间亦连接着完整的跃动电弧光束。

2. Diablo II v1.13c Ground Truth 权威数据与渲染机制分析

严格遵循暗黑破坏神 II v1.13c 官方数据表(Skills.txt, Missiles.txt)及底层动画实现机制:

2.1 Missiles.txt 权威字段定义

  • 闪电主投射物 lightningbolt (ID: 98):
    • CelFile: null(主控投射物本身逻辑上无单帧外观,而是作为轨迹发生器)
    • Vel: 30 (速度 750 px/s), Range: 25 (最大飞行 25 帧 = 750px)
    • pCltDoFunc: 8(核心客户端运动/渲染函数)
    • CltParam1: 3(Subloops = 3,每次循环生成 3 组子片段)
    • CltSubMissile1: lightninghit (ID: 99)(尾迹子投射物)
  • 连锁闪电主投射物 chainlightning (ID: 93):
    • CelFile: LightningStrike
    • Vel: 30, Range: 25
    • pCltDoFunc: 8, CltParam1: 3, CltSubMissile1: lightninghit
  • 尾迹与打击投射物 lightninghit (ID: 99):
    • CelFile: LightningStrike(32 方向 DCC 动画)
    • Range: 12(每个子片段在原地留存 12 帧,即近 0.5 秒)
    • AnimLen: 8, AnimSpeed: 16, LoopAnim: 1, SubLoop: 1, SubStop: 7
    • Trans: 1(加法半透明高亮渲染)

2.2 原版长电光视觉形成原理

  1. 主弹头高速向前以每帧 30 像素推进;
  2. pCltDoFunc = 8 在弹头推进路径上密集生成 lightninghit (LightningStrike) 节段;
  3. 每个节段留存 12 帧,并持续循环播放电光激荡动画;
  4. 弹头飞出 25 帧的过程中,沿途留存的数十个电光节段首尾相接、密不可分,玩家眼中看到的便是一道从女巫身前一直延绵到屏幕边缘的完整、持续闪耀的刺目电光长柱!
  5. 连锁闪电在目标间弹跳折跃时,每段跳跃轨迹同样被密集的电光节段填充,形成怪物间的连续电弧。

3. 当前代码根因剖析 (Root Cause Analysis)

3.1 投射物数据模型仅保留单一质点

在 src/scene/act-scene.ts(第 2108–2131 行 及 2216–2230 行):

} else if (skillId === 49) {
  const missile = getMissileTxtData('lightningbolt')
  speed = missile.speedPxPerSec || 750
  range = missile.distancePx || 750
  ...
  missileType = 'lightningbolt'
}
...
engine.projectiles.push({
  skillId: String(skillId),
  x: pX,
  y: pY,
  vx: pVx,
  vy: pVy,
  damage,
  ttl,
  fromPlayer: true,
  missileType,
  pierce,
  ...
})
  • 引擎仅记录了一个质点坐标 (x, y),既没有轨迹历史缓冲区,也没有子投射物尾迹队列。

3.2 渲染管线仅绘制单一 Sprite 切片

在 src/scene/act-scene.ts 的 drawMissileProjectile(第 4337–4346 行):

const group = missileArt.frames[dccDir] ?? missileArt.frames[0]
const frame = group?.[frameIndex] ?? group?.[0]
if (frame !== undefined) {
  const drawX = frame.anchorX !== undefined ? shot.x + frame.anchorX : shot.x - frame.width / 2
  const drawY = (frame.anchorY !== undefined ? shot.y + frame.anchorY : shot.y - frame.height / 2) - altitude
  const isAdditive = missileArt.meta.name !== 'arrow'
  renderer.draw(frame, drawX, drawY, {
    atlas: missileArt.handle,
    ...(isAdditive ? { blendMode: 'additive' } : {}),
  })
}
  • LightningStrike 纹理的一帧有效像素仅约 112×79px。
  • 当前代码直接在 (shot.x, shot.y) 处绘制这单一的一张 112px 小图,随着它以 750px/s 的速度向前移动,后方没有任何残留渲染,导致玩家看到的仅仅是一个孤零零的 100px 小飞镖,而非一条长电光。

4. 修复方案设计 (Proposed Architecture & Implementation Plan)

4.1 方案 A:轨迹多节段采样与长电弧渲染(推荐,高效零垃圾回收)

  1. 轨迹历史链表/步进生成:
    • 对于 lightningbolt 与 chainlightning,根据当前弹头的行进距离(从起点 (startX, startY) 到当前位置 (shot.x, shot.y),或维护 trailSegments: Array<{x, y, age}>):
    • 按 60~80px 步进(贴合 LightningStrike 纹理的有效跨度)沿弹道反向铺设连续节段;
  2. 多节段动态着色与相位抖动:
    • 沿弹道的所有节段共享飞行方向的 dccDir(32 方向之一);
    • 每个节段赋予不同的动画相位偏移(Phase Offset),使长条闪电呈现如原版一般剧烈波动的真实高压电浆质感;
    • 节段根据存活时间(TTL)平滑淡出,形成头部激进破空、中后部连贯贯穿的超长电光柱。

4.2 连锁闪电 (Chain Lightning) 跨目标完整电弧连接

  • 当连锁闪电命中目标并跳跃到下一个目标时,在两怪物坐标之间生成一条完整的连续折线电弧节段(存活 8~12 帧),使玩家清晰目睹电光在怪物群之间跳跃撕裂的壮观网络。

4.3 验证方案 (Verification Plan)

  1. 视觉长宽比与连贯度检验:
    • 施放闪电技能,在屏幕上观测从女巫指尖延伸到最大射程(跨越 700+ 像素)的连贯闪电巨柱;
    • 验证闪电在命中墙体截断前保持完整连续,没有空隙与断层;
  2. 连锁闪电跳跃验证:
    • 吸引多只怪物,施放连锁闪电,验证弹跳时光柱在各目标之间形成清晰相连的跃动电弧;
  3. 性能基准验证:
    • 在高施法速度(FCR)连续施放闪电时,保持 60 FPS 稳定,GPU Atlas Draw Calls 维持批量批处理。
### 1. 缺陷概述 (Issue Summary) 在当前网页端实现中,女巫的 **闪电 (Lightning, Skill ID: 49)** 与 **连锁闪电 (Chain Lightning, Skill ID: 53)** 投射物在视觉呈现上存在明显的表现力不足: - 当前施放闪电或连锁闪电时,屏幕上仅出现一个**极短的单节电光飞弹(约 112px 长的孤立切片)**像飞镖一样在屏幕上穿过,显得非常短小单薄; - 原版暗黑破坏神 II (v1.13c) 中,闪电是一道**自施法者手部贯穿全屏至 750px (25码) 的巨型连续电光巨柱 (Continuous Lightning Beam)**;连锁闪电在跳跃弹射时,目标之间亦连接着完整的跃动电弧光束。 --- ### 2. Diablo II v1.13c Ground Truth 权威数据与渲染机制分析 严格遵循暗黑破坏神 II v1.13c 官方数据表(`Skills.txt`, `Missiles.txt`)及底层动画实现机制: #### 2.1 `Missiles.txt` 权威字段定义 - **闪电主投射物 `lightningbolt` (ID: 98)**: - `CelFile`: `null`(主控投射物本身逻辑上无单帧外观,而是作为轨迹发生器) - `Vel`: `30` (速度 750 px/s), `Range`: `25` (最大飞行 25 帧 = 750px) - **`pCltDoFunc`: `8`**(核心客户端运动/渲染函数) - **`CltParam1`: `3`**(`Subloops = 3`,每次循环生成 3 组子片段) - **`CltSubMissile1`: `lightninghit` (ID: 99)**(尾迹子投射物) - **连锁闪电主投射物 `chainlightning` (ID: 93)**: - `CelFile`: `LightningStrike` - `Vel`: `30`, `Range`: `25` - **`pCltDoFunc`: `8`**, **`CltParam1`: `3`**, **`CltSubMissile1`: `lightninghit`** - **尾迹与打击投射物 `lightninghit` (ID: 99)**: - **`CelFile`: `LightningStrike`**(32 方向 DCC 动画) - **`Range`: `12`**(每个子片段在原地留存 12 帧,即近 0.5 秒) - **`AnimLen`: `8`, `AnimSpeed`: `16`, `LoopAnim`: `1`, `SubLoop`: `1`, `SubStop`: `7`** - **`Trans`: `1`**(加法半透明高亮渲染) #### 2.2 原版长电光视觉形成原理 1. 主弹头高速向前以每帧 30 像素推进; 2. `pCltDoFunc = 8` 在弹头推进路径上密集生成 `lightninghit` (`LightningStrike`) 节段; 3. 每个节段留存 12 帧,并持续循环播放电光激荡动画; 4. 弹头飞出 25 帧的过程中,沿途留存的数十个电光节段首尾相接、密不可分,玩家眼中看到的便是一道**从女巫身前一直延绵到屏幕边缘的完整、持续闪耀的刺目电光长柱**! 5. 连锁闪电在目标间弹跳折跃时,每段跳跃轨迹同样被密集的电光节段填充,形成怪物间的连续电弧。 --- ### 3. 当前代码根因剖析 (Root Cause Analysis) #### 3.1 投射物数据模型仅保留单一质点 在 [`src/scene/act-scene.ts`](file:///usr/local/google/home/taodao/diablo2-web/src/scene/act-scene.ts)(第 2108–2131 行 及 2216–2230 行): ```typescript } else if (skillId === 49) { const missile = getMissileTxtData('lightningbolt') speed = missile.speedPxPerSec || 750 range = missile.distancePx || 750 ... missileType = 'lightningbolt' } ... engine.projectiles.push({ skillId: String(skillId), x: pX, y: pY, vx: pVx, vy: pVy, damage, ttl, fromPlayer: true, missileType, pierce, ... }) ``` - 引擎仅记录了一个质点坐标 `(x, y)`,既没有轨迹历史缓冲区,也没有子投射物尾迹队列。 #### 3.2 渲染管线仅绘制单一 Sprite 切片 在 [`src/scene/act-scene.ts`](file:///usr/local/google/home/taodao/diablo2-web/src/scene/act-scene.ts) 的 `drawMissileProjectile`(第 4337–4346 行): ```typescript const group = missileArt.frames[dccDir] ?? missileArt.frames[0] const frame = group?.[frameIndex] ?? group?.[0] if (frame !== undefined) { const drawX = frame.anchorX !== undefined ? shot.x + frame.anchorX : shot.x - frame.width / 2 const drawY = (frame.anchorY !== undefined ? shot.y + frame.anchorY : shot.y - frame.height / 2) - altitude const isAdditive = missileArt.meta.name !== 'arrow' renderer.draw(frame, drawX, drawY, { atlas: missileArt.handle, ...(isAdditive ? { blendMode: 'additive' } : {}), }) } ``` - `LightningStrike` 纹理的一帧有效像素仅约 112×79px。 - 当前代码直接在 `(shot.x, shot.y)` 处绘制这单一的一张 112px 小图,随着它以 750px/s 的速度向前移动,后方没有任何残留渲染,导致玩家看到的仅仅是一个孤零零的 100px 小飞镖,而非一条长电光。 --- ### 4. 修复方案设计 (Proposed Architecture & Implementation Plan) #### 4.1 方案 A:轨迹多节段采样与长电弧渲染(推荐,高效零垃圾回收) 1. **轨迹历史链表/步进生成**: - 对于 `lightningbolt` 与 `chainlightning`,根据当前弹头的行进距离(从起点 `(startX, startY)` 到当前位置 `(shot.x, shot.y)`,或维护 `trailSegments: Array<{x, y, age}>`): - 按 **60~80px** 步进(贴合 `LightningStrike` 纹理的有效跨度)沿弹道反向铺设连续节段; 2. **多节段动态着色与相位抖动**: - 沿弹道的所有节段共享飞行方向的 `dccDir`(32 方向之一); - 每个节段赋予不同的动画相位偏移(Phase Offset),使长条闪电呈现如原版一般剧烈波动的真实高压电浆质感; - 节段根据存活时间(TTL)平滑淡出,形成头部激进破空、中后部连贯贯穿的超长电光柱。 #### 4.2 连锁闪电 (Chain Lightning) 跨目标完整电弧连接 - 当连锁闪电命中目标并跳跃到下一个目标时,在两怪物坐标之间生成一条完整的连续折线电弧节段(存活 8~12 帧),使玩家清晰目睹电光在怪物群之间跳跃撕裂的壮观网络。 #### 4.3 验证方案 (Verification Plan) 1. **视觉长宽比与连贯度检验**: - 施放闪电技能,在屏幕上观测从女巫指尖延伸到最大射程(跨越 700+ 像素)的连贯闪电巨柱; - 验证闪电在命中墙体截断前保持完整连续,没有空隙与断层; 2. **连锁闪电跳跃验证**: - 吸引多只怪物,施放连锁闪电,验证弹跳时光柱在各目标之间形成清晰相连的跃动电弧; 3. **性能基准验证**: - 在高施法速度(FCR)连续施放闪电时,保持 60 FPS 稳定,GPU Atlas Draw Calls 维持批量批处理。
Author
Owner

Verification & Resolution Summary for Issue #434

1. Root Cause & 1.13c Ground Truth Parity Implementation

  • Client-Side Submissile Generation (pCltDoFunc = 8, CltParam1 = 3): Implemented LightningTrailManager in src/render/missile-renderer.ts. For each 30px projectile step of lightningbolt (Skill #49) and chainlightning (Skill #53), 3 sub-segment slices of lightninghit are linearly interpolated along the step vector (10px spacing).
  • Perpendicular Lateral Jitter (CltParam2 = 16): Each slice applies random lateral jitter bounded strictly within [-8, +8] px orthogonal to the flight trajectory.
  • Animation Looping & Lingering Duration (Range = 12, SubLoop = 1, SubStop = 7): Slices randomize their starting frame in 0..7, loop through frames 1..7, and persist for 12 ticks (480ms) with additive blending (Trans = 1).
  • Decoupled Persistence & Chaining Connected Arcs: Slices linger and decay independently even when the head projectile hits a target or obstacle. In Chain Lightning, slices from preceding hops remain active alongside newly spawned hops, forming continuous connected electric arc networks across multiple combatants.
  • Metadata Registrations: Added LIGHTNINGBOLT_META and LIGHTNING_META in src/render/missiles-meta.ts, registered in MISSILE_METAS, and added lightninghit (ID 99) to CANONICAL_113C_MISSILES and isHarmlessEmitter in src/game/skills.ts.

2. Verification Results

  • npm run typecheck (tsc --noEmit): 0 errors.
  • Automated unit test suite tests/skills/sor/issue-434-lightning-trail.test.ts: 14/14 tests passed.
  • Regression suites skill-049-lightning.test.ts, skill-053-chain-lightning.test.ts, issue-433-thunder-storm-visuals.test.ts: 55/55 passed (total 69/69 passed).

3. Commit & Delivery

  • Commit: a3f5013 fix(skill): restore Lightning and Chain Lightning continuous beam and arc visuals (Fixes #434)
  • Pushed to origin main.
## Verification & Resolution Summary for Issue #434 ### 1. Root Cause & 1.13c Ground Truth Parity Implementation - **Client-Side Submissile Generation (`pCltDoFunc = 8`, `CltParam1 = 3`)**: Implemented `LightningTrailManager` in `src/render/missile-renderer.ts`. For each 30px projectile step of `lightningbolt` (Skill #49) and `chainlightning` (Skill #53), 3 sub-segment slices of `lightninghit` are linearly interpolated along the step vector (10px spacing). - **Perpendicular Lateral Jitter (`CltParam2 = 16`)**: Each slice applies random lateral jitter bounded strictly within `[-8, +8]` px orthogonal to the flight trajectory. - **Animation Looping & Lingering Duration (`Range = 12`, `SubLoop = 1`, `SubStop = 7`)**: Slices randomize their starting frame in `0..7`, loop through frames `1..7`, and persist for 12 ticks (480ms) with additive blending (`Trans = 1`). - **Decoupled Persistence & Chaining Connected Arcs**: Slices linger and decay independently even when the head projectile hits a target or obstacle. In Chain Lightning, slices from preceding hops remain active alongside newly spawned hops, forming continuous connected electric arc networks across multiple combatants. - **Metadata Registrations**: Added `LIGHTNINGBOLT_META` and `LIGHTNING_META` in `src/render/missiles-meta.ts`, registered in `MISSILE_METAS`, and added `lightninghit` (ID 99) to `CANONICAL_113C_MISSILES` and `isHarmlessEmitter` in `src/game/skills.ts`. ### 2. Verification Results - `npm run typecheck` (`tsc --noEmit`): 0 errors. - Automated unit test suite `tests/skills/sor/issue-434-lightning-trail.test.ts`: 14/14 tests passed. - Regression suites `skill-049-lightning.test.ts`, `skill-053-chain-lightning.test.ts`, `issue-433-thunder-storm-visuals.test.ts`: 55/55 passed (total 69/69 passed). ### 3. Commit & Delivery - Commit: `a3f5013` `fix(skill): restore Lightning and Chain Lightning continuous beam and arc visuals (Fixes #434)` - Pushed to `origin main`.
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#434
No description provided.