代码创建动画状态机
最近开始了新的项目,主要负责小怪部分的功能实现。
在做的过程中发现,所有小怪的动画状态机绝大部分的状态是相同的。如果每个小怪的动画状态机都手动创建的话,非常繁琐。正好之前看了一篇介绍代码创建动画状态机的方法--unity5.x代码创建AnimatorController状态机,为了偷懒,学习一小代码创建AnimatorController的方法。
1、创建AnimatorController
AnimatorController animatorController = AnimatorController.CreateAnimatorControllerAtPath("Assets/Test.controller");
这里主要的方法就是public static AnimatorController CreateAnimatorControllerAtPath(string path);传递给它一个string类型的参数即可,path即是创建的AnimatorController 的保存位置。
2、获取AnimatorController的层layer和AnimatorStateMachine
AnimatorControllerLayer layer = animatorController.layers[0]; AnimatorStateMachine animatorStateMachine = layer.stateMachine;
第一步是获取AnimatorController的层,即当前层;第二步是获取当前层的AnimatorStateMachine,获取的AnimatorStateMachine在后面有用处。
3、设置一下AnyState和Entry的坐标位置(为了美观起见。。。)
animatorStateMachine.anyStatePosition = new Vector3(0, 0, 0); animatorStateMachine.entryPosition = new Vector3(0, 0, 0);
这里要说的是,经过测试发现,Z坐标没有影响,主要有影响的是X,Y坐标。规律是:X从左往右增大;Y从上往下增大。具体的变化幅度需要自己试验体会。
4、添加动画Parameters
private static void AddParamter(AnimatorController controller, string name, AnimatorControllerParameterType type) { controller.AddParameter(name, type); }
这里主要想说的是public void AddParameter(string name, AnimatorControllerParameterType type);方法。第一个参数是Parameters的名字,第二个参数是Parameters的类型,类型主要有Float、Int、Bool、Trigger。跟面板上面的一一对应。
5、创建AnimatorState
AnimatorState back = AddState<BackState>("Back", animatorStateMachine, 3, new Vector3(0, -200, 0)); back.speed = -1;
private static AnimatorState AddState<T>(string stateName, AnimatorStateMachine sm, float threshold, Vector3 position) where T : StateMachineBehaviour { AnimatorState animatorState = sm.AddState(stateName, position); AnimatorStateTransition animatorStateTransition = sm.AddAnyStateTransition(animatorState); animatorStateTransition.canTransitionToSelf = false; animatorStateTransition.AddCondition(AnimatorConditionMode.Equals, threshold, "StateI"); animatorState.AddStateMachineBehaviour<T>(); return animatorState; }
首先看AddState方法,它是一个泛型方法。这里需要说明一下,本工程是使用基于UNITY的Animator自带的动画状态机实现的。T是继承自StateMachineBehaviour的类,可以直接添加到动画状态机上面。如果不需要使用动画状态机的话,可以不使用泛型方法。
第一行:
AnimatorState animatorState = sm.AddState(stateName, position);
参数是AnimatorState的名称和坐标位置,其中,坐标位置和上面第3点的坐标系是相同的。
第二行:
AnimatorStateTransition animatorStateTransition = sm.AddAnyStateTransition(animatorState);
添加一个AnyState到本状态的过渡Transition。
第三行:
animatorStateTransition.canTransitionToSelf = false;
设置过渡的canTransitionToSelf 为false。
第四行:
animatorStateTransition.AddCondition(AnimatorConditionMode.Equals, threshold, "StateI");
主要的方法是public void AddCondition(AnimatorConditionMode mode, float threshold, string parameter);方法,有三个参数,第一个是AnimatorConditionMode类型的枚举参数,可能的值有If、IfNot、Greater、Less、Equals、NotEqual,与面板上面设置时一致;第二个参数是threshold,即阈值;最后一个参数是parameter,即Animator的Parameters参数名。这里需要说明的是,我们在面板上面创建Animator的时候,会遇到一个过渡有多个Conditions的情况,在代码中解决这种问题的方法,即是多次调用public void AddCondition(AnimatorConditionMode mode, float threshold, string parameter);方法创建Conditions。
第五行:
animatorState.AddStateMachineBehaviour<T>();
主要是为了使用Animator的自带状态机。为创建的AnimatorState添加脚本。
这样,就创建了一个Back状态。这里遇到一个问题,在面板上面创建Back状态的话,我需要倒着播放动画,直接将Back状态的speed设为-1即可,而使用代码创建的话,要达到相同的目标,可使用如下方法:
back.speed = -1;
如下图所示,即是Back的Inspector面板属性:
需要设置某些属性的话,可以使用类似设置speed的方法设置。
6、创建Sub_state Machine
AnimatorStateMachine attack = AddStateMachine<AttackState>("Attack", animatorStateMachine, new Vector3(0, -150, 0)); AddSubState(attack, animatorStateMachine, "Attack", 3, 8);
private static AnimatorStateMachine AddStateMachine<T>(string stateName, AnimatorStateMachine sm, Vector3 position) where T : StateMachineBehaviour { AnimatorStateMachine sub = sm.AddStateMachine(stateName, position); sub.AddStateMachineBehaviour<T>(); return sub; }
private static void AddSubState(AnimatorStateMachine stateMachine, AnimatorStateMachine sm, string nameStr, int length, float threshold) { Vector3[] positions = new Vector3[length]; int startY = -100; for (int j = 0; j < length; ++j) { positions[j] = new Vector3(300, startY, 0); startY += 70; } for (int i = 0; i < length; ++i) { AnimatorState animatorState = stateMachine.AddState(nameStr + i.ToString(), positions[i]); AnimatorStateTransition animatorStateTransition = sm.AddAnyStateTransition(animatorState); animatorStateTransition.canTransitionToSelf = false; animatorStateTransition.AddCondition(AnimatorConditionMode.Equals, threshold, "StateI"); animatorStateTransition.AddCondition(AnimatorConditionMode.Equals, i, nameStr + "I"); } }
AddStateMachine()方法与AddState()方法基本相同,这里不再赘述。这里有一点需要注意一下,AddSubState()方法中,创建AnimatorState使用的是Sub_state Machine,而创建过渡条件,使用的则是2中获取到的AnimatorStateMachine。可以试验一下,使用Sub_state Machine设置过渡条件是什么样的问题。
7、创建BlendTree
BlendTree blendTree = null; animatorController.CreateBlendTreeInController("Move", out blendTree);
这一步还没有深入研究,先放在这里。
最后,完整的工程代码如下所示:
using UnityEngine; using System.Collections; using UnityEditor; using UnityEditor.Animations; public class CreateAnimator : Editor { [MenuItem("Tool/CreateController")] static void DoCreateAnimationAssets() { //创建Controller AnimatorController animatorController = AnimatorController.CreateAnimatorControllerAtPath("Assets/Test.controller"); //得到它的Layer AnimatorControllerLayer layer = animatorController.layers[0]; AnimatorStateMachine animatorStateMachine = layer.stateMachine; animatorStateMachine.anyStatePosition = new Vector3(0, 0, 0); animatorStateMachine.entryPosition = new Vector3(0, 0, 0); AddParamter(animatorController, "Forward", AnimatorControllerParameterType.Float); AddParamter(animatorController, "StateI", AnimatorControllerParameterType.Int); AddParamter(animatorController, "MoveSpeed", AnimatorControllerParameterType.Float); AddParamter(animatorController, "AttackI", AnimatorControllerParameterType.Int); //将动画保存到 AnimatorController中 AnimatorState back = AddState<BackState>("Back", animatorStateMachine, 3, new Vector3(0, -200, 0)); back.speed = -1; AnimatorStateMachine attack = AddStateMachine<AttackState>("Attack", animatorStateMachine, new Vector3(0, -150, 0)); AddSubState(attack, animatorStateMachine, "Attack", 3, 8); BlendTree blendTree = null; animatorController.CreateBlendTreeInController("Move", out blendTree); } private static AnimatorState AddState<T>(string stateName, AnimatorStateMachine sm, float threshold, Vector3 position) where T : StateMachineBehaviour { AnimatorState animatorState = sm.AddState(stateName, position); AnimatorStateTransition animatorStateTransition = sm.AddAnyStateTransition(animatorState); animatorStateTransition.canTransitionToSelf = false; animatorStateTransition.AddCondition(AnimatorConditionMode.Equals, threshold, "StateI"); animatorState.AddStateMachineBehaviour<T>(); return animatorState; } private static AnimatorStateMachine AddStateMachine<T>(string stateName, AnimatorStateMachine sm, Vector3 position) where T : StateMachineBehaviour { AnimatorStateMachine sub = sm.AddStateMachine(stateName, position); sub.AddStateMachineBehaviour<T>(); return sub; } private static void AddSubState(AnimatorStateMachine stateMachine, AnimatorStateMachine sm, string nameStr, int length, float threshold) { Vector3[] positions = new Vector3[length]; int startY = -100; for (int j = 0; j < length; ++j) { positions[j] = new Vector3(300, startY, 0); startY += 70; } for (int i = 0; i < length; ++i) { AnimatorState animatorState = stateMachine.AddState(nameStr + i.ToString(), positions[i]); AnimatorStateTransition animatorStateTransition = sm.AddAnyStateTransition(animatorState); animatorStateTransition.canTransitionToSelf = false; animatorStateTransition.AddCondition(AnimatorConditionMode.Equals, threshold, "StateI"); animatorStateTransition.AddCondition(AnimatorConditionMode.Equals, i, nameStr + "I"); } } private static void AddParamter(AnimatorController controller, string name, AnimatorControllerParameterType type) { controller.AddParameter(name, type); } }
最后有一点需要说明的是,本文开头的参考博客里面在创建AnimatorController的时候,会将动画片段也设置好。但考虑到本工程中的动画文件的命名不是很规范,就省略了这一步骤。如果感兴趣的话,也可以扩展这一功能。