Unity之Mecanim动画系统笔记

本文原创版权归 博客园 阿诚de窝 所有,此处为技术收藏,如有再转,请自觉于文章醒目位置标明原文作者及出处,以示尊重!

作者:阿诚de窝

原文:http://www.cnblogs.com/hammerc/p/4825877.html

Mecanim简介

Mecanim动画系统是Unity3D4.0开始引入的一套全新的动画系统,主要提供了下面4个方面的功能:

  1. 针对人形角色提供一套特殊的工作流。
  2. 动画重定向的能力,可以非常方便的把动画从一个角色模型应用到其他角色模型之上。
  3. 提供可视化的Animation编辑器,可以方便的创建和预览动画片段。
  4. 提供可视化的Animator编辑器,可以方便的管理多个动画切换的状态。

工作流

模型的准备

Unity不能制作3D模型和进行骨骼绑定,这些需要在专业的建模软件中由美术进行制作,一般常用的建模软件有下面几种:

  • 3DMax
  • Maya
  • Cinema4D
  • Blender
  • Mixamo

当美术制作好了资源以后,我们只需要将这些资源导入到Unity3D中使用即可。

角色设置

导入到Unity3D的资源需要进行一些简单的设置,主要分为下面两种设置:

  • 人形角色的设置;
  • 通用角色的设置。

让角色运动

通过Unity3D Mecanim提供的各种工具对动画进行配置,使其可以正常播放,常用的Mecanim模块如下:

  • 动画剪辑(Animation Clip)
  • 动画状态机(State Machines)
  • 混合树(Blend Tree)
  • 动画参数(Animation Parameters)

示例

学习动画系统需要有具体的动画文件及资源,这里我们使用官方提供的示例场景,大家可以在Unity Asset Store中下载到,地址如下:

https://www.assetstore.unity3d.com/en/#!/content/5328

使用Unity5.0

虽然Mecanim是4.0推出的系统,但是我还是使用Unity5.0来进行学习,当然对于Mecanim来说,使用4.x还是5.x都不会有太大的区别。

我们要在Unity3D中使用上模型和动画,需要经过下面几个阶段的制作,下面以一个人形的模型开发为准来介绍。

模型制作

模型建模(Modelling)

我们的美术在建模时一般会制作一个称为T-Pose(及双臂张开)的模型。

骨骼绑定(Rigging)

在之前制作的模型上进行骨骼的绑定,我们需要注意的是骨骼数量不能少于15根,同时要遵循Unity3D的骨骼制作标准,如下:

http://docs.unity3d.com/Manual/Preparingacharacterfromscratch.html

蒙皮(Skinning)

即刷权重的过程,即将骨骼影响的模型每块三角面的权重进行设置,这样我们的骨骼才能影响到模型,产生动画。

模型导入

Unity3D支持下面几种模型格式的文件导入:

  • fbx
  • obj
  • max
  • mb
  • blend

更多的信息可以参考官网的用户手册:http://docs.unity3d.com/Manual/HOWTO-importObject.html

由于类型max等文件是对应建模软件的源文件,Unity3D要支持该文件需要安装3DMax,其它文件也一样,那么比较好的文件格式是fbx,该格式是通用的3D文件格式,而虽然obj也是通用的3D文件格式,但是其不支持动画等特性,一般只用于静态物体。

Fbx SDK

这里提供Fbx的SDK下载地址:http://www.autodesk.com/products/fbx/overview

关于Fbx的模型文件该如何导出,可以参考Unity3D给出的引导:http://docs.unity3d.com/Manual/HOWTO-exportFBX.html

导入到Unity3D中

导入非常简单,只需要在Project面板中右击选择“Import New Asset...”,再弹出菜单中选择我们导出的fbx文件即可。

模型配置

我们在Project窗口中选中导入的模型后,在Inspector窗口就会看到该模型的配置信息,一共有下面3个标签页,同时会给出官网的解说链接:

Model:http://docs.unity3d.com/Manual/FBXImporter-Model.html

这里需要额外说的就是,当我们的模型需要使用到Light Mapping烘焙系统时,需要勾选Generate Lightmap UVs选项来开启第二套uv。

Rig:http://docs.unity3d.com/Manual/FBXImporter-Rig.html

这里需要额外说的就是,骨骼的类型:None:表示没有骨骼,Legacy:表示为老版本动画系统的骨骼,Generic:表示为通用的骨骼,Humanoid:表示为人形骨骼(设置为人形骨骼可以使用Mecanim专门为人形骨骼开发的各种功能了)。

如果选择了Humanoid类型,可以点击Configura...按钮对人形骨骼和肌肉配置进行修改。

Animations:http://docs.unity3d.com/Manual/FBXImporter-Animations.html

动画组件之间的关系

我们先看一张图:

Unity之Mecanim动画系统笔记_第1张图片

这里我们可以看到,我们在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,如下:

Unity之Mecanim动画系统笔记_第2张图片

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方法同时传递一个字符串:

Unity之Mecanim动画系统笔记_第3张图片

3.运行游戏就会看到输出了。

那么我们可以使用Event的功能做什么呢?试想一下,如果我们希望我们的人物在跳跃着陆时需要播放一个音效,就可以使用到事件的功能了。

动画的设置

我们先看看Animation Clip的一些设置:

Loop time:动画是否循环播放。

下面出现了3个大致一样的选项:

Root Transform Rotation:表示为播放动画的对象的旋转方面的信息。

Root Transform Position(Y):表示为播放动画的对象的位置 y 轴方面的信息。

Root Transform Position(XZ):表示为播放动画的对象的位置 XZ 平面方面的信息。

他们都有一致的选项,我们逐个的来看:

