在动作游戏与 RPG 开发中,技能释放不仅仅是播放一个 Animation Clip。它涉及到复杂的时序同步:第 0.1秒 播放音效,第 0.3秒 生成特效,第 0.4秒 开启伤害判定框,第 0.8秒 允许输入打断。
如何优雅地维护这些时序逻辑?本文档对比了传统方案与工业化方案,并提供最佳实践。
Unity 原生支持在 Animation Clip 的特定帧插入 Event,调用挂在同一个 GameObject 上的函数。
Add Animation Event。OnHit 和参数 1.0。void OnHit(float damageMultiplier) { ... }。CrossFade 强行打断时,被跳过的帧上的 Event 不会触发。这会导致严重 Bug(例如:玩家动作被打断,但“无敌状态”没被清除,导致永久无敌)。.anim 文件里,策划无法在一个地方概览所有配置。结论: 仅适用于简单的原型或纯视觉反馈(如脚步声)。严禁用于核心战斗逻辑(伤害、状态切换)。
利用 Unity Animator 的 StateMachineBehaviour 脚本,绑定在 Animator Controller 的 State 上,而非 Clip 内。
Add Behaviour -> 新建脚本 SkillState.OnStateEnter, OnStateUpdate, OnStateExit 钩子。OnStateUpdate 每一帧都跑,如果要精确在“动作播放到 35%”时触发伤害,需要写繁琐的 normalizedTime 检查逻辑,且难以可视化编辑。这是目前 3A 及大型独立游戏(如 Hades, God of War)的主流做法。核心思想是将技能看作一个序列 (Sequence),而非单纯的动画。
技能不再是一个函数,而是一个 ScriptableObject(我们称之为 SkillSequence)。它包含多条轨道(Track):
Unity 内置的 Timeline 功能强大,但原生主要用于过场动画 (Cutscene)。
基于 EditorWindow 开发一个类似 Timeline 的 GUI。
public class SkillData : ScriptableObject {
public float duration;
public List<SkillEvent> events; // 按时间排序
}
[Serializable]
public class SkillEvent {
public float time;
public EventType type; // Damage, VFX, Sound
public string parameter;
}
Update 中计时:
void Update() {
timer += Time.deltaTime;
while (nextEventIndex < events.Count && timer >= events[nextEventIndex].time) {
Execute(events[nextEventIndex]);
nextEventIndex++;
}
}
鉴于项目类型(塔防+肉鸽),技能系统主要用于防御塔和怪物。
不需要做完整的 Timeline 编辑器,性价比最高的是 “Animation Curve + ScriptableObject”。
FireTime: 0.5s (发射投射物时间)BackswingTime: 0.8s (后摇结束,可进行下一次攻击时间)TotalDuration: 1.2s (动作总时长)黄金法则: Logic Drives Animation, Never the Other Way Around. (逻辑驱动表现,永远不要反过来。)