Unity动画系统学习笔记动画剪辑与状态机
Posted 夜槿笙歌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity动画系统学习笔记动画剪辑与状态机相关的知识,希望对你有一定的参考价值。
一、动画系统工作流
一个完整的动画系统工作流包含如下几个部分:
- 动画剪辑(Animation Clips):包含某些对象如何随时间更改其位置、旋转或其他属性的信息。
- 状态机(Animator Controller):跟踪当前正在播放的动画剪辑,以及当动画剪辑应该改变或混合在一起时的状态信息。
- 骨骼(Avatar):用来映射人形角色的一种通用内部格式。通过骨骼可以将外部的人形动画重定向到我们自己的角色模型中。
- 动画组件(Animator):动画剪辑、状态机、骨骼一同通过动画组件附加到某个游戏物体上。
二、动画剪辑
如果学习过视频剪辑或动画制作相关的知识,应该对它再熟悉不过了。简单来讲,就是在时间轴上打上一个个关键帧,并改变每个关键帧时物体的属性。然后Unity就会自动生成关键帧之间的形状、动作补间,使物体具有连贯、流畅的动画。
选中一个物体,按「Ctrl+6」打开「Animation」面板,为其创建一个动画剪辑。
创建完成后,点击「Add Property」按钮,就可以添加你想要改变的属性
添加属性后,在时间轴上打上对应的关键帧,并改变属性的数值,就可以形成一段连续的动画
点击左上角的录制模式,我们就可以直接在场景中调整物体的各项属性,Animation会自动添加关键帧并记录下改变后的数值。
点击左下角的「Curves」可以进入动画曲线界面
三、动画状态机
在给物体创建完动画剪辑后,在目录中会自动生成一个「Animator Controller」,这个文件就是动画状态机。通过动画状态机,我们可以控制模型在各个动画状态之间进行切换。
现在来尝试实现实现一个让角色从待机状态切换到死亡状态的动画。首先将两个动画剪辑导入到状态机,然后将待机状态设置为当前层级的默认状态
再创建一条从待机状态到死亡状态的切换
添加一个参数「isDead」,并设置为转换条件
将状态机挂载到主角身上的「Animator」组件中,运行游戏。通过手动控制「isDead」参数观察效果。
要通过代码控制参数的改变也很简单
_animator.SetBool("isDead",true);
或
private static readonly int IsDead = Animator.StringToHash("isDead");
// ...
_animator.SetBool(IsDead,true);
3.1 混合树
在某些情况下,我们需要动画之间进行平滑的过渡,而不是从一个状态直接切换到另一个状态。比如处在走路状态时,要切换到跑步状态,就需要一个加速的过程。这种效果可以通过混合树实现。
3.1.1 1D混合
对于从走路到跑步的动画切换,可以通过一个简单的1D混合树来实现。
首先在状态机中创建一个混合树,然后双击打开
可以发现,混合树自动创建了一个参数,且混合类型默认是1D混合。
我们需要根据速度来进行走路到奔跑的切换,所以将参数名改为Speed。然后将行走和奔跑的动画剪辑添加进来
然后在Idle状态和混合树之间进行连线,如果Speed大于0,则进入混合树,否则维持在Idle状态。这里为了防止精度问题,将阈值设置为0.1。运行游戏看下效果
PS:如果从外部导入的动画有如下这种报错的话,可以将动画剪辑中的Events事件删除。
3.1.2 2D混合
当我们的动画混合比较复杂,一个参数已经无法满足需求时,就可以采用2D混合。2D混合具有如下几种类型
- 2D Simple Directional:这种混合模式适用于不同方向动画的混合,比如前进、后退、向左、向右。但在同一方向上有多个动画,如行走和奔跑,则不建议用这种模式
- 2D Freedom Directional:这种混合模式适用于存在多个相同方向动画的情况,需要有一个原点(比如Idle)。
- 2D Freedom Cartesian:这种混合模式适用于两个参数类型不同的动画,比如角速度和线速度。
下面我们来使用「2D Freedom Directional」模式来制作角色完整的移动效果。首先增加两个控制参数「Horizontal」和「Vertical」用来控制水平方向的动画和垂直方向的动画。
然后将各个方向的动画添加到混合树中,根据方向设置「Horizontal」和「Vertical」的值
挪动中间的红点,可以预览混合的效果
然后在代码中根据输入,设置「Horizontal」和「Vertical」参数
_horizontal = Input.GetAxis("Horizontal");
_vertical = Input.GetAxis("Vertical");
_animator.SetFloat("Horizontal",_horizontal);
_animator.SetFloat("Vertical",_vertical);
看下效果
3.2 子状态机
游戏中一个角色的动画状态机内可能会包含数十个动画剪辑,如果这些动画剪辑都堆在同一个界面中,势必会造成状态机的混乱和难以维护。为了解决这一问题,我们可以使用子状态机。它可以将一组相关的动画提取出来,放入同一个状态机内。
接下来我们尝试将跳跃动画放入一个子状态机中。首先创建一个子状态机,命名为Jump,然后双击进入。
可以看到,子状态机与普通的状态机几乎相同,唯一的区别在于多了一个回到上层的入口((Up)Base Layer)。
接下来导入跳跃的动画。一般跳跃动画有三个,分别是起跳、降落、落地。因为在跳跃过程中,落地的时机是不确定的,因此我们将Land设置为当前状态机的默认状态,且从Fall到Land的切换不需要等待动画片段播放完成。添加一个isLand
参数,用来判断当前是否落地。如果落地,则执行从Fall到Land状态的切换。
然后在代码中通过Animator.CrossFade()
方法触发该状态机。该方法需要传入子状态机名称和过渡时间。
_animator.CrossFade("Jump",0.1f);
看下效果
可以发现落地后会暂停一会儿才会切换到奔跑动作,这是因为落地动画播放完成后,状态先转换到Idle,然后再转换到Move导致的。我们可以将Land直接连接到上层的Idle和Move,使其能够快速切换到相应的状态。
看下效果
3.3 重写动画控制器
我们在前面完成了一套基础的动画状态控制器,但假如我们的角色要换一个职业,该职业有着相同的动画状态,但却有不同的动画剪辑,难道我们需要重新复制一份动画控制器吗?显然不是,Unity为我们提供了重写动画控制器的选项。
在工程目录中点击右键「Create -> Animator Override Controller」就可以创建一个重写动画控制器。
然后将原本的动画控制器拖入,即可识别出所有的动画状态,我们只需要把对应的动画剪辑拖入即可。如果没有指定新的动画剪辑,则会播放原本的动画控制器对应的动画。
指定完动画剪辑后,将重写的控制器挂载到角色身上,看下效果
四、参考代码
角色控制器
public class PlayerController : MonoBehaviour
public float MoveSpeed = 5f;
public float RotateSpeed = 40f;
public float JumpScale = 10f;
private Rigidbody _rigidbody;
private TriggerCheck _groundCheck;
private Animator _animator;
private float _horizontal;
private float _vertical;
private static readonly int Speed = Animator.StringToHash("Speed");
private static readonly int IsLand = Animator.StringToHash("isLand");
private void Awake()
_rigidbody = GetComponent<Rigidbody>();
_groundCheck = transform.Find("GroundCheck").GetComponent<TriggerCheck>();
_animator = GetComponent<Animator>();
private void Update()
_horizontal = Input.GetAxis("Horizontal");
_vertical = Input.GetAxis("Vertical");
_animator.SetFloat("Horizontal",_horizontal);
_animator.SetFloat("Vertical",_vertical);
if (Input.GetKeyDown(KeyCode.Space) && _groundCheck.IsTrigger)
_rigidbody.AddForce(Vector3.up*JumpScale);
_animator.CrossFade("Jump",0.1f);
_animator.SetBool(IsLand,_groundCheck.IsTrigger);
private void FixedUpdate()
if (_vertical != 0)
_rigidbody.MovePosition(transform.position+transform.forward * (MoveSpeed * Time.fixedDeltaTime * _vertical));
else if(_horizontal != 0)
_rigidbody.MovePosition(transform.position+transform.right * (MoveSpeed * Time.fixedDeltaTime * _horizontal));
if (_horizontal != 0 && _vertical != 0)
transform.eulerAngles += Vector3.up * (RotateSpeed * _horizontal * Time.fixedDeltaTime);
_animator.SetFloat(Speed,_vertical);
落地触发检测
public class TriggerCheck : MonoBehaviour
private int _count;
public bool IsTrigger => _count > 0;
public LayerMask TargetLayers;
public Action OnTriggered;
private void OnTriggerEnter(Collider other)
if (IsTargetLayer(other.gameObject, TargetLayers))
_count++;
private void OnTriggerExit(Collider other)
if (IsTargetLayer(other.gameObject, TargetLayers))
_count--;
private bool IsTargetLayer(GameObject obj, LayerMask targetLayers)
// 根据Layer数值进行位移获得用于运算的Mask值
int objLayerMask = 1 << obj.layer;
return (targetLayers.value & objLayerMask) > 0;
Unity3D之Mecanim动画系统学习笔记:Animation View
动画组件之间的关系
我们先看一张图:
这里我们可以看到,我们在GameObject之上绑定的Animator组件是控制模型进行动画播放的。
而其属性Controller则对应一个Animator Controller文件,该文件可以在Animator窗口中打开,其是被设计为状态机形式的系统,多个状态之间的切换关系可以在该界面进行设置。
Animator Controller中的每个状态则对应一个Animation Clip,每个Animation Clip是一个简单的动画单元,可以在Animation窗口中打开。
动画文件
Animation Clip
如果fbx包含动画文件,则可以在其内部创建多个动画剪辑,每个Animation Clip包含一个简单的动画如Idle、run等等。
Animation文件
老的动画形式的文件,使用XML数据来记录动画信息。
Animation View的使用
好了,回到我们的主角Animation View中来,对于Animation Clip文件来说,虽然可以使用Animation窗口打开编辑,但是我们一般都不会进行编辑,因为太过于复杂,这部分内容应该在建模软件中由美术编辑好。
那么,Animation窗口可以用来做什么呢?答案是我们可以用它来制作一些简单的动画,比如在一些游戏中,战斗开始前,摄像机会在整个场景中来回移动最后再回到角色后方(目的是让玩家在战斗之前了解敌人所在方位及战斗场景的地形)。
移动摄像机的示例
1.首先我们打开Animation窗口,选择场景中的Main Camera,然后点击Animation窗口中的Create按钮,保存我们的文件,Unity会生成两个文件,一个Animator Controller文件及一个Animation文件。
2.下面我们点击Add Property两次,添加position及rotation,如下:
3.将红色线条移到最后一帧,然后点击菜单栏“GameObject”->“Align With View”,将摄像机移动到当前Scene视图的位置同时旋转使其面向Scene视图面向的方向。
4.点击播放按钮即可看到摄像机移动效果。
5.如果还要继续添加可以在后面双击时间轴添加关键帧即可。
此时我们会发现Main Camera已经被添加一个Animator的组件,同时所有的参数也已经设置好了,我们直接点击运行游戏就可以看到效果。
Apply Root Motion
在我们的Animator组件中,有一个Apply Root Motion的选项,该选项没有勾选时,我们的动画会按照世界坐标来移动,即我们的Animation上的数值会直接设定到目标的position之上,而如果勾选,则是在目标的position之上添加我们的动画设定的数值。
事件系统
对于一个动画,我们还可以为其任意一帧添加一个事件:
1.首先,我们为Main Camera添加一个接收Event事件的组件:
1 using UnityEngine; 2 using System.Collections; 3 4 public class TestAnimEvent : MonoBehaviour 5 { 6 public void ShowAnimMsg(string msg) 7 { 8 print(msg); 9 } 10 }
2.下面我们在Animation窗口中的任意一帧添加一个事件,并调用ShowAnimMsg方法同时传递一个字符串:
3.运行游戏就会看到输出了。
那么我们可以使用Event的功能做什么呢?试想一下,如果我们希望我们的人物在跳跃着陆时需要播放一个音效,就可以使用到事件的功能了。
以上是关于Unity动画系统学习笔记动画剪辑与状态机的主要内容,如果未能解决你的问题,请参考以下文章
Unity3D之Mecanim动画系统学习笔记:Animation View
Unity3D之Mecanim动画系统学习笔记:Animator Controller