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。

反馈调优建议按这个顺序:

  1. 动作接触帧。
  2. 逻辑命中帧。
  3. 命中特效出现帧。
  4. 命中音效。
  5. 6 到几十毫秒级的停顿。
  6. 重击才启用的镜头震动。
  7. 飘字和血条变化。

所有反馈同帧堆满不一定更“重”,反而会遮挡读招。普通攻击、暴击、重击应分级。

11. Animator 资源验收

  • Base Layer 每个业务状态的名字与代码一致。
  • 根或状态上存在 AnimStateBehaviour;分组子状态机存在 AnimGroupBehaviour。
  • Trigger/Integer 参数类型与调用重载一致。
  • Any State 不会绕过必要的退出回调。
  • 起手段不是 Loop;Loop 段明确退出条件。
  • Default 和 Variation A-D Controller 的状态名、参数名一致。
  • 换皮后复测每个 Clip 的命中帧和取消窗口。
  • 对象池复用时 Trigger、Integer、speed、layerWeight 和 enabled 状态已复位。