Unity动画控制器的原理就是状态机。
传统的状态机需要在代码里写一个很大的Switch…Case来处理状态,Unity以可视化的形式简化了。而且还提供了子状态机和动画混合功能。由于状态机的原理是同一时刻只能有一个状态,所以Unity还提供了层的概念来将动画分成两个层来同时编辑
系统状态 动画控制器默认提供Any State、Exit、Entry三种状态,我们无法删除默认的三种状态。除了Exit状态以外,自定义的动画状态无法连接到Any State和Entry状态,只能反向连接。
首先是entry状态,他表示当前控制器的初始状态,右击该状态,选择Make Transition命令,即可连接新的状态。状态机会按照连线的状态一次切换动画。橘黄色的状态表示默认状态,如果想切换默认状态,选择另一个状态,然后右键——Set as Layer Default State
接着是Any State状态,比如角色死亡一类的,需要从现有状态切换到另一个动画。可以从Any State状态连线到立刻播放的状态,等它的状态处理完后,再连线回到默认状态,继续原有逻辑
最后是Exit状态,状态机可以创建子状态。如果子状态需要回到父状态Base Layer。就需要把子状态连线到Exit状态
切换条件 状态机互相切换需要条件,一个状态机一共支持4种条件:Float、Int、Bool和Trigger。Trigger就像Bool一样,设置true后需要立即设置False。
单击两个状态机之间的连线,就可以在inspector面板下方就可以添加满足的条件。
状态机在同一时刻只能执行一个状态 ,即使两个状态的条件都满足了,也只能进入其中一个。Solo复选框表示即使当前即使别的条件达成了,也只能进入选中Solo状态的动画。Mute表示即使当前条件达成了,也不能进入选中Mute的状态。
Has Exit Time复选框表示不同动画切换时是否启动动画过度,可以调节蓝色半透明区域来设置过渡时间
设置动画的代码
1 2 3 4 5 6 Animator animator = GetComponent<Animator>(); int intID = Animator.StringToHash("New Int" );animator.SetFloat("New Float" ,1f ); animator.SetInteger(intID,1 ); animator.SetBool("New Bool" ,true ); animator.SetTrigger("New Trigger" );
状态机脚本 我们是可以给每个状态添加自己的脚本的。我们可以给每个状态添加脚本来监听一些状态事件,比如状态开启、状态更新和状态退出等。
选中一个状态,然后单击Add Behaviour即可添加脚本。此外,也可以序列化常用数据,如int、String、bool和object等,然后在面板中输入参数即可。例如进入某个状态,播放一个特定的音乐或者做一些特殊的逻辑等。
点击生成的StateMachineBehaviour
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 using System.Collections;using System.Collections.Generic;using UnityEngine;public class NewMachineBehaviour : StateMachineBehaviour { override public void OnStateEnter (Animator animator, AnimatorStateInfo stateInfo, int layerIndex ) { } override public void OnStateUpdate (Animator animator, AnimatorStateInfo stateInfo, int layerIndex ) { } override public void OnStateExit (Animator animator, AnimatorStateInfo stateInfo, int layerIndex ) { } override public void OnStateMove (Animator animator, AnimatorStateInfo stateInfo, int layerIndex ) { } override public void OnStateIK (Animator animator, AnimatorStateInfo stateInfo, int layerIndex ) { } }
IK动画 Inverse Kinematics,子骨骼节点带动父骨骼节点运动
如果想要控制人物IK,首先需要在具有骨骼的角色的——Animator面板上的——Layer(Base Layer)点击小齿轮——勾选IK Pass,这样的话我们下面的脚本的OnAnimatorIK
才会起效
我们在角色的左手右手分别绑定一个球体,通过移动球体来控制IK影响手部位的动画
新建MoveHandIK
脚本,在Playing模式下,就可以通过移动圆球来改变胳臂位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 using System.Collections;using System.Collections.Generic;using UnityEngine;public class MoveHandIK : MonoBehaviour { public Animator animator; public Transform rightHandObj; public Transform leftHandObj; private void OnAnimatorIK (int layerIndex ) { if (animator) { animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1f ); animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1f ); animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1f ); animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1f ); if (rightHandObj != null ) { animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandObj.position); animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandObj.rotation); } if (leftHandObj != null ) { animator.SetIKPosition(AvatarIKGoal.RightHand, leftHandObj.position); animator.SetIKRotation(AvatarIKGoal.RightHand, leftHandObj.rotation); } } } }
Root Motion 3维角色的某些动画本身可能就是带位移的,比如挥砍的时候前进几步,为了能在Unity中也能更新Transform信息,需要打开Apply Root Motion复选框。
在Project窗口选择带动画的模型,然后在Inspector面板勾选Bake Into Pose复选框,这表示动画播放完毕后才同步位移信息,不选中表示位移随着动画同时改变
然后我们在脚本中就可以监听Animator的移动更新事件。注意,位移移动事件是在Update()方法之后执行的。在OnAnimatorMove()
中控制位移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 using UnityEngine;public class CharactorMove : MonoBehaviour { public Animator animator; private void OnAnimatorMove () { if (animator) { Vector3 newPosition = transform.position; newPosition.z += 1f * Time.deltaTime; transform.position = newPosition; } } }
如果不挂载此脚本,人物会根据原动画的运动方式叠加位移
Avatar Mask Avatar Mask可以限制某些骨骼不播放动画。在Project视图中选择Create→Avatar Mask命令,可以创建它。
如果是人形动画,那么可以直接设置人形遮罩骨骼,其中红色的部分表示禁止这部分骨骼播放动作
如果是使用Generic动画,需要单独选中需要禁止播放动画的骨骼节点
想要应用Mask,在Animator面板中的Base Layer中设置(下图为IK Pass的图)
层 层是用来做动画融合的,同一套骨骼上的两个动画同时播放,例如FPS类游戏或者篮球类游戏。下半身跑动的过程中,上半身还可以旋转投篮等。
为了让上下部分的骨骼相互不影响,可以设置他们的Avatar Mask。
在Animator窗口中,点击Layer面板右上角的加号,即可添加层。可以让Base Layer来处理整体逻辑,而让New Layer专门用来做动画融合,Weight可以设置融合的权重,Mask就是遮罩文件了,Blending设置的Override表示直接覆盖掉其它层的动画。
Blend Tree Blend Tree用来做动画混合。动画混合和动画融合是不同的概念。
动画混合是指两个动画切换的时候,为了避免太过生硬而混合在一起的过程,比较经典的例子就是控制角色四向跑动时的切换。
在Layer中单击鼠标右键,从弹出的快捷菜单中选择Create State——From New Blend Tree命令即可创建它。
创建完成后,双击打开,点击生成的Blend Tree主干,在Inspector面板中,我们可以自定义BlendType,添加需要混合的motion
motion里面就是animation clip,点击添加进去即可,我们关闭Automate Thresholds的勾选,就可以自己编辑不同动画片段的Threshold参数了。
通过下面的代码,我们就可以在运行时改变自己设置的TreeValue参数了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using System.Collections;using System.Collections.Generic;using UnityEngine;public class SetTreeValue : MonoBehaviour { public Animator animator; private int blendTreeID; private void Start () { blendTreeID = Animator.StringToHash("TreeValue" ); } private void Update () { animator.SetFloat(blendTreeID, Input.GetAxis("Horizontal" ) * 96.0f ); } }
非运行播放动画 通常,在做编辑器的时候,需要在非运行模式下也能播放动画,我们使用下面的代码来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 using System.Collections;using System.Collections.Generic;using UnityEngine;#if UNITY_EDITOR using UnityEditor;using UnityEditor.Animations;using System.Linq;#endif [RequireComponent(typeof(Animator)) ] public class EditorPreviewAnim : MonoBehaviour { }#if UNITY_EDITOR [CustomEditor(typeof(EditorPreviewAnim)) ] public class EditorPreviewAnimEditor : Editor { private AnimationClip[] m_Clips = null ; private EditorPreviewAnim m_Script = null ; private void OnEnable () { m_Script = (target as EditorPreviewAnim); Animator animator = m_Script.GetComponent<Animator>(); AnimatorController controller = (AnimatorController)animator.runtimeAnimatorController; m_Clips = controller.animationClips; } private int m_SelectIndex = 0 ; private float m_SliderValue = 0 ; public override void OnInspectorGUI () { base .OnInspectorGUI(); EditorGUI.BeginChangeCheck(); m_SelectIndex = EditorGUILayout.Popup("AnimationClip" , m_SelectIndex, m_Clips.Select(pkg => pkg.name).ToArray()); m_SliderValue = EditorGUILayout.Slider("Schedule" , m_SliderValue, 0f , 1f ); if (EditorGUI.EndChangeCheck()) { AnimationClip clip = m_Clips[m_SelectIndex]; float time = clip.length * m_SliderValue; clip.SampleAnimation(m_Script.gameObject, time); } } } #endif
Animator Override Controller 前面我们介绍了Animator Controller可以编辑动画之间的切换状态。在游戏中,很多模型动画的切换事件的逻辑 可能都是一样的,比如游戏中的很多怪物,它们之间的区别可能就是动画文件不一样,总不能每一个怪物都编辑一套相同的Animator Controller控制行为吧,此时就需要使用Animator Override Controller了
我们首先直接在Project窗口创建Animator Override Controller
然后在Inspector面板中,选定切换逻辑一致的Animator,将需要的Animation clip放进去即可
RuntimeAnimatorController RuntimeAnimatorController
是用来处理Animator Controller动态更新的。
如果我们想在运行时挂载Controller,可以先将Controller放在Resources文件夹里
然后将此脚本挂载在需要运行时添加Animator的人物身上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 using System.Collections;using System.Collections.Generic;using UnityEngine;[RequireComponent(typeof(Animator)) ] public class Script_07_07 : MonoBehaviour { public Animator animator; void OnGUI () { if (GUILayout.Button ("<size=50>读取</size>" )) { RuntimeAnimatorController controller = Resources.Load<RuntimeAnimatorController> ("New Animator Controller" ); animator.runtimeAnimatorController = controller; } if (GUILayout.Button ("<size=50>删除</size>" )) { animator.runtimeAnimatorController = null ; } } }
运行就可以点击按钮挂载Animator