Animator 状态机
Assets/
Scenes/
Arena.unity
Scripts/
Animations/
你已经有的这一课新增
Knight 现在能用 PlayerMovement 动了,但还保持着 T-pose 硬邦邦地滑来滑去。这一课给它装上腿。
你的代码从不直接播放 clip。它只设置参数;Animator 决定这对应哪个 clip,并在它们之间混合。
看着左边的角色把右边亮起的那个状态演出来 —— 而底部那三个参数,就是你的代码唯一碰得到的东西。
搭建 Controller
- 1
在 Project 里:,命名为 AC_Knight。
- 2
选中 Knight,添加 Animator 组件,把 AC_Knight 拖进 Controller,把模型的 Avatar 拖进 Avatar。
- 3
打开 窗口,把 Idle clip 拖进去 —— 它就变成了橙色的默认状态。
- 4
在 Parameters tab 里添加:Speed(Float)、IsGrounded(Bool)、Attack(Trigger)。
Blend Tree 好过把走/跑拆成单独状态
- 1
在 Animator 里右键 → Create State → From New Blend Tree。命名为 Locomotion,并设为默认。
- 2
双击它。把 Blend parameter 设为 Speed,然后加三个 motion:Idle 在 0、Walk 在 2、Run 在 6。
- 3
现在一个 float 就能让 idle → walk → run 平滑交叉淡化。移动部分完全不需要过渡箭头。
用代码驱动它
using UnityEngine;
[RequireComponent(typeof(Animator))]
public class PlayerAnimator : MonoBehaviour
{
private Animator anim;
private CharacterController cc;
// hashes are faster than strings
private static readonly int SpeedHash = Animator.StringToHash("Speed");
private static readonly int GroundedHash = Animator.StringToHash("IsGrounded");
private static readonly int AttackHash = Animator.StringToHash("Attack");
void Awake()
{
anim = GetComponent<Animator>();
cc = GetComponent<CharacterController>();
}
void Update()
{
// horizontal speed only — vertical fall must not look like running
Vector3 flat = new Vector3(cc.velocity.x, 0f, cc.velocity.z);
anim.SetFloat(SpeedHash, flat.magnitude, 0.1f, Time.deltaTime); // 0.1f = damping
anim.SetBool(GroundedHash, cc.isGrounded);
if (Input.GetMouseButtonDown(0))
anim.SetTrigger(AttackHash);
}
}SetFloat 的第三、第四个参数是 damp 时间和 delta time:值是缓变而不是跳变,所以 blend 不会突跳。
Trigger 是一个被消费后自动复位的 bool —— 用在 Attack 这种一次性动作上正合适。
StringToHash 在 static 字段里调一次就好:每帧都拿字符串去查是白费力气。
修好九成卡顿的过渡设置
凡是需要响应玩家的过渡(攻击、跳跃),取消勾选 Has Exit Time。必须播完的动画则保留勾选。
Transition Duration 0.1–0.2 秒手感最跟手。超过 0.3 秒,角色就像喝醉了一样。
Interruption Source → Current State 让新的攻击能切入上一个攻击,而不是排队等待。
Animation Events —— 在挥出的那一刻命中
伤害不该在按键那一刻结算,而该在剑真正挥出去那一刻。Animation Events 能在你选的 clip 帧上调用一个方法。
// on the same GameObject as the Animator
public void OnAttackHit() // pick this name in the clip's Events row
{
Debug.Log("hit frame");
// damage the enemies in range
}