Bake Into Pose:不勾选表示动画产生的旋转或位移(具体看是哪个选项下面)会应用到播放动画的主体对象上,勾选则表示不会应用到播放动画的主体对象上。

具体的效果如下(我的这个动画是人物进行前空翻的动画):Root Transform Position(XZ)的Bake Into Pose没有勾选,表示人物前空翻时,主体GameObject会向前移动,如果勾选则GameObject在XZ平面则不会移动。同理,Root Transform Position(Y)勾选表示GameObject在Y轴不移动,如果不勾选,则跟随摄像机会产生抖动,因为目标GameObject在Y轴也出现了跳跃时移动。

Based Upon:基准点,有如下类型:

  • Original:表示基准点使用在动画文件中预设好的值。
  • Center of Mass:表示基准点使用质量中心,表现为人物下半身会嵌入地面。
  • Feet:表示基准点使用第一帧的脚。

动画状态

我们先简单的看看动画状态,点击项目中任意一个Animator Controller,打开Animator界面可以看到如下的信息:

我们先不管左面的Layers和Parameters面板,右方的显示面板是一个标准的有限状态机配置面板,其中有3个状态“Entry”、“Any State”和“Exit”是由系统生成的,其它状态则由我们自己创建和管理,需要注意的是在Unity4.x中,系统创建的状态只有“Any State”一个。

每个状态之间由带箭头的线段连接,表示这个状态可以过渡到指向的状态。

状态面板

状态关系和切换我们以后再看,现在我们点击任意一个状态,在Inspector面板可以看到该状态的详细信息,如下:

  • Motion:表示当前状态对应的Animation Clip;
  • Speed:表示当前状态的速度,1表示正常速度,后面的Parameter勾上表示使用一个参数来表示当前的速度,同时输入框会变为下拉选择框,我们可以选择指定的参数,参数可以在Parameters面板中配置,其作用就是可以方便的通过代码修改参数的值来达到控制速度的目的,下面的Parameter也一致,就不赘述了。
  • Mirror:是否将动画沿Y轴进行翻转,一般用来复用动画,比如招右手的动画勾选了此项就会变为招左手;
  • Cycle Offset:播放偏移量;
  • Foot IK:是否开启脚部的IK动画(反向动力学),一般关闭,在需要脚部贴合地面的情况时可以开启;
  • Write Defaults:动画播放完毕后是否将状态重置为默认状态,一般勾选即可;

下面的Transitions面板则会列出当前状态可以过渡到其它状态的列表:

  • Solo:勾选表示当前过渡为唯一过渡,即当前状态只能过渡到这个项目指向的状态;
  • Mute:勾选表示使这个动画过渡关闭,即当前状态不能过渡到这个项目指向的状态;

简介

Animator Controller在Unity中是作为一种单独的配置文件存在的文件类型,其后缀为controller,Animator Controller包含了以下几种功能:

  • 可以对多个动画进行整合;
  • 使用状态机来实现动画的播放和切换;
  • 可以实现动画融合和分层播放;
  • 可以通过脚本来对动画播放进行深度控制;

下面我们通过一个图来直观的看看动画的组成结构:

Unity之Mecanim动画系统笔记_第4张图片

Animator组件用来控制人物动画的播放,其中需要的两个最核心的内容就是控制动画播放逻辑的Animator Controller以及动画骨骼Avatar对象。

Animator组件

我们需要播放动画的角色都需要添加Animator组件,该组件即为我们控制动画的接口,下面我们来看看Animator组件:

Unity之Mecanim动画系统笔记_第5张图片

  • Controller:使用的Animator Controller文件。
  • Avatar:使用的骨骼文件。
  • Apply Root Motion:绑定该组件的GameObject的位置是否可以由动画进行改变(如果存在改变位移的动画)。
  • Update Mode:更新模式:Normal表示使用Update进行更新,Animate Physics表示使用FixUpdate进行更新(一般用在和物体有交互的情况下),Unscale Time表示无视timeScale进行更新(一般用在UI动画中)。
  • Culling Mode:剔除模式:Always Animate表示即使摄像机看不见也要进行动画播放的更新,Cull Update Transform表示摄像机看不见时停止动画播放但是位置会继续更新,Cull Completely表示摄像机看不见时停止动画的所有更新。

下方还会显示我们的动画的一些主要信息。

Animator Override Controller

我们在右键创建资源时还会发现这样的一个资源,该资源的意思是基于某个Animator Controller进行每个状态的动画的修改,其他设置不变,可以方便的复用我们已经创建好的Animator Controller。

创建一个Animator Controller

我们在Project视图中右击菜单中可以创建Animator Controller,下面我们看看新创建的Animator Controller:

Unity之Mecanim动画系统笔记_第6张图片

首先,我们发现的是3个默认的状态,这些状态是Unity自动帮我们创建的同时也无法删除:

  • Entry:表示当进入当前状态机时的入口,该状态连接的状态会成为进入状态机后的第一个状态;
  • Any State:表示任意的状态,其作用是其指向的状态是在任意时刻都可以切换过去的状态;
  • Exit:表示退出当前的状态机,如果有任意状态指向该出口,表示可以从指定状态退出当前的状态机;

创建新状态

1.我们可以通过右键菜单进行创建:

Unity之Mecanim动画系统笔记_第7张图片

2.或者我们可以通过把一个Animation Clip拖拽到状态机窗口中进行创建;

我们发现我们创建的第一个状态被设置为默认的第一个状态,第一个状态会被标记为黄色,如下:

Unity之Mecanim动画系统笔记_第8张图片

我们可以通过Inspector窗口进行该状态的设置,上篇笔记有详细的解说。

我们可以使用这两种方法来创建多个状态,同时配置好每个状态,如下:

