Animator、命中停顿与战斗反馈 private
Animator、命中停顿与战斗反馈
1. AnimatorController 是业务适配层
IAnimatorController 暴露播放、结束、状态查询、速度、冻结、Layer 和运行时控制器切换。英雄状态机不直接依赖 Unity Animator 的具体参数布局:
void PlayAnimation(string stateName,
string parameterName = null);
void PlayAnimation(string stateName,
string parameterName, int value);
void PlayGroupAnimation(string subStateMachineName,
string parameterName, int value);
float GetCurrentAnimationTime(int layerIndex = 0);
void FinishPlayAnimation();
void FreezeAnimation(bool freeze);
这使 Dummy、Experimental 和正式 AnimatorController 能替换,也便于无表现服务端或测试环境运行。
2. Trigger 与 Integer 两种入口
普通状态使用 Trigger:
_requestPlayAnimStateHashName =
Animator.StringToHash(stateName);
_animator.SetTrigger(parameterName ?? stateName);
同一组技能可使用 Integer:
_animator.SetInteger(parameterName, value);
例如业务调用 PlayAnimation("Skill_A", "Skill", SkillType),Animator 中由 Skill 整数选择子动作。状态机只知道技能枚举,不需要知道 Animator 的过渡图细节。
再次请求当前状态时,控制器会执行 Animator.Play(hash, -1, 0) 从头播放,解决连续普攻同状态无法重新触发的问题。
3. 为什么不用“等待 Clip.length 秒”
初始化时控制器从 Animator Controller 取 AnimStateBehaviour 和 AnimGroupBehaviour,订阅真实 Enter/Exit:
var behavior = _animator.GetBehaviour<AnimStateBehaviour>();
behavior.OnStateEnterAction += OnAnimatorStateEnter;
behavior.OnStateExitAction += OnAnimatorStateExit;
foreach (var group in
_animator.GetBehaviours<AnimGroupBehaviour>()) {
group.OnStateMachineEnterAction +=
OnAnimatorStateMachineEnter;
group.OnStateMachineExitAction +=
OnAnimatorStateMachineExit;
}
回调按 state hash 维护 Enter/Exit 状态表。IsCurrentAnimationAutoFinished 因此能识别真实退出,包括 Animator Transition,而不是只依赖硬编码秒数。
4. normalizedTime 的意义
GetCurrentAnimationTime 在过渡中读取 NextState,否则读 CurrentState:
AnimatorStateInfo state =
_animator.IsInTransition(layer)
? _animator.GetNextAnimatorStateInfo(layer)
: _animator.GetCurrentAnimatorStateInfo(layer);
return state.normalizedTime;
这正是取消窗口使用的时间轴。循环动画的 normalizedTime 会超过 1,状态机应明确是比较“已完成一次”还是取小数部分;本项目很多技能以 >= 1.0f 作为起手结束条件,适用于非循环起手段。
5. FinishPlayAnimation 不是强制跳 Idle
FinishPlayAnimation 只复位当前参数:
- Integer 设为 0。
- Float 设为 0。
- Bool 设为 false。
- Trigger 执行 ResetTrigger。
真正切回哪个动画由 Animator 图和下一业务状态共同决定。因此遇到卡动作时要同时看:业务 State 是否退出、参数是否复位、Animator 是否有 Exit Transition、Transition 条件是否互相冲突。
6. 动画事件的正确职责
Animation Event 适合:
- 武器 Trail 开关。
- 脚步声和轻量音效。
- 挂点特效。
- 相机震动提示。
- 纯表现对象的启停。
不适合单独承担:
- 扣血最终结果。
- 服务端技能 CD。
- 实体生成的唯一权威入口。
- 断线重连必须恢复的状态。
本项目大量技能用 AttackLogicDelay 和状态机 Timer 产生逻辑时点,而不是完全依赖 Animation Event。这样远端、低帧和无 Animator 环境仍能维持战斗逻辑链。
7. 命中停顿
具体技能将停顿参数写入 AttackData:
PendingAttackData.FreezeAnimTime =
skillParam.AttackFreezeAnimTimeFactor *
hero.Attribute.AttackFreezeAnimTime;
PendingAttackData.FreezeAnimTimeLong =
skillParam.AttackFreezeAnimTimeFactor *
hero.Attribute.AttackFreezeAnimTimeLong;
ActorEntity 最终通过 AnimatorController.FreezeAnimation(true) 暂停 Animator,再用延迟恢复。短停顿让接触帧更有重量;但它会影响 normalizedTime 的推进,所以不能在冻结期间用真实时间强制切状态而完全忽略动画状态。
8. 命中结果的表现链
sequenceDiagram
participant Hit as AttackCollisionDirector
participant Local as 本地预测
participant Battle as Battle
participant Sync as SyncDamageSystem
participant Victim as Victim Actor/View
Hit->>Local: SimulateOnBeHit
Local->>Victim: 即时命中反馈
Hit->>Battle: FrameSkillHit
Battle->>Battle: 校验并计算 DamageResult
Battle-->>Sync: SC_DamageResult
Sync->>Sync: UniqueId/StateSimulator 去重
Sync->>Victim: ApplyOnBeHit
Sync->>Victim: ApplyHpChangedToView
客户端命中时可先调用 SimulateOnBeHit 和 OnHitSuccess;Battle 回包后,SyncDamageSystem 用 StateSimulator.IsJustSimulateHurt(uniqueId) 判断是否是同一次预测,避免受击动画和停顿播放两次。
9. 数值、受击和反馈分离
DamageResult 携带 Hurt、Dead、BeatBack、Critical、Heavy、Dodge、Immunity、FreezeAnimTime 等标记。AttributeChangeMsg 再通过 IViewObject.ApplyHpChangedToView 更新血条、飘字等表现。
因此一击“有数字但没动作”和“有动作但不扣血”是两条不同故障:
- 前者查 DamageResult 标记、受击状态优先级、Animator。
- 后者查 FrameSkillHit、Battle 校验、AttributeChangeMsg 和实体索引。
10. VFX、音效和镜头
具体技能通常在 Begin 立即播放角色语音,再用不同延迟播放 Cast/Finish 特效。EffectPlayer 通过挂点或世界位置实例化;AudioPlayer 使用 HeroView 的 AudioSource。镜头由 InGameCameraController.ShakeCamera 选择 ShakeConfig,不应由伤害数值代码直接操纵 Camera Transform。
反馈调优建议按这个顺序:
- 动作接触帧。
- 逻辑命中帧。
- 命中特效出现帧。
- 命中音效。
- 6 到几十毫秒级的停顿。
- 重击才启用的镜头震动。
- 飘字和血条变化。
所有反馈同帧堆满不一定更“重”,反而会遮挡读招。普通攻击、暴击、重击应分级。
11. Animator 资源验收
- Base Layer 每个业务状态的名字与代码一致。
- 根或状态上存在 AnimStateBehaviour;分组子状态机存在 AnimGroupBehaviour。
- Trigger/Integer 参数类型与调用重载一致。
- Any State 不会绕过必要的退出回调。
- 起手段不是 Loop;Loop 段明确退出条件。
- Default 和 Variation A-D Controller 的状态名、参数名一致。
- 换皮后复测每个 Clip 的命中帧和取消窗口。
- 对象池复用时 Trigger、Integer、speed、layerWeight 和 enabled 状态已复位。