V技能特殊攻击 - 设计与实现文档
创建时间: 2026-01-25 状态: ✅ 实现完成,待验证 负责模块: SkillManager组件
📋 目录
功能概述
功能描述
玩家按下V键触发的特殊攻击技能,包含视觉特效、敌人聚集、镜头控制和攻击动画的完整流程。
核心特性
- 🎭 角色残影放大效果
- 🌀 漩涡聚集特效
- 👁️ 动态镜头跟踪
- 🎯 敌人强制聚集
- ⚔️ 集中攻击动画
🎯 目标设计方案
技能执行流程(更新于 2026-01-25)
按下V键
↓
[Phase 1] 启动阶段
├── 玩家位置生成残影放大效果(基于sprite中心锚点,放大→缩小→渐隐)
├── 播放心跳音效(可选)
└── 前方200px生成漩涡特效
↓
[Phase 2] 检测敌人
└── 检测前方扇形范围内的敌人
↓
[Phase 3] 聚集阶段
├── 镜头移动到漩涡位置(固定不动)
├── 逐个处理敌人:
│ ├── 敌人状态机切换到stun状态
│ ├── 停止stun状态的timer(防止自动恢复)
│ ├── 设置 can_move = false
│ └── 聚集敌人到漩涡位置
└── 所有敌人聚集完毕后,镜头切换回玩家
↓
[Phase 4] 冲刺阶段
└── 玩家残影冲刺到漩涡位置
↓
[Phase 5] 攻击阶段
└── 播放攻击动画
↓
[Phase 6] 恢复阶段
├── 解除所有敌人stun状态(触发on_timeout转换到chase/wander)
├── 隐藏漩涡
└── 恢复玩家控制
视觉效果规格
残影放大效果
- 位置: 玩家当前位置
- 动画:
- 快速放大到2倍(0.2秒,EASE_OUT + TRANS_BACK)
- 缩小回原始大小并渐隐(0.3秒,EASE_IN + TRANS_QUAD)
- 颜色: 淡蓝色
Color(0.8, 0.9, 1.0, 0.7)
漩涡特效
- 位置: 玩家面向方向200px处
- 半径: 60px(增大以提高可见性)
- 颜色: 蓝紫色
Color(0.4, 0.5, 1.0, 1.0) - 螺旋数量: 4条
- 线宽: 4.0
- 层级: z_index = 1(在角色上方)
- 持续时间: 从Phase 1到Phase 6攻击动画结束
镜头行为(更新于 2026-01-25)
- 聚集阶段: 镜头固定在漩涡位置(不跟随敌人移动)
- 聚集完毕后: 镜头切换回玩家位置
- 缩放: 不改变zoom值(保持原始缩放)
- 移动时间: 0.5秒
🔧 实现策略
架构设计
SkillManager.gd (主控制器)
├── special_attack_v() - 入口函数
├── Phase 1: _create_initial_effects()
│ ├── GhostExpandEffect - 残影放大
│ ├── VortexEffect - 漩涡生成
│ └── AudioManager - 心跳音效
├── Phase 2-5: _gather_enemies_sequence()
│ ├── For each enemy:
│ │ ├── _position_camera_for_gather()
│ │ ├── _stun_enemy()
│ │ ├── _gather_enemy()
│ │ └── await gather_tween.finished
├── Phase 6: _execute_attack()
│ ├── AfterImageEffect - 冲刺残影
│ ├── AnimationComponent.play("attack")
│ └── await animation.finished → hide vortex
└── Phase 7: _cleanup()
├── _release_enemy_stuns()
└── _restore_camera()
核心组件交互
graph TB
A[SkillManager] -->|创建| B[GhostExpandEffect]
A -->|创建| C[VortexEffect]
A -->|控制| D[CameraManager]
A -->|眩晕| E[Enemy]
A -->|聚集| F[GatherEffect]
A -->|播放| G[AnimationComponent]
E -->|状态检查| H[StateMachine States]
🛠️ 技术细节
1. 残影放大效果实现
文件: Util/Components/SkillManager.gd:77-90
# 1.2 创建残影放大效果
var sprite = body.get_node_or_null("AnimatedSprite2D")
if sprite:
var ghost = GhostExpandEffectScript.new()
ghost.scale_multiplier = ghost_expand_scale
ghost.duration = 0.5
ghost.ghost_color = Color(0.8, 0.9, 1.0, 0.7)
# 使用 call_deferred 避免 "Parent node is busy" 错误
body.get_parent().call_deferred("add_child", ghost)
# 延迟创建精灵,等待节点添加完成
await owner_node.get_tree().process_frame
ghost.create_from_sprite(sprite, body.global_position)
关键技术点:
- ❌ 问题: 直接
add_child(ghost)会触发"Parent node is busy setting up children"错误 - ✅ 解决: 使用
call_deferred("add_child", ghost)延迟添加节点 - ✅ 同步:
await process_frame确保节点已添加到树中才创建精灵
动画实现: Util/Effects/GhostExpandEffect.gd
func _play_expand_animation() -> void:
var tween = create_tween()
# 阶段1:快速放大
tween.set_ease(Tween.EASE_OUT)
tween.set_trans(Tween.TRANS_BACK)
tween.tween_property(_ghost_sprite, "scale", Vector2.ONE * scale_multiplier, duration * 0.4)
# 阶段2:缩小回原始大小并渐隐
tween.set_ease(Tween.EASE_IN)
tween.set_trans(Tween.TRANS_QUAD)
tween.tween_property(_ghost_sprite, "scale", Vector2.ONE, duration * 0.6)
tween.parallel().tween_property(_ghost_sprite, "modulate:a", 0.0, duration * 0.6)
tween.finished.connect(_on_animation_finished)
2. 敌人聚集位置修正
文件: Util/Components/SkillManager.gd:165
# ❌ 旧实现(错误)
var gather_tween = _gather_enemy(enemy, _gather_position, gather_time)
# ✅ 新实现(正确)
var gather_tween = _gather_enemy(enemy, _vortex_position, gather_time)
问题分析:
_gather_position: 计算为玩家前方100px_vortex_position: 计算为玩家前方200px- Bug: 敌人聚集到100px位置,但漩涡在200px位置,导致视觉不匹配
修复结果:
- 敌人现在正确聚集到漩涡中心位置(200px)
3. 敌人强制停止机制
3.1 眩晕函数实现
文件: Util/Components/SkillManager.gd:254-261
## 眩晕敌人(内部方法)
func _stun_enemy(enemy: Node) -> void:
if "stunned" in enemy:
enemy.stunned = true # 标志1: 眩晕状态
if "can_move" in enemy:
enemy.can_move = false # 标志2: 禁止移动
# 标志3: 强制速度归零
if enemy is CharacterBody2D:
(enemy as CharacterBody2D).velocity = Vector2.ZERO
print("眩晕敌人: ", enemy.name, " 保持眩晕")
三重保险机制:
stunned = true: 业务逻辑层标志can_move = false: 移动权限控制velocity = Vector2.ZERO: 物理层速度清零
3.2 敌人类属性添加
文件: Scenes/enemies/dinosaur/Scripts/enemy.gd
var stunned : bool = false
var can_move : bool = true # 用于技能聚集时强制停止移动
3.3 状态机防护层
所有移动相关状态添加can_move检查:
文件:
- Util/StateMachine/CommonStates/chase_state.gd
- Util/StateMachine/CommonStates/wander_state.gd
- Util/StateMachine/CommonStates/idle_state.gd
- Util/StateMachine/CommonStates/attack_state.gd
func physics_process_state(delta: float) -> void:
# 检查是否可以移动(技能聚集时强制停止)
if "can_move" in owner_node and not owner_node.can_move:
if owner_node is CharacterBody2D:
(owner_node as CharacterBody2D).velocity = Vector2.ZERO
return
# ... 原有状态逻辑 ...
防护原理:
- 在每个状态的
physics_process_state开头检查 - 如果
can_move = false,强制velocity归零并立即return - 阻止状态机的移动逻辑覆盖速度设置
4. 镜头控制策略
文件: Util/Components/SkillManager.gd:158-162
# 3. 镜头定位到敌人和漩涡中间位置(不缩放)
var vortex_pos = _vortex_position + body.global_position
var camera_target_pos = (enemy.global_position + vortex_pos) / 2.0
var tween = create_tween()
tween.tween_property(camera_manager.camera, "global_position",
camera_target_pos, camera_move_time)
# 注意:不调用 tween_property(..., "zoom", ...) 以避免缩放
设计要点:
- ✅ 计算敌人和漩涡的中点:
(enemy_pos + vortex_pos) / 2.0 - ✅ 只改变镜头position,不改变zoom
- ✅ 移动时间0.5秒,使用Tween平滑过渡
镜头恢复: Util/Components/SkillManager.gd:217-220
# 7.4 恢复镜头到玩家
if camera_manager:
var camera_tween = create_tween()
camera_tween.tween_property(camera_manager.camera, "global_position",
body.global_position, 0.5)
5. 漩涡生命周期管理
创建时机
文件: Util/Components/SkillManager.gd:98
# Phase 1: 1.3 创建漩涡效果
_vortex_instance = VortexEffectScript.new()
body.get_parent().add_child(_vortex_instance)
_vortex_instance.global_position = vortex_pos
销毁时机(修复后)
# ❌ 旧实现(Phase 4 - 聚集阶段后立即隐藏)
# 问题:攻击动画时看不到漩涡
if _vortex_instance:
_vortex_instance.visible = false
# ✅ 新实现(Phase 6 - 攻击动画结束后隐藏)
# 文件: SkillManager.gd:190
await animation_component.animation_finished
if _vortex_instance:
_vortex_instance.visible = false
时间轴对比:
| 阶段 | 旧实现 | 新实现 |
|---|---|---|
| Phase 1 | 创建漩涡 ✅ | 创建漩涡 ✅ |
| Phase 2-5 | 漩涡可见 ✅ | 漩涡可见 ✅ |
| Phase 4末 | ❌ 隐藏漩涡(过早) | 漩涡可见 ✅ |
| Phase 6 | ❌ 攻击时无漩涡 | ✅ 攻击时有漩涡 |
| Phase 6末 | - | ✅ 攻击动画后隐藏 |
6. 漩涡可见性优化
文件: Util/Effects/VortexEffect.gd
# 原始配置(不可见)
@export var radius: float = 30.0 # ❌ 太小
@export var vortex_color: Color = Color(0.3, 0.4, 0.8, 0.8) # ❌ 太暗
# z_index 未设置,可能被遮挡
# 优化后配置(清晰可见)
@export var radius: float = 60.0 # ✅ 增大一倍
@export var vortex_color: Color = Color(0.4, 0.5, 1.0, 1.0) # ✅ 更亮
@export var spiral_count: int = 4
@export var line_width: float = 4.0
func _ready() -> void:
z_index = 1 # ✅ 确保在角色上方显示
优化效果:
- 半径从30增加到60(+100%面积)
- 颜色更亮(RGB值提高,alpha=1.0)
- z_index=1确保不被角色遮挡
❌ 问题与解决方案
问题1: 残影位置不对
现象:
- 残影没有在玩家位置出现
- 或者残影只放大不缩小
原因分析:
- 最初实现可能使用了错误的位置计算
- 动画只有放大阶段,缺少缩小回原始大小的逻辑
解决方案:
- 使用
body.global_position作为残影位置 - 修改动画为两阶段:
- 阶段1: 放大到2倍(duration * 0.4)
- 阶段2: 缩小回1倍并渐隐(duration * 0.6)
相关代码: GhostExpandEffect.gd
问题2: 敌人聚集后还会移动
现象:
- 敌人被聚集到目标位置后仍然会走动
- 眩晕状态无效
原因分析:
- 只设置
stunned = true,但状态机中没有检查 - 状态机的
physics_process_state仍在更新速度 - 速度设置后被状态机逻辑覆盖
解决方案(多层防护):
层1: 添加can_move属性
# enemy.gd
var can_move : bool = true
层2: 眩晕时设置三个标志
# SkillManager.gd
func _stun_enemy(enemy: Node):
enemy.stunned = true
enemy.can_move = false
enemy.velocity = Vector2.ZERO
层3: 状态机开头检查
# chase_state.gd, wander_state.gd, etc.
func physics_process_state(delta: float):
if "can_move" in owner_node and not owner_node.can_move:
(owner_node as CharacterBody2D).velocity = Vector2.ZERO
return
# ... 原有逻辑 ...
验证方法:
- 在
_stun_enemy中添加print("眩晕敌人: ", enemy.name, " 保持眩晕") - 检查控制台是否输出眩晕日志
- 观察敌人是否完全静止
问题3: 漩涡没有看到
现象:
- 技能释放时漩涡特效不可见
- 或者漩涡被其他元素遮挡
原因分析:
- 半径太小(30px在游戏中很难察觉)
- 颜色太暗,与背景对比度不足
- z_index未设置,被角色或其他元素遮挡
解决方案:
# VortexEffect.gd
@export var radius: float = 60.0 # 增大半径
@export var vortex_color: Color = Color(0.4, 0.5, 1.0, 1.0) # 提高亮度
@export var line_width: float = 4.0 # 加粗线条
func _ready():
z_index = 1 # 确保在上层显示
相关文件: VortexEffect.gd
问题4: 敌人没有聚集到漩涡位置
现象:
- 敌人聚集到距离玩家100px的位置
- 但漩涡在200px位置,两者不重合
原因分析:
- 代码中有两个位置变量:
_gather_position: 100px(旧逻辑)_vortex_position: 200px(正确位置)
- 错误使用了
_gather_position作为聚集目标
解决方案:
# 修改前
var gather_tween = _gather_enemy(enemy, _gather_position, gather_time)
# 修改后
var gather_tween = _gather_enemy(enemy, _vortex_position, gather_time)
代码位置: SkillManager.gd:165
问题5: 漩涡持续时间不对
现象:
- 漩涡在攻击动画播放前就消失了
- 攻击时看不到目标漩涡
原因分析:
- 原实现在Phase 4(聚集结束)后立即隐藏漩涡
- 但Phase 6才是攻击阶段,导致攻击时无视觉目标
解决方案:
# Phase 4 末尾(删除隐藏代码)
# if _vortex_instance:
# _vortex_instance.visible = false # ❌ 移除
# Phase 6 攻击动画结束后(新增隐藏代码)
await animation_component.animation_finished
if _vortex_instance:
_vortex_instance.visible = false # ✅ 正确时机
代码位置: SkillManager.gd:190
问题6: 镜头缩放不符合需求
现象:
- 镜头在聚集时会缩放(zoom in/out)
- 需求是只移动位置,不改变缩放级别
原因分析:
- 初始实现可能包含了zoom属性的tween动画
解决方案:
# 只tween position,不tween zoom
var tween = create_tween()
tween.tween_property(camera_manager.camera, "global_position",
camera_target_pos, camera_move_time)
# 删除: tween.tween_property(..., "zoom", ...)
代码位置: SkillManager.gd:158-162
问题7: 按V时没有残影效果
现象:
- 按下V键后没有看到残影放大动画
- 控制台可能有"Parent node is busy"错误
原因分析:
- Godot引擎限制:在
_ready或其他繁忙时刻直接add_child会失败 - 残影节点还没添加到树中就调用了
create_from_sprite
解决方案:
# 使用call_deferred延迟添加
body.get_parent().call_deferred("add_child", ghost)
# 等待下一帧确保节点已添加
await owner_node.get_tree().process_frame
# 现在可以安全地创建精灵
ghost.create_from_sprite(sprite, body.global_position)
关键技术:
call_deferred: 延迟到当前帧处理完成后执行await process_frame: 等待节点已加入场景树
代码位置: SkillManager.gd:77-90
⚙️ 配置参数
SkillManager导出参数
# === 特效脚本引用 ===
@export var GhostExpandEffectScript: Script # 残影放大效果脚本
@export var VortexEffectScript: Script # 漩涡效果脚本
@export var AfterImageEffectScript: Script # 残影效果脚本
@export var GatherEffectScript: Script # 聚集效果脚本
# === 技能参数 ===
@export var vortex_distance: float = 200.0 # 漩涡距离
@export var gather_distance: float = 100.0 # (已弃用,使用vortex_position)
@export var ghost_expand_scale: float = 2.0 # 残影放大倍数
@export var camera_move_time: float = 0.5 # 镜头移动时间
@export var gather_time: float = 0.8 # 敌人聚集时间
VortexEffect参数
@export var radius: float = 60.0 # 漩涡半径
@export var vortex_color: Color = Color(0.4, 0.5, 1.0, 1.0) # 漩涡颜色
@export var spiral_count: int = 4 # 螺旋数量
@export var rotation_speed: float = 2.0 # 旋转速度
@export var line_width: float = 4.0 # 线宽
GhostExpandEffect参数
@export var scale_multiplier: float = 2.0 # 放大倍数
@export var duration: float = 0.5 # 动画时长
@export var ghost_color: Color = Color(0.8, 0.9, 1.0, 0.7) # 残影颜色
参数调优建议
| 参数 | 默认值 | 建议范围 | 效果说明 |
|---|---|---|---|
| vortex_distance | 200 | 150-300 | 太小会遮挡角色,太大会超出屏幕 |
| vortex_radius | 60 | 40-80 | 影响漩涡可见性 |
| ghost_expand_scale | 2.0 | 1.5-3.0 | 放大倍数过大可能超出屏幕 |
| camera_move_time | 0.5 | 0.3-0.8 | 太快会眩晕,太慢会拖沓 |
| gather_time | 0.8 | 0.5-1.2 | 每个敌人聚集时间 |
📁 相关文件
核心文件
| 文件路径 | 功能 | 关键修改 |
|---|---|---|
| Util/Components/SkillManager.gd | 技能管理主控制器 | ✅ 聚集位置修正<br>✅ 漩涡生命周期<br>✅ 残影call_deferred<br>✅ 镜头控制 |
| Util/Effects/GhostExpandEffect.gd | 残影放大效果 | ✅ 双阶段动画(放大→缩小) |
| Util/Effects/VortexEffect.gd | 漩涡特效 | ✅ 增大半径<br>✅ 提高亮度<br>✅ z_index=1 |
| Scenes/enemies/dinosaur/Scripts/enemy.gd | 敌人类 | ✅ 添加can_move属性 |
状态机文件(添加can_move检查)
| 文件路径 | 修改内容 |
|---|---|
| Util/StateMachine/CommonStates/chase_state.gd | ✅ can_move检查 |
| Util/StateMachine/CommonStates/wander_state.gd | ✅ can_move检查 |
| Util/StateMachine/CommonStates/idle_state.gd | ✅ can_move检查 |
| Util/StateMachine/CommonStates/attack_state.gd | ✅ can_move检查 |
依赖组件
- Util/Classes/GatherEffect.gd - 敌人聚集效果
- Util/Effects/AfterImageEffect.gd - 冲刺残影
- Util/Components/AnimationComponent.gd - 动画播放
- Util/Components/CameraManager.gd - 镜头管理
✅ 验证清单
功能验证
运行游戏并按下V键,验证以下项目:
Phase 1 - 启动阶段
- 玩家位置出现残影效果
- 残影快速放大到2倍
- 然后缩小回原始大小
- 最后渐隐消失
- 听到心跳音效
- 前方200px处出现蓝紫色漩涡
- 漩涡清晰可见(半径60px)
- 漩涡在角色上方(不被遮挡)
Phase 2-5 - 聚集阶段
- 镜头移动到敌人和漩涡的中点位置
- 镜头没有缩放(zoom保持不变)
- 移动平滑(0.5秒)
- 敌人被正确眩晕
- 敌人完全静止,无任何移动
- 控制台输出"眩晕敌人: [名称] 保持眩晕"
- 敌人聚集到漩涡位置
- 聚集目标是漩涡中心(200px)
- 不是100px的gather_position
- 聚集动画平滑(0.8秒)
- 漩涡持续可见
Phase 6 - 攻击阶段
- 玩家残影冲刺到漩涡位置
- 播放攻击动画
- 漩涡在攻击动画时仍然可见
- 攻击动画结束后漩涡消失
Phase 7 - 恢复阶段
- 所有敌人恢复移动能力
- 镜头平滑返回玩家位置
- 玩家可以重新控制移动
代码验证
- 所有状态机文件都有can_move检查
-
_stun_enemy正确设置三个标志 - 聚集使用
_vortex_position而非_gather_position - 漩涡在Phase 6末尾隐藏
- 残影使用
call_deferred添加节点 - 镜头控制没有zoom相关代码
性能验证
- 无控制台错误或警告
- 帧率稳定,无明显卡顿
- 特效结束后节点正确清理(无内存泄漏)
🔧 调试技巧
问题:残影不出现
- 检查
GhostExpandEffectScript是否正确赋值 - 查看控制台是否有"Parent node is busy"错误
- 确认
await process_frame存在
问题:敌人还在移动
- 在
_stun_enemy中添加print调试 - 检查enemy.gd是否有
can_move属性 - 验证所有状态机文件都有can_move检查
- 查看enemy.velocity是否被其他代码覆盖
问题:漩涡看不到
- 临时增大radius到100测试
- 修改vortex_color为纯白色
Color.WHITE - 检查z_index是否设置为1
- 确认漩涡节点的visible属性为true
问题:聚集位置不对
- 在
_gather_enemy调用前打印位置:print("聚集目标: ", _vortex_position + body.global_position) - 验证使用的是
_vortex_position而非_gather_position
📝 开发笔记
设计决策
-
为什么残影要缩小回原始大小?
- 视觉上更符合"蓄力→释放"的感觉
- 避免残影过大影响视野
- 渐隐效果更自然
-
为什么敌人需要三重停止机制?
stunned: 业务逻辑层,供其他系统判断can_move: 权限控制层,状态机优先检查velocity = 0: 物理层,直接清零速度- 三层确保万无一失
-
为什么镜头不缩放?
- 缩放会导致画面跳动,影响体验
- 玩家需要保持对整体战场的感知
- 只移动位置能清楚展示聚集过程
-
为什么漩涡要持续到攻击结束?
- 提供清晰的视觉目标
- 增强技能的"仪式感"
- 让玩家明确攻击发生的位置
已知限制
-
多个敌人时镜头频繁移动
- 当前实现:逐个处理敌人,每次移动镜头
- 可能优化:并行聚集所有敌人,镜头只移动一次到中心位置
- 暂不修改:当前实现更有节奏感
-
敌人数量过多时技能时间过长
- 每个敌人聚集时间固定0.8秒
- 10个敌人需要8秒
- 可能优化:限制最大敌人数量或缩短单次聚集时间
未来优化方向
-
性能优化
- 对象池管理特效节点(避免频繁创建销毁)
- 限制同时处理的敌人数量
- 使用Shader实现部分特效
-
视觉优化
- 添加更多粒子效果
- 漩涡添加吸引粒子流
- 攻击时添加冲击波效果
-
体验优化
- 添加技能CD显示
- 添加技能蓄力进度条
- 添加技能范围预览
📚 相关文档
🕒 更新日志
| 日期 | 版本 | 修改内容 | 作者 |
|---|---|---|---|
| 2026-01-25 | 1.0 | 创建文档,记录完整设计和实现 | Claude |
| 2026-01-25 | 1.1 | 添加所有问题的解决方案和验证清单 | Claude |
文档状态: ✅ 完整 下一步: 用户验证功能是否符合预期