Unity之Mecanim动画系统笔记_第9张图片

状态切换

我们的状态机已经有了状态,但是还没有指定每个状态之间的关系,下面我们来看看该如何指定状态之间的关系。

在Mecanim中,动画之间的播放不再是通过调用诸如“Play”之类的方法进行切换了,而是通过判断参数的变换来进行状态即动画的切换。

我们打开Parameters面板,这里被用来设置状态机使用到的各种参数,如下:

Unity之Mecanim动画系统笔记_第10张图片

点击加号可以创建一个参数,在Unity中允许我们创建4种类型的参数:

Float:float类型的参数,多用于控制状态机内部的浮点型参数;

Int:int类型的参数,多用于控制状态机内部的整型参数;

Bool:bool类型参数,多用于状态切换;

Trigger:本质上也是一个bool类型的参数,但是其值默认为false,且设置为true后系统会自动将其还原为false;

下面我们创建两个简单的Bool类型的变量,如下:

Unity之Mecanim动画系统笔记_第11张图片

我们希望,当Walk为true时人物走动,为false时人物站立,Run也是一致,只不过会过渡到跑动。

下面我们通过右击状态的菜单可以拉出一条状态转换的线条,表示当前状态转换到目标状态。

Unity之Mecanim动画系统笔记_第12张图片

我们点击线条可以在Inspector窗口设定转换的条件,如下:

Unity之Mecanim动画系统笔记_第13张图片

我们在条件框设置Walk为true,表示当Walk被设置为true时就从站立跳转到走路动画,同样的,走路回到站立也需要一条转换的线条,不过Walk要设置为false:

Run的设定也一致。

Has Exit Time

我们发现线条中有一个勾选项Has Exit Time,那么它是什么意思呢?

Unity之Mecanim动画系统笔记_第14张图片

如果我们勾选了该项,在动画转换时会等待当前动画播放完毕才会转换到下一个动画,如果当前动画是循环动画会等待本次播放完毕时转换,所以对于需要立即转换动画的情况时记得要取消勾选。

还有一种情况时,当我当前的动画播放完毕后就自动转换到箭头所指的下一个状态(没有其他跳转条件),此时必须勾选该选项,否则动画播放完毕后就会卡在最后一帧,如果是循环动画就会一直循环播放。

控制动画转换

我们在绑定了Animator组件的GameObject之上添加下面的新脚本组件,就可以实现通过按键来切换人物播放的动画了:

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class TestAnimChange : MonoBehaviour
 5 {
 6     private Animator _animator;
 7 
 8     void Start()
 9     {
10         _animator = this.GetComponent();
11     }
12     
13     void Update()
14     {
15         if(Input.GetKeyDown(KeyCode.W))
16         {
17             _animator.SetBool("Walk", true);
18         }
19         if(Input.GetKeyUp(KeyCode.W))
20         {
21             _animator.SetBool("Walk", false);
22         }
23         if(Input.GetKeyDown(KeyCode.R))
24         {
25             _animator.SetBool("Run", true);
26         }
27         if(Input.GetKeyUp(KeyCode.R))
28         {
29             _animator.SetBool("Run", false);
30         }
31     }
32 }
复制代码

 

控制人物动画播放

这里我重新弄了一个简单的场景和新的Animator Controller来作为示例。

下面先看看Animator Controller的配置:

Unity之Mecanim动画系统笔记_第15张图片

人物在站立状态只能进入走路,走路只能进入奔跑或返回站立,奔跑只能进入跳跃或返回走路,跳跃则只能返回奔跑。

参数方面为两个参数:

  • Float类型的moveSpeed;
  • Trigger类型的Jump;

连线直接的转换条件为:moveSpeed大于0.1进入走路,走路moveSpeed大于0.9进入奔跑小于0.1返回站立,奔跑moveSpeed小于0.9返回走路,Jump被触发则进入跳跃;

为人物添加Animator组件并绑定上面的Animator Controller文件,同时绑定下面的脚本:

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class TestAnim : MonoBehaviour
 5 {
 6     //将名称转换为哈希值可以提高索引的速度
 7     private int moveSpeed = Animator.StringToHash("moveSpeed");
 8     private int jump = Animator.StringToHash("Jump");
 9     private int runState = Animator.StringToHash("Base Layer.Run");
10 
11     private Animator _animator;
12 
13     void Start()
14     {
15         _animator = this.GetComponent();
16     }
17     
18     void Update()
19     {
20         float speed = Input.GetAxis("Vertical");
21         _animator.SetFloat(moveSpeed, speed);
22 
23         //获取动画的当前状态
24         AnimatorStateInfo info = _animator.GetCurrentAnimatorStateInfo(0);
25         //跳跃除了要按下空格键外, 还需要处于奔跑状态才行, 否则按下空格键时就会标记 Jump, 一进入奔跑就马上跳跃
26         if(Input.GetKeyDown(KeyCode.Space) && info.fullPathHash == runState)
27         {
28             _animator.SetTrigger(jump);
29         }
30     }
31 }
复制代码

State Machine Behavior

在Unity5.0中,我们可以给Animator Controller中的每个State添加脚本了,类似于专门用于GameObject的MonoBehavior,State可以添加State Machine Behavior,我们打开任意的Animator Controller,就可以在Inspector窗口发现添加脚本的按钮:

Unity之Mecanim动画系统笔记_第16张图片

除了State外,我们还可以在层上添加,层可以看做包含了整个State的一个大State:

Unity之Mecanim动画系统笔记_第17张图片

我们可以添加一个脚本看看,下面是我添加了注释的State Machine Behavior脚本的标准形式:

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class StateScript : StateMachineBehaviour
 5 {
 6     // OnStateEnter is called before OnStateEnter is called on any state inside this state machine
 7     override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
 8     {
 9         //进入当前状态时会被调用
10     }
11 
12     // OnStateUpdate is called before OnStateUpdate is called on any state inside this state machine
13     override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
14     {
15         //进入当前状态的每帧会被调用
16     }
17 
18     // OnStateExit is called before OnStateExit is called on any state inside this state machine
19     override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
20     {
21         //退出当前状态时会被调用
22     }
23 
24     // OnStateMove is called before OnStateMove is called on any state inside this state machine
25     override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
26     {
27     }
28 
29     // OnStateIK is called before OnStateIK is called on any state inside this state machine
30     override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
31     {
32     }
33 
34     // OnStateMachineEnter is called when entering a statemachine via its Entry Node
35     override public void OnStateMachineEnter(Animator animator, int stateMachinePathHash)
36     {
37     }
38 
39     // OnStateMachineExit is called when exiting a statemachine via its Exit Node
40     override public void OnStateMachineExit(Animator animator, int stateMachinePathHash)
41     {
42     }
43 }
复制代码

通过使用State Machine Behaviour我们可以更加方便的在特定的时间点触发一些我们需要的事件,但是需要注意的是,我们一般给State Machine Behaviour赋值一些场景的对象不是直接在Inspector面板里拖拽而是通过Animator的GetBehavior方法获取到指定的State Machine Behaviour的实例后通过脚本进行赋值的。

什么是IK?

IK(Inverse Kinematics)即反向动力学,即可以使用场景中的各种物体来控制和影响角色身体部位的运动,一般来说骨骼动画都是传统的从父节点到子节点的带动方式(即正向动力学),而IK则倒过来,由骨骼子节点带动骨骼父节点,具体情况比如人物走路踩到了石头就需要由脚的子节点来带动全身骨骼做出踩到石头的响应。

IK可以使人物和场景更加贴合,从而达到更加真实的游戏效果,如果大家玩过《波斯王子》或《刺客信条》系列,应该对主角的攀爬和飞檐走壁的能力印象深刻,这些都是应用了IK,使动画贴合到具体的场景中进行的表现。

Unity3D本身已经带有了IK的功能(http://docs.unity3d.com/Manual/InverseKinematics.html),我们接下来就对IK进行一下简单的学习和使用。

FinalIK

该插件是对Unity本身的IK的优化和增强,可以模拟出更加真实的效果,有兴趣可以看一看。

https://www.assetstore.unity3d.com/cn/#!/content/14290

实例

我们直接上手一个小例子来看看Unity3D中的IK应该如何使用,我们会创建一个场景,使人物的头部始终面向一个点,同时创建四个点控制人物的手和腿的移动。

我们在场景中添加一个人物和5个小球,如下:

Unity之Mecanim动画系统笔记_第18张图片

根据Unity官方的文档给出的资料来看,首先必须在需要使用IK动画的Animator的层上开启“IK Pass”,如下图所示:

Unity之Mecanim动画系统笔记_第19张图片

只有开启了这个选项,系统才会调用IK相应的方法。

下面我们为这个人物添加一个脚本,如下:

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class TestIK : MonoBehaviour
 5 {
 6     public Transform lookAtTarget;
 7 
 8     public Transform leftHandTarget;
 9     public Transform rightHandTarget;
10     public Transform leftFootTarget;
11     public Transform rightFootTarget;
12 
13     private Animator _animator;
14 
15     void Start()
16     {
17         _animator = this.GetComponent();
18     }
19     
20     void OnAnimatorIK(int layerIndex)
21     {
22         if(_animator != null)
23         {
24             //仅仅是头部跟着变动
25             _animator.SetLookAtWeight(1);
26             //身体也会跟着转, 弧度变动更大
27             //_animator.SetLookAtWeight(1, 1, 1, 1);
28             if(lookAtTarget != null)
29             {
30                 _animator.SetLookAtPosition(lookAtTarget.position);
31             }
32 
33             _animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1);
34             _animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1);
35             if(leftHandTarget != null)
36             {
37                 _animator.SetIKPosition(AvatarIKGoal.LeftHand, leftHandTarget.position);
38                 _animator.SetIKRotation(AvatarIKGoal.LeftHand, leftHandTarget.rotation);
39             }
40 
41             _animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);
42             _animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);
43             if(leftHandTarget != null)
44             {
45                 _animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandTarget.position);
46                 _animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandTarget.rotation);
47             }
48             
49             _animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1);
50             _animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1);
51             if(leftHandTarget != null)
52             {
53                 _animator.SetIKPosition(AvatarIKGoal.LeftFoot, leftFootTarget.position);
54                 _animator.SetIKRotation(AvatarIKGoal.LeftFoot, leftFootTarget.rotation);
55             }
56             
57             _animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, 1);
58             _animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, 1);
59             if(leftHandTarget != null)
60             {
61                 _animator.SetIKPosition(AvatarIKGoal.RightFoot, rightFootTarget.position);
62                 _animator.SetIKRotation(AvatarIKGoal.RightFoot, rightFootTarget.rotation);
63             }
64         }
65     }
66 }
复制代码

需要注意的是,控制IK的脚本必须添加到OnAnimatorIK方法中才会生效,下面看下效果图:

Unity之Mecanim动画系统笔记_第20张图片

解决什么问题?

动画分层可以用来解决什么样的问题呢?试想一下如果你要开发一款第三人称的射击游戏,那么肯定是希望身体的动画分为上下两部分,上方根据瞄准的位置和是否射击进行动画播放,下方根据移动播放动画。最好的例子就是Unity4.x自带的示例AngryBots了。

Avatar Mask

下面我们就使用Avatar Mask来实现人物在奔跑中招手的效果。

我们先添加一个人物到场景,同时为其添加一个Animator Controller并设定好跳转条件,如下:

Unity之Mecanim动画系统笔记_第21张图片

接下来我们添加下面的脚本来控制动画的播放,对了为了不让角色因为播放动画而移动,记得把“Apply Root Motion”取消,方便我们查看动画播放的效果。

添加的脚本如下:

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class MaskTest : MonoBehaviour
 5 {
 6     private Animator _animator;
 7 
 8     void Start()
 9     {
10         _animator = this.GetComponent();
11     }
12     
13     void Update()
14     {
15         if(Input.GetKeyDown(KeyCode.R))
16         {
17             _animator.SetBool("run", true);
18         }
19         if(Input.GetKeyUp(KeyCode.R))
20         {
21             _animator.SetBool("run", false);
22         }
23 
24         AnimatorStateInfo state = _animator.GetCurrentAnimatorStateInfo(0);
25         if(state.shortNameHash == Animator.StringToHash("Run") && Input.GetKeyDown(KeyCode.J))
26         {
27             _animator.SetTrigger("jump");
28         }
29     }
30 }
复制代码

接下来我们希望按下空格时人物播放招手动画,但是跑动跳跃的动画不能停止播放。

我们需要添加一个新的Layer来管理招手动画的播放:

Unity之Mecanim动画系统笔记_第22张图片

同时我们要配置好招手动画,这里的Idle可以不添加任何动画,仅仅表示空就可以了,同时还添加了一个Trigger“wave”表示进入招手动画的条件。

接下来我们设置一下该层,将其权重设置为1:

Unity之Mecanim动画系统笔记_第23张图片

好了,我们修改一下代码试试看吧:

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class MaskTest : MonoBehaviour
 5 {
 6     private Animator _animator;
 7 
 8     void Start()
 9     {
10         _animator = this.GetComponent();
11     }
12     
13     void Update()
14     {
15         if(Input.GetKeyDown(KeyCode.R))
16         {
17             _animator.SetBool("run", true);
18         }
19         if(Input.GetKeyUp(KeyCode.R))
20         {
21             _animator.SetBool("run", false);
22         }
23 
24         AnimatorStateInfo state = _animator.GetCurrentAnimatorStateInfo(0);
25         if(state.shortNameHash == Animator.StringToHash("Run") && Input.GetKeyDown(KeyCode.J))
26         {
27             _animator.SetTrigger("jump");
28         }
29 
30         if(Input.GetKeyDown(KeyCode.Space))
31         {
32             _animator.SetTrigger("wave");
33         }
34     }
35 }
复制代码

运行一下,发现当播放招手动画时其它动画都会停止,该怎么办呢?

答案是我们需要创建一个Avatar Mask来表示我们只希望播放动画的部分,即手部动画播放,其它部分不播放,在Project窗口右击创建一个Avatar Mask文件:

Unity之Mecanim动画系统笔记_第24张图片

将右手部分以外的区别关闭即可:

Unity之Mecanim动画系统笔记_第25张图片

这里说一个Transform这个选项,该选项是控制每个骨骼是否参与动画使用的,如果我们的人物有翅膀和尾巴之类的东西就要使用它了。

最后一步我们将这个Avatar Mask拖入Animator Controller的Wave层中即可:

Unity之Mecanim动画系统笔记_第26张图片

再次运行下游戏,我们需要的效果是不是已经出来了呢。

设置解说

下面我们来详解一下Layer面板的设置:

Unity之Mecanim动画系统笔记_第27张图片

Unity之Mecanim动画系统笔记_第28张图片

Weight

动画层的权重,默认的Base Layer必须为1。如果设置为0则当前层的动画不会播放,1则会播放,0-1之间会采用类似融合的情况来播放动画,比如之前的招手的例子,如果设置为0.5则招手动画播放时手部只会抬到脖子附近。

Mask

动画遮罩,上方已经详解就不赘述。

Blending

动画混合方式:

  • Override:覆盖,表示当前层的动画会覆盖掉其它层的动画,比如招手播放时右手就不能播放其它的动画了;
  • Additive:添加,表示当前层的动画的量添加到其它层的动画,比如招手播放时,手部奔跑或站立的甩动也会保留;

Sync

开启了该功能后会多出一些选项,如下:

Unity之Mecanim动画系统笔记_第29张图片

我们可以将该功能看做复制的功能。

Source Layer:指定当前层是哪个层的副本,设定后当前层的状态会和指定层完全一致或完全同步,但是我们可以修改某一个状态的动画。

该功能提供的效果就是两个状态一致的层可以做出一些不同的调整。

Timing:当前层和Souurce层同一个状态使用的动画时间长度不一致时,不勾选复制的层按Source层的时间播放(效果就是复制层动画可能会变快或变慢,Source层动画播放速度不变),勾选则Source层按复制层的时间播放(效果就是Source层动画可能会变快或变慢,复制层动画播放速度不变)。

IK Pass

表示启动IK动画,上一篇文章已经详解就不赘述。

认识Blend Tree

我们在Animator Controller中除了可以创建一个State外还可以创建一个Blend Tree,如下:

Unity之Mecanim动画系统笔记_第30张图片

那么我们看下新创建的Blend Tree和State有什么区别:

Unity之Mecanim动画系统笔记_第31张图片

唯一的区别就是Montion指向的类型变成了Blend Tree类型,那么一个Blend Tree其实也就是一个状态,和状态不同的地方就是一个状态只能设定一个动画,而一个Blend Tree则可以设定为多个动画的混合。

混合树是Mecanim动画系统中比较复杂的一个内容,且其分为多个维度,下面我们逐个的来进行学习。

一维混合树

我们以官方的例子使用一维混合树来实现下面的效果:我们人物的跑动分为3个动画,分别是向前跑、向左跑和向右跑,其中向左跑和向右跑人物都会有一定的倾斜,这样更加符合现实的情况,那么我们在状态机中跑动只有一个状态,所以我们的跑动需要设置为混合树来混合这3个动画。

首先我们需要创建一个新的场景,拖入我们的人物模型,然后创建一个Animator Controller并对其进行配置:

Unity之Mecanim动画系统笔记_第32张图片

注意我们的Run是Blend Tree而不是State,双击Run就可以进入混合树的编辑界面。

右击我们的混合树添加3个Motion,如下:

同时我们设定好3个方向的跑动动画:

Unity之Mecanim动画系统笔记_第33张图片

我们还需要设定一个名为Direction的Float类型的参数来控制这个混合树:

Unity之Mecanim动画系统笔记_第34张图片

接下来我们取消Automate Thresholds的选项,并按下图进行选择,系统会为我们配置好阀值:

Unity之Mecanim动画系统笔记_第35张图片

现在我们点击预览框查看动画播放时就可以通过拖拽小红线来看不同的变化了,我们的可使用的角度范围为-130到130之间。

到现在我们的动画控制器就配置好了。

脚本

下面我们使用脚本来控制一下人物,我们给人物添加下面的脚本即可:

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class TestBlendTree : MonoBehaviour
 5 {
 6     public float DirectionDampTime = 30.0f;
 7 
 8     private Animator _animator;
 9 
10     void Start()
11     {
12         _animator = this.GetComponent();
13     }
14     
15     void Update()
16     {
17         if(Input.GetKeyDown(KeyCode.W))
18         {
19             _animator.SetBool("run", true);
20         }
21         if(Input.GetKeyUp(KeyCode.W))
22         {
23             _animator.SetBool("run", false);
24         }
25 
26         AnimatorStateInfo state = _animator.GetCurrentAnimatorStateInfo(0);
27         //奔跑状态下才允许转弯
28         if(state.shortNameHash == Animator.StringToHash("Run"))
29         {
30             //指定人物转弯通过控制混合数的参数即可
31             float h = Input.GetAxis("Horizontal") * 130.0f;
32             //DirectionDampTime 指示了每秒可以到达的最大值
33             //deltaTime 表示当前帧的时间
34             _animator.SetFloat("Direction", h, DirectionDampTime, Time.deltaTime);
35         }
36         else
37         {
38             //重置一下参数
39             _animator.SetFloat("Direction", 0);
40         }
41     }
42 }
复制代码

为了让摄像机跟随人物,我们直接添加官方给出的这个脚本到摄像机上即可:

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class ThirdPersonCamera : MonoBehaviour
 5 {
 6     public float distanceAway;            // distance from the back of the craft
 7     public float distanceUp;            // distance above the craft
 8     public float smooth;                // how smooth the camera movement is
 9     
10     private GameObject hovercraft;        // to store the hovercraft
11     private Vector3 targetPosition;        // the position the camera is trying to be in
12     
13     Transform follow;
14     
15     void Start(){
16         follow = GameObject.FindWithTag ("Player").transform;    
17     }
18     
19     void LateUpdate ()
20     {
21         // setting the target position to be the correct offset from the hovercraft
22         targetPosition = follow.position + Vector3.up * distanceUp - follow.forward * distanceAway;
23         
24         // making a smooth transition between it's current position and the position it wants to be in
25         transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * smooth);
26         
27         // make sure the camera is looking the right way!
28         transform.LookAt(follow);
29     }
30 }
复制代码

记得将人物的Tag设置为Player,同时脚本也要设置一下:

动画方面的一点要求

每个混合树的动画有一些要注意的地方:

  1. 动画长度需要一致;
  2. 动画的起始姿势需要一致;

二维混合树

同1维混合树,不过二维混合树已经作为一个平面来处理,同时需要两个参数来进行控制。对于更复杂的动画融合可以使用该模式,这里就不深入学习了。

我们可以将两个1维混合树合并为一个2维混合树来控制。

多维混合树

多维混合树在Unity5时添加,其配置更加复杂,一般使用在脸部表情的动画融合上。

资源加载是必备的知识点,这里就说说Mecanim动画的资源如何打包及加载。

注意,Unity4.x和Unity5.x的AssetBundle打包策略不一样,本笔记是基于Unity4.x的AssetBundle进行打包的。

我们一般使用FBX类型的模型及动画文件,而动画文件的储存一般有两种情况,一是所有的动画和模型都一起存放到一个文件中,还有一种情况是模型单独一个文件而动画单独一个文件。这里我们就两种情况都看一下。

使用的资源是Unity3D自带的以及从一本教材中取出的两种类型的动画资源,同时需要对其动画创建对应的Animator Controller。

模型动画都存放在一个文件中的情况

一个FBX文件保存了模型、骨骼和动画,如下图:

下面是配置的Animator Controller:

Unity之Mecanim动画系统笔记_第36张图片

需要注意的是,官方并没有提供为Animator设置Animator Controller的接口,所以我们必须将配置好的GameObject制作为一个预制件进行加载。

Resources加载

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class AllInOneResourcesLoad : MonoBehaviour
 5 {
 6     private Animator _animator;
 7 
 8     void Start()
 9     {
10         GameObject go = Resources.Load("AllInOne/ConstructorPrefab");
11 
12         GameObject man = Instantiate(go) as GameObject;
13         _animator = man.GetComponent();
14     }
15     
16     void OnGUI()
17     {
18         if(GUI.Button(new Rect(0, 0, 100, 30), "idle"))
19         {
20             _animator.SetBool("walk", false);
21             _animator.SetBool("run", false);
22         }
23         if(GUI.Button(new Rect(100, 0, 100, 30), "walk"))
24         {
25             _animator.SetBool("walk", true);
26             _animator.SetBool("run", false);
27         }
28         if(GUI.Button(new Rect(200, 0, 100, 30), "run"))
29         {
30             _animator.SetBool("walk", false);
31             _animator.SetBool("run", true);
32         }
33         if(GUI.Button(new Rect(300, 0, 100, 30), "jump"))
34         {
35             _animator.SetTrigger("jump");
36         }
37     }
38 }
复制代码

AssetBundle加载

打包

复制代码
 1 using UnityEngine;
 2 using UnityEditor;
 3 
 4 public class CreateAllInOneAB
 5 {
 6     [MenuItem("Tool/CreateAllInOneAB")]
 7     private static void Create()
 8     {
 9         BuildPipeline.BuildAssetBundle(null, new[]
10             {
11                 AssetDatabase.LoadAssetAtPath("Assets/Resources/AllInOne/ConstructorPrefab.prefab", typeof(GameObject))
12             },
13             Application.streamingAssetsPath + "/AllInOne.assetbundle",
14             BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.UncompressedAssetBundle,
15             BuildTarget.StandaloneWindows64);
16     }
17 }
复制代码

加载

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class AllInOneAssetBundleLoad : MonoBehaviour
 5 {
 6     private Animator _animator;
 7 
 8     void Start()
 9     {
10         AssetBundle assetBundle = AssetBundle.CreateFromFile(Application.streamingAssetsPath + "/AllInOne.assetbundle");
11 
12         GameObject go = assetBundle.Load("ConstructorPrefab", typeof(GameObject)) as GameObject;
13 
14         GameObject man = Instantiate(go) as GameObject;
15         _animator = man.GetComponent();
16     }
17 
18     void OnGUI()
19     {
20         if(GUI.Button(new Rect(0, 0, 100, 30), "idle"))
21         {
22             _animator.SetBool("walk", false);
23             _animator.SetBool("run", false);
24         }
25         if (GUI.Button(new Rect(100, 0, 100, 30), "walk"))
26         {
27             _animator.SetBool("walk", true);
28             _animator.SetBool("run", false);
29         }
30         if (GUI.Button(new Rect(200, 0, 100, 30), "run"))
31         {
32             _animator.SetBool("walk", false);
33             _animator.SetBool("run", true);
34         }
35         if (GUI.Button(new Rect(300, 0, 100, 30), "jump"))
36         {
37             _animator.SetTrigger("jump");
38         }
39     }
40 }
复制代码

模型动画分开存放的情况

还有一种情况是模型和动画是分为多个FBX文件存放的,比如下面是模型文件:

Unity之Mecanim动画系统笔记_第37张图片

虽然有一个Take 001的动画,但是实际上我们并不使用该动画,而是使用下面仅保存了动画的FBX文件:

Unity之Mecanim动画系统笔记_第38张图片

下面是配置的Animator Controller:

Unity之Mecanim动画系统笔记_第39张图片

除了没有提供设置Animator Controller的接口,也无法在运行时对动画剪辑进行增加删除的操作,所以我们一般打包时就收集所有的依赖项一起打包,归根结底还是只需要一个制作好的预制件即可。

从这个角度看,其实是否将动画进行拆分最终的使用方式都是一样的。

Resources加载

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class ResourcesLoad : MonoBehaviour
 5 {
 6     private Animator _animator;
 7 
 8     void Start()
 9     {
10         GameObject go = Resources.Load("ZombieNurse/ZombieNursePrefab");
11 
12         GameObject man = Instantiate(go) as GameObject;
13         _animator = man.GetComponent();
14     }
15 
16     void OnGUI()
17     {
18         if(GUI.Button(new Rect(0, 0, 100, 30), "idle"))
19         {
20             _animator.SetBool("run", false);
21         }
22         if(GUI.Button(new Rect(100, 0, 100, 30), "run"))
23         {
24             _animator.SetBool("run", true);
25         }
26         if(GUI.Button(new Rect(200, 0, 100, 30), "attack"))
27         {
28             _animator.SetTrigger("attack");
29         }
30         if(GUI.Button(new Rect(300, 0, 100, 30), "dead"))
31         {
32             _animator.SetTrigger("dead");
33         }
34     }
35 }
复制代码

AssetBundle加载

打包

复制代码
 1 using UnityEditor;
 2 using UnityEngine;
 3 
 4 public class CreateAB
 5 {
 6     [MenuItem("Tool/CreateAB")]
 7     private static void Create()
 8     {
 9         BuildPipeline.BuildAssetBundle(null, new[]
10             {
11                 AssetDatabase.LoadAssetAtPath("Assets/Resources/ZombieNurse/ZombieNursePrefab.prefab", typeof(GameObject))
12             },
13             Application.streamingAssetsPath + "/AB.assetbundle",
14             BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.UncompressedAssetBundle,
15             BuildTarget.StandaloneWindows64);
16     }
17 }
复制代码

加载

复制代码
 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class AssetBundleLoad : MonoBehaviour
 5 {
 6     private Animator _animator;
 7 
 8     void Start()
 9     {
10         AssetBundle assetBundle = AssetBundle.CreateFromFile(Application.streamingAssetsPath + "/AB.assetbundle");
11 
12         GameObject go = assetBundle.Load("ZombieNursePrefab", typeof(GameObject)) as GameObject;
13 
14         GameObject man = Instantiate(go) as GameObject;
15         _animator = man.GetComponent();
16     }
17 
18     void OnGUI()
19     {
20         if(GUI.Button(new Rect(0, 0, 100, 30), "idle"))
21         {
22             _animator.SetBool("run", false);
23         }
24         if(GUI.Button(new Rect(100, 0, 100, 30), "run"))
25         {
26             _animator.SetBool("run", true);
27         }
28         if(GUI.Button(new Rect(200, 0, 100, 30), "attack"))
29         {
30             _animator.SetTrigger("attack");
31         }
32         if(GUI.Button(new Rect(300, 0, 100, 30), "dead"))
33         {
34             _animator.SetTrigger("dead");
35         }
36     }
37 }
复制代码

 

动作游戏

还记得读书的时候熬夜打《波斯王子》的时光,我们的王子通过跳跃穿过墙壁的小洞、在高层建筑上进行攀爬和跳跃,还有在操作失误掉下高楼和触发必死机关后使用时之沙的时光倒流功能回归死亡之前的开挂技能。

而现在这些功能使用Unity3D的Mecanim都可以非常简单的实现了!

这篇笔记主要记录下Mecanim的3个高级功能:

  1. Animation Curve:可以根据动画的运行时间自定义一条曲线,在播放动画时可以获得这条曲线对应的值,可以实现跳跃穿过小洞的功能;
  2. Target Match:匹配一个预先定义好的点进行动画播放,可以实现人物攀爬的功能;
  3. Animation Record:可以对动画进行录制,然后可以播放录制好的动画,可以实现时之沙的时光倒流功能;

可以说Unity3D现在已经具备了开发一款媲美国际大厂的动作游戏的能力了。

Animation Curve

这里我们直接使用官网给出的示例,打开场景Scale Capsule:

运行游戏,我们发现此时人物是不能通过跳跃穿过墙上的小孔的:

Unity之Mecanim动画系统笔记_第40张图片

原因很简单,人物的胶囊碰撞体比小孔要大。当我们勾选了右上角的Apply Scale之后,再跳跃试试看:

Unity之Mecanim动画系统笔记_第41张图片

此时我们发现当人物跳跃时,胶囊碰撞体就会进行缩小,而我们的人物就可以钻过这个小洞了,在跳跃之后胶囊体会进行恢复。

我们找到动画文件Dive,就可以看到其设置了两个Curve:

Unity之Mecanim动画系统笔记_第42张图片

而其中的ScaleOffsetCapsule就是用来控制胶囊体缩放的曲线,我们可以发现其呈现了1->0->1的一个区间。

然后我们看看参数的设置:

参数必须是Float类型同时名称必须和动画文件中的Curve名称一致,这样当我们播放跳跃动画时该参数就会根据预设好的曲线来取值了,注意该参数后方的输入框已经禁用了,不能输入。

最后我们看下脚本ScaleCapsule的实现:

通过每帧获取参数的值来实现跳跃的碰撞体变小的功能。

Target Match

核心是使用到了Animator的MatchTarget方法,帮助如下:

http://docs.unity3d.com/ScriptReference/Animator.MatchTarget.html

这里我们还是直接使用官网给出的示例,打开场景Target Match:

Unity之Mecanim动画系统笔记_第43张图片

我们发现在箱子上方已经做好了一个目标点,我们的人物的右手会匹配到这个点进行动画播放。

需要注意的是,使用MatchTarget的动画必须位于Base Layer层。Match Target会将我们人物的一个部位的位置和目标点的位置进行插值来移动我们人物,结果就是可以通过一个指定高度的攀爬动画来匹配任意高度的攀爬。

我们看下调用的脚本:

复制代码
 1 // Update is called once per frame
 2 void Update () 
 3 {
 4 
 5     if (animator)
 6     {
 7         AnimatorStateInfo state = animator.GetCurrentAnimatorStateInfo(0);
 8 
 9         if (Input.GetButton("Fire1")) animator.SetBool("Jump", true);
10 
11         if (state.IsName("Base Layer.JumpUp") || state.IsName("Base Layer.FullJump")) 
12         {
13             animator.SetBool("Jump", false);
14                             
15             animator.MatchTarget(RightHand.position, RightHand.rotation, AvatarTarget.RightHand, new MatchTargetWeightMask(new Vector3(1, 1, 1), 0), animator.GetFloat("MatchStart"), animator.GetFloat("MatchEnd"));
16             hasJumped = true;
17         }
18 
19         if (hasJumped && state.normalizedTime > 1.2)
20         {
21             hasJumped = false;
22             Application.LoadLevel(0);
23         }
24     }
25 }
复制代码

Animation Record

Unity3D的Animator类为我们提供了4个方法用于录制和播放动画:

  • StartRecording:开始录制动画。http://docs.unity3d.com/ScriptReference/Animator.StartRecording.html
  • StopRecording:结束录制动画。http://docs.unity3d.com/ScriptReference/Animator.StopRecording.html
  • StartPlayback:开始播放录制的动画。http://docs.unity3d.com/ScriptReference/Animator.StartPlayback.html
  • StopPlayback:结束播放录制的动画。http://docs.unity3d.com/ScriptReference/Animator.StopPlayback.html

同时我们也可以通过访问Animator提供的一些属性来了解动画录制的一些信息:

  • playbackTime:播放时间。http://docs.unity3d.com/ScriptReference/Animator-playbackTime.html
  • recorderStartTime:开始录制的时间。http://docs.unity3d.com/ScriptReference/Animator-recorderStartTime.html
  • recorderStopTime:结束录制的时间。http://docs.unity3d.com/ScriptReference/Animator-recorderStopTime.html

使用比较简单就不做示例展示了,需要注意的时,回放录制的动画时人物会回到开始录制的坐标点,而不是在当前坐标点开始播放。

你可能感兴趣的:(Unity)