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动画系统学习笔记动画剪辑与状态机的主要内容,如果未能解决你的问题,请参考以下文章

Unity笔记一些动画系统状态机细节

Unity3D之Mecanim动画系统学习笔记:Animation View

Unity3D之Mecanim动画系统学习笔记:Animator Controller

Unity 2D Animation——3.待机动画录制

Unity—ParticleSystem(粒子系统)与Animator(动画状态机)批量管理器

unity怎么unity判断动画结束