mcpField
U
unity-animation

unity-animation

github.com/nice-wolf-studio创意与媒体4077 次安装6 周前更新
代码仓库

关于 unity-animation

面向 Unity 开发者的动画技能,涵盖 Mecanim、Animator Controller、Blend Tree、Timeline 和动画脚本。内容通过设置说明与代码示例介绍系统组件和工作流程。

主要功能

  1. 01Mecanim 组件与动画类型
  2. 02Animator 状态、转换与参数
  3. 03Blend Tree 配置
  4. 04Avatar 设置与重定向
  5. 05Root Motion 与动画事件
  6. 06Timeline 与动画脚本

适用场景

  1. 01构建角色移动与状态流程
  2. 02在 Humanoid 骨架间重定向动画
  3. 03在动画播放期间触发事件
  4. 04使用 Timeline 编排影片序列

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-animation

安装后在 Claude Code 中输入/unity-animation确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
animation-clips.md
animator-controller.md
timeline.md

详细介绍

Unity 动画系统

动画系统概述

Unity 的 Mecanim 动画系统由三个相互关联的组件构成:

  1. 动画片段 -- 独立的动作片段(Idle、Walk、Run)
  2. Animator Controller -- 状态机,将动画片段组织成包含状态和过渡的流程图
  3. Avatar 系统 -- 将人形角色骨架映射到通用的内部格式,以便重定向动画

Animator 组件附加到 GameObject 上,并引用播放所需的 Animator Controller 和 Avatar 资源。

动画类型
  • Humanoid -- 需要配置 Avatar;支持在不同角色骨架之间重定向动画;CPU 开销比 Generic 高 15–20%
  • Generic -- 对特定层级中的 Transform 或 MonoBehaviour 属性进行动画处理;无法在不同层级之间迁移
  • Legacy -- 较旧的 Animation 组件;适用于简单的单次播放或 UI 动画

Animator Controller

Animator Controller 资源会为角色或带动画的 GameObject 安排 Animation Clips 和 Transitions。

创建: 在 Project 窗口中右键 > Create > Animator Controller

关键组件
  • 状态 -- 每个状态播放关联的 Animation Clip 或 Blend Tree
  • 过渡 -- 定义状态机如何以及何时在状态之间切换
  • 参数 -- 脚本可设置以控制过渡的变量(Float、Int、Bool、Trigger)
  • 层 -- 用于不同身体部位或动画用途的独立状态机
  • 子状态机 -- 用于分层组织的嵌套状态机
参数

共有四种类型:

类型描述脚本方法
Float小数SetFloat() / GetFloat()
Int整数SetInteger() / GetInteger()
Bool真/假SetBool() / GetBool()
Trigger自动重置的布尔值SetTrigger() / ResetTrigger()
using UnityEngine;

public class PlayerAnimController : MonoBehaviour
{
    Animator animator;

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        // Note: Uses legacy Input Manager for simplicity. See unity-input for the new Input System.
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        bool fire = Input.GetButtonDown("Fire1");

        animator.SetFloat("Forward", v);
        animator.SetFloat("Strafe", h);
        animator.SetBool("Fire", fire);
    }

    void OnCollisionEnter(Collision col)
    {
        if (col.gameObject.CompareTag("Enemy"))
        {
            animator.SetTrigger("Die");
        }
    }
}
Animator Override Controller

替换 Animator Controller 中的动画片段,同时保留其结构、参数和逻辑。适用于多个角色共用同一状态机但使用不同动画片段的情况。

重要: 使用 Override Controller 时,请将过渡退出时间设为标准化时间(而非秒数),否则,如果覆盖动画片段的时长不同,退出时间可能会被忽略。

层
  • Override 模式 -- 替换之前各层中的动画
  • Additive 模式 -- 在之前各层的动画基础上叠加动画
  • Avatar Mask -- 限制某一层作用于特定身体部位(例如,仅上半身)
  • 同步层 -- 使用不同动画片段复用状态机结构

状态机和过渡

状态

Animator Controller 中的每个状态都代表一个独立动作。特殊状态包括:

  • Entry -- 默认入口
  • Any State -- 从任何当前状态进行过渡
  • Exit -- 退出当前状态机或子状态机
过渡

过渡定义状态之间如何融合。

设置描述
Has Exit Time在标准化时间点触发过渡(例如,0.75 = 已完成 75%)
Transition Duration融合时长;以秒为单位(Fixed Duration),或为源状态时长的一部分
Transition Offset目标状态开始播放的位置(0.5 = 中点)
Conditions基于参数的规则;必须同时满足所有规则
Interruption Source可中断过渡的来源:None、Current State、Next State,或它们的组合
Ordered Interruption决定过渡解析是在当前过渡处停止,还是继续检查任何有效过渡

同时设置 Has Exit Time 和 Conditions 时,Unity 只会在退出时间之后检查条件。

状态机行为

继承 StateMachineBehaviour 的脚本会附加到状态上。回调包括:OnStateEnter、OnStateUpdate、OnStateExit、OnStateMove、OnStateIK。它们都会接收 (Animator animator, AnimatorStateInfo stateInfo, int layerIndex)。

public class AttackState : StateMachineBehaviour
{
    public AudioClip attackSound;
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        AudioSource.PlayClipAtPoint(attackSound, animator.transform.position);
    }
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        // Cleanup when leaving state
    }
}

混合树

混合树根据参数值,在多个相似动画之间平滑混合;与之不同,过渡会在一段时间内从一个独立状态切换到另一个状态。

关键要求: 动画必须性质相似且时序一致。脚部接触点应在归一化时间上对齐(例如,左脚位于 0.0,右脚位于 0.5)。

混合类型
  • 1D -- 单个参数控制混合(例如,用速度控制走路/跑步)
  • 2D Simple Directional -- 两个参数,每个方向对应一个动作
  • 2D Freeform Directional -- 两个参数,每个方向对应多个动作
  • 2D Freeform Cartesian -- 两个参数,动作不代表方向
  • Direct -- 每个动作都有自己的权重参数(面部动画)

创建: 在 Animator Controller 中右键单击 > Create State > From New Blend Tree。双击进入图表。通过 Inspector 添加子动作。

// Driving a locomotion blend tree from script
void Update()
{
    float speed = new Vector3(rb.velocity.x, 0, rb.velocity.z).magnitude;
    float direction = Vector3.SignedAngle(transform.forward,
        rb.velocity.normalized, Vector3.up);

    animator.SetFloat("Speed", speed, 0.1f, Time.deltaTime);
    animator.SetFloat("Direction", direction, 0.1f, Time.deltaTime);
}

Avatar 和 Humanoid 骨架

Avatar 系统会识别模型是否为人形,并映射身体部位以进行动画重定向。

核心概念
  • Avatar -- 将骨骼结构映射到 Unity 的内部人形格式
  • 重定向 -- 共享相同 Avatar 映射的不同人形骨架之间可以转移动画
  • 肌肉定义 -- 在肌肉空间而非骨骼空间中直观地控制
设置
  1. 在 Project 窗口中选择模型
  2. 在 Import Settings 的 Rig 选项卡中,将 Animation Type 设置为 Humanoid
  3. 配置 Avatar 映射(Unity 会在可能的情况下自动映射)
  4. 在 Avatar Configuration 窗口中验证并调整骨骼分配

根运动

根运动会将动画驱动的移动传递到 GameObject 的 Transform。

架构
  • 身体 Transform -- 角色的重心;存储世界空间曲线
  • 根 Transform -- 身体 Transform 在 Y 平面上的投影;每帧在运行时计算
动画片段检查器设置
设置用途
Bake Into Pose (Rotation)朝向保留在身体上;GameObject 不会接收旋转
Bake Into Pose (Y)垂直运动保留在身体上;除跳跃动画外,均应启用
Bake Into Pose (XZ)水平运动保留在身体上;应为待机动画启用,以防止漂移
Based UponBody Orientation(动作捕捉)、Original(关键帧动画)、Feet(防止浮空)

Animator.gravityWeight 由 Bake Into Pose Position Y 控制:启用时为 1,禁用时为 0。

// Custom root motion handling
using UnityEngine;

[RequireComponent(typeof(Animator))]
public class RootMotionController : MonoBehaviour
{
    Animator animator;
    CharacterController controller;

    void Start()
    {
        animator = GetComponent<Animator>();
        controller = GetComponent<CharacterController>();
        animator.applyRootMotion = false; // We handle it manually
    }

    void OnAnimatorMove()
    {
        // Apply root motion through CharacterController
        Vector3 deltaPosition = animator.deltaPosition;
        deltaPosition.y -= 9.81f * Time.deltaTime; // Add gravity
        controller.Move(deltaPosition);
        transform.rotation *= animator.deltaRotation;
    }
}

动画事件

动画事件会在动画时间轴上的指定位置触发函数。

参数类型
  • Float -- 数值(例如,音量)
  • Int -- 整数值
  • String -- 文本数据
  • Object -- GameObject 或 Prefab 引用
设置
  1. 在 Animation 选项卡中展开 Events 部分
  2. 将播放头定位到所需帧
  3. 单击 Add Event
  4. 设置与已附加脚本上的方法匹配的函数名称
using UnityEngine;

public class FootstepHandler : MonoBehaviour
{
    public AudioClip[] footstepSounds;
    public GameObject dustPrefab;

    // Called by animation event -- function name must match event
    public void PlayFootstep(int footIndex)
    {
        if (footstepSounds.Length > 0)
        {
            int clipIndex = Random.Range(0, footstepSounds.Length);
            AudioSource.PlayClipAtPoint(footstepSounds[clipIndex], transform.position);
        }
    }

    // Called by animation event passing an Object parameter
    public void SpawnEffect(Object effectPrefab)
    {
        Instantiate((GameObject)effectPrefab, transform.position, Quaternion.identity);
    }
}

Timeline

Timeline 可用于创建电影内容、游戏玩法序列、音频序列和粒子效果。包:com.unity.timeline(v1.8.11,与 Unity 6.3 兼容)。

核心组件
  • Timeline Asset -- 定义轨道、片段及其排列方式
  • Timeline Instance -- 绑定到特定场景对象的运行时实例
  • Playable Director -- 播放 Timeline 资源并将轨道绑定到场景对象的组件
轨道类型
轨道用途
Animation控制绑定的 GameObject 上的 Animator
Audio在绑定的 AudioSource 上播放 AudioClip
Activation启用/禁用绑定的 GameObject
Signal通过 SignalReceiver 在特定时间触发事件
Control触发子 Timeline、粒子系统或其他 Playable Director
Playable使用 Playables API 的自定义轨道
功能
  • 直接在 Timeline 中录制动画
  • 支持 Humanoid 动画
  • Animation Override 轨道
  • 用于模块化组合的子 Timeline

动画脚本编写

反向运动学(IK)

要求:Humanoid Avatar,且已在层上启用 IK Pass。

using UnityEngine;

public class IKController : MonoBehaviour
{
    Animator animator;
    public bool ikActive = true;
    public Transform rightHandTarget;
    public Transform lookTarget;

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    void OnAnimatorIK()
    {
        if (animator == null || !ikActive) return;

        // Look at target
        animator.SetLookAtWeight(1f);
        animator.SetLookAtPosition(lookTarget.position);

        // Right hand IK
        animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1f);
        animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1f);
        animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandTarget.position);
        animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandTarget.rotation);
    }
}
直接播放状态
// Play state immediately
animator.Play("Attack");

// Play with normalized time offset (start at 50%)
animator.Play("Attack", 0, 0.5f);

// Crossfade into state over 0.25 seconds
animator.CrossFadeInFixedTime("Run", 0.25f);

// Crossfade using normalized time
animator.CrossFade("Run", 0.1f);
查询状态信息
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);

if (stateInfo.IsName("Attack"))
{
    Debug.Log("Currently attacking");
}

if (stateInfo.normalizedTime >= 1.0f && !animator.IsInTransition(0))
{
    Debug.Log("Animation finished");
}

// Use hash for performance
int runHash = Animator.StringToHash("Run");
if (animator.HasState(0, runHash))
{
    animator.Play(runHash);
}
目标匹配
// Match character's hand to a ledge position during climb animation
animator.MatchTarget(ledgePosition, Quaternion.identity, AvatarTarget.RightHand,
    new MatchTargetWeightMask(Vector3.one, 0f), 0.1f, 0.4f);

常见模式

角色移动控制器
using UnityEngine;

[RequireComponent(typeof(Animator))]
public class LocomotionController : MonoBehaviour
{
    Animator animator;
    static readonly int SpeedHash = Animator.StringToHash("Speed");
    static readonly int GroundedHash = Animator.StringToHash("Grounded");
    static readonly int JumpHash = Animator.StringToHash("Jump");

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        float speed = Input.GetAxis("Vertical");
        animator.SetFloat(SpeedHash, Mathf.Abs(speed), 0.1f, Time.deltaTime);
        animator.SetBool(GroundedHash, IsGrounded());

        if (Input.GetButtonDown("Jump") && IsGrounded())
        {
            animator.SetTrigger(JumpHash);
        }
    }

    bool IsGrounded()
    {
        return Physics.Raycast(transform.position + Vector3.up * 0.1f, Vector3.down, 0.2f);
    }
}
运行时替换 Override Controller
using UnityEngine;

public class WeaponAnimSwap : MonoBehaviour
{
    public AnimatorOverrideController swordOverride;
    public AnimatorOverrideController bowOverride;
    Animator animator;

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    public void EquipSword()
    {
        animator.runtimeAnimatorController = swordOverride;
    }

    public void EquipBow()
    {
        animator.runtimeAnimatorController = bowOverride;
    }
}

反模式

反模式问题解决方案
每帧使用字符串参数GC 分配、查找速度慢使用 Animator.StringToHash() 缓存哈希值
将 Humanoid 骨架用于非人形对象造成 15-20% 不必要的 CPU 开销对道具、动物和特效使用 Generic 动画类型
在共享根对象下放置多个 Animator每个层级只能进行单线程写入,阻碍并行处理为每个 Animator 分配独立的根 GameObject
过度使用 StateMachineBehaviour会创建同步点,破坏并行求值尽量减少 SMB 回调;优先使用脚本端逻辑
通过 Animator 为 RectTransform 设置动画会导致确定性写入问题使用旧版 Animation 组件为 UI 设置动画
为单次播放动画创建复杂状态机即使在空闲状态下也会持续评估转换改用 Animation 组件或 Playables API
手动调用 Animator.Update()会绕过并行执行使用 PlayableGraph 进行手动控制,同时保留并行处理
未将转换退出时间设为 normalizedOverride Controller 片段可能会跳过转换始终使用 normalized time 设置退出时间
更换控制器后忘记调用 Rebind()绑定信息过时会导致播放错误在运行时更换控制器时调用 Rebind()
空闲片段未启用 Bake Into Pose(XZ)空闲动画会累积漂移为静止片段启用 Bake Into Pose XZ

核心 API 速查

Animator —— 核心属性

applyRootMotion、speed、updateMode(Normal/AnimatePhysics/UnscaledTime)、cullingMode、deltaPosition、deltaRotation、velocity、isHuman、layerCount、bodyPosition、gravityWeight

Animator —— 核心方法
  • 播放: Play(state, layer, time)、CrossFade(state, duration)、CrossFadeInFixedTime(state, duration)
  • 参数: SetFloat/Int/Bool/Trigger(name, value)、GetFloat/Int/Bool(name)、ResetTrigger(name)
  • 状态查询: GetCurrentAnimatorStateInfo(layer)、GetNextAnimatorStateInfo(layer)、IsInTransition(layer)
  • IK: SetIKPosition/Rotation(goal, value)、SetIKPositionWeight/RotationWeight(goal, weight)、SetLookAtPosition(pos)
  • 实用工具: MatchTarget(...)、GetBoneTransform(bone)、Rebind()、StringToHash(name)、SetLayerWeight(layer, weight)

相关技能

  • unity-foundations —— Unity 核心概念、GameObject、组件、项目结构
  • unity-scripting —— MonoBehaviour 生命周期、C# 模式、事件系统
  • unity-graphics —— 渲染、材质、着色器(与动画材质属性相关)

Additional Resources

评论

支持 Markdown