代码生成AnimatorController

Posted 一个懒人的Blog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了代码生成AnimatorController相关的知识,希望对你有一定的参考价值。

0.出发点
现在的项目需要设置多套动画组合,全部是由策划在XML文件中设置完成,如果完全的手动在AnimatorController中去做不但工作量大而且如果将来有配置修改了还要一个个去找到对应的自状态机并且修改。因此就萌生了用代码去生成状态机的想法,而且在网上也有了很多的教程可以参考,只是每个项目都不同,且对于一些参数和属性的设置也不尽相同,因此还是把自己的代码进行一些修改后分享出来,基本上应该是包含了状态机常用的功能。

1.数据来源

一个典型的XML文件

<?xml version="1.0" encoding="ISO-8859-1"?>
<config>
    <datas>
        <data INDEX="1" Clip1="jump" Clip1Count="1" Clip2="BackLeap" Clip2Count="2" Clip3="die" Clip3Count="1"></data>
        <data INDEX="2" Clip1="BackLeap" Clip1Count="3" Clip2="jump" Clip2Count="0" Clip3="jump" Clip3Count="0"></data>
        <data INDEX="3" Clip1="BackLeap" Clip1Count="1" Clip2="ForwardLeap" Clip2Count="1" Clip3="jump" Clip3Count="1"></data>
    </datas>
</config>            

2.动画控制器中的主要元素 

2.1 Unity中editor的功能十分的强大,能够加载项目中的各种资源,而AnimatorController就是其中之一。

2.2 一个AnimatorController的结构基本如下:

2.3 一些说明:

- AnimatorControllerLayer:一个AnimatorController由多个Layer组成,但是除了BaseLayer外其它的Layer并不主要负责动画逻辑,而是多用于动画遮罩。

- AnimatorControllerParameter:顾名思义是状态机中使用的参数,这个参数可以在不同的Layer和子状态机中使用。在代码添加参数时会选择参数类型,它是个枚举`AnimatorControllerParameterType`。

- AnimatorStateMachine:动画状态机,核心逻辑实线层。在一个状态机中可以有多个state,也可以有多个Sub AnimatorStateMachine。通过AddStateMachine方法来生成并添加子状态机。

- AnimatorState:动画状态,也是这个系统中的基础单元。其可以设定各种属性,比较常用的是AnimationClip和Speed等。

- AnimatorStateTransition:也就是动画转换,其中可以设定触发参数,而且其中还有一个很重要的东西就是动画过度的设定。

 

3.完整代码

  1 using UnityEngine;
  2 using System.Collections;
  3 using UnityEditor;
  4 using System;
  5 using UnityEditor.Animations;
  6 using System.IO;
  7 using System.Xml;
  8 using System.Text.RegularExpressions;
  9 using System.Xml.Serialization;
 10 using System.Collections.Generic;
 11 using System.Linq;
 12 
 13 //[CustomEditor(typeof(EditorTools))]
 14 public class EditorTools : MonoBehaviour
 15 {
 16     #region 创建动画控制器
 17 
 18     /// <summary>
 19     ///  记录上一个state,用于自状态机中
 20     /// </summary>
 21     static AnimatorState lastAnimatorState = null;
 22     static string ParameterName;
 23     // 动画片段
 24     static AnimationClip die;
 25     static AnimationClip jump;
 26     static AnimationClip BackLeap;
 27     static AnimationClip ForwardLeap;
 28     /// <summary>
 29     /// base layer AnimatorStateMachine
 30     /// </summary>
 31     static AnimatorStateMachine mainASM;
 32     static int stateHeight = 100;
 33     static int stateWidth = 220;
 34 
 35     /// <summary>
 36     /// 根据配置文件创建特技组
 37     /// </summary>
 38     [MenuItem ("Tools/CreateAnimatorState")]
 39     static void CreateAnimatorState ()
 40     {
 41         // 获取动画片段
 42         List<object> allAssets = new List<object> (AssetDatabase.LoadAllAssetsAtPath ("Assets/Charactors/player2.FBX"));
 43         var animationClips = allAssets.Where (o => o.GetType () == typeof(AnimationClip)).ToList ();
 44         foreach (var item in animationClips) {
 45             AnimationClip x = item as AnimationClip;
 46             switch (x.name) {
 47             case "die":
 48                 die = x;
 49                 break;
 50             case "jump":
 51                 jump = x;
 52                 break;
 53             case "BackLeap":
 54                 BackLeap = x;
 55                 break;
 56             case "ForwardLeap":
 57                 ForwardLeap = x;
 58                 break;
 59             default:
 60                 break;
 61             }
 62         }
 63 
 64         // 当每个动画是一个单独的FBX文件中时可以用下面的方法来获取
 65         //die = AssetDatabase.LoadAssetAtPath ("Assets/Charactors/player2.FBX", typeof(AnimationClip)) as AnimationClip;
 66 
 67 
 68         // 获取状态机
 69         AnimatorController animatorController = AssetDatabase.LoadAssetAtPath ("Assets/AnimatorController/demo.controller", typeof(AnimatorController)) as AnimatorController;
 70         AnimatorControllerLayer layer = animatorController.layers [0];
 71         mainASM = layer.stateMachine;
 72 
 73         // 获取当前所有的参数
 74         AnimatorControllerParameter[] paras = animatorController.parameters;
 75         List<AnimatorControllerParameter> listParas = new List<AnimatorControllerParameter> (paras);
 76 
 77         // 删除指定的参数
 78         var acps = listParas.Where (p => p.name.Contains ("GroupParameter")).ToArray ();
 79         foreach (AnimatorControllerParameter item in acps) {
 80             animatorController.RemoveParameter (item);
 81         }
 82 
 83         // 删除指定的子状态机
 84         ChildAnimatorStateMachine[] childASM = mainASM.stateMachines;
 85         List<ChildAnimatorStateMachine> listCASM = new List<ChildAnimatorStateMachine> (childASM);
 86         var casms = listCASM.Where (c => c.stateMachine.name.Contains ("Group")).ToArray ();
 87         foreach (ChildAnimatorStateMachine item in casms) {
 88             mainASM.RemoveStateMachine (item.stateMachine);
 89         }
 90 
 91         // 读配置文件
 92         XmlConfig xc = ReadXml ();
 93 
 94         Vector3 startPos = mainASM.anyStatePosition;
 95 
 96 
 97         // 根据配置创建自状态机
 98         for (int index = 0; index < xc.datas.Count; index++) {
 99             Data data = xc.datas [index];
100 
101             // 设置特技参数,
102             ParameterName = "GroupParameter" + data.INDEX.ToString ();
103             animatorController.AddParameter (ParameterName, AnimatorControllerParameterType.Trigger);
104 
105             // 创建子状态机
106             AnimatorStateMachine sub = AddSubStateMachine<AnimatorEvent> ("Group_" + data.INDEX, ParameterName, mainASM, startPos + new Vector3 (stateWidth * index, -stateHeight, 0));
107             // 创建子状态机中的state
108             SetStateInSubMachine (sub, data);
109             lastAnimatorState = null;
110         }
111     }
112 
113     /// <summary>
114     ///  创建sub state machine用于放置特效组中的动画
115     /// </summary>
116     /// <typeparam name="T"></typeparam>
117     /// <param name="stateName"></param>
118     /// <param name="sm"></param>
119     /// <param name="position"></param>
120     /// <param name="data"></param>
121     /// <returns></returns>
122     private static AnimatorStateMachine AddSubStateMachine<T> (string stateName, string para, AnimatorStateMachine sm, Vector3 position) where T : StateMachineBehaviour
123     {
124         AnimatorStateMachine sub = sm.AddStateMachine (stateName, position);
125         sub.AddStateMachineBehaviour<T> ();
126         AnimatorStateTransition transition = mainASM.defaultState.AddTransition (sub, false);
127         transition.AddCondition (AnimatorConditionMode.If, 0, para);
128         return sub;
129     }
130 
131     /// <summary>
132     ///  根据配置数据在子状态机中创建state
133     /// </summary>
134     /// <typeparam name="T"></typeparam>
135     /// <param name="subSM"></param>
136     /// <param name="data"></param>
137     private static void SetStateInSubMachine (AnimatorStateMachine subSM, Data data)
138     {
139         AnimatorState newState;
140         string stateName;
141         Vector3 pos;
142         List<AnimationClip> acArray = new List<AnimationClip> ();
143         SetAnimationClip (data.Clip1, data.Clip1Count, ref acArray);
144         SetAnimationClip (data.Clip2, data.Clip2Count, ref acArray);
145         SetAnimationClip (data.Clip3, data.Clip3Count, ref acArray);
146 
147         for (int x = 1; x <= acArray.Count; x++) {
148             stateName = "GroupState_" + data.INDEX + "_" + x.ToString ();
149             pos = subSM.entryPosition + new Vector3 (stateWidth, -stateHeight * x, 0);
150             newState = AddState (stateName, subSM, pos, acArray [x - 1], x, acArray.Count);
151             lastAnimatorState = newState;
152         }
153     }
154 
155     static void SetAnimationClip (string clipName, int count, ref List<AnimationClip> acArray)
156     {
157         for (int i = 0; i < count; i++) {
158             if (clipName == die.name) {
159                 acArray.Add (die);
160             }
161             if (clipName == jump.name) {
162                 acArray.Add (jump);
163             }
164             if (clipName == BackLeap.name) {
165                 acArray.Add (BackLeap);
166             }
167             if (clipName == ForwardLeap.name) {
168                 acArray.Add (ForwardLeap);
169             }
170         }
171     }
172 
173     static AnimatorState AddState<T> (string stateName, AnimatorStateMachine sm, float threshold, string parameter, Vector3 position,
174                                       AnimationClip clip, bool first = false, bool last = false) where T : StateMachineBehaviour
175     {
176         AnimatorStateTransition animatorStateTransition;
177         //  生成AnimatorState
178         AnimatorState animatorState = sm.AddState (stateName, position);
179         // 设置动画片段
180         animatorState.motion = clip;
181         // 创建AnimatorStateTransition
182         // entry连接到特技组的第一个动画
183         if (first) {
184             animatorStateTransition = sm.AddAnyStateTransition (animatorState);
185             animatorStateTransition.AddCondition (AnimatorConditionMode.Equals, threshold, parameter);
186         }
187         // 最后一个动画连接到stand
188         if (last) {
189             animatorStateTransition = animatorState.AddTransition (mainASM.defaultState);
190         }
191 
192         // 特技组内的连接创建
193         if (!first && !last) {
194             animatorStateTransition = animatorState.AddTransition (mainASM.defaultState);
195         }
196 
197         animatorStateTransition = lastAnimatorState.AddTransition (animatorState, true);
198 
199         //AnimatorStateTransition 的设置
200         animatorStateTransition.canTransitionToSelf = false;
201         animatorState.AddStateMachineBehaviour<T> ();
202         return animatorState;
203     }
204 
205     static AnimatorState AddState (string stateName, AnimatorStateMachine sm, Vector3 position, AnimationClip clip, int index, int count)
206     {
207         AnimatorStateTransition animatorStateTransition = null;
208         //  生成AnimatorState
209         AnimatorState animatorState = sm.AddState (stateName, position);
210         // 设置动画片段
211         animatorState.motion = clip;
212         // 创建AnimatorStateTransition
213         // AnyState连接到特技组的第一个动画
214         if (index == 1) {
215             //animatorStateTransition = sm.AddAnyStateTransition(animatorState);
216             //animatorStateTransition.canTransitionToSelf = false;
217         }
218         // 最后一个动画连接到main animator machine的default state
219         if (index == count) {
220             animatorStateTransition = animatorState.AddTransition (mainASM.defaultState);
221             animatorStateTransition.hasExitTime = true;
222         }
223 
224         // 特技组内的连接创建
225         if (lastAnimatorState != null) {
226             animatorStateTransition = lastAnimatorState.AddTransition (animatorState, true);
227         }
228 
229         return animatorState;
230     }
231 
232     #endregion
233 
234     #region public method
235 
236     static XmlConfig ReadXml ()
237     {
238         //string xmlStr = File.ReadAllText(Application.dataPath.ToString() + "/StreamingAssets/XMLConfigFiles/Stunt.xml");
239         //Debug.Log(xmlStr);
240         //string objTxt = Regex.Replace(xmlStr, @"<!--[^-]*-->", string.Empty, RegexOptions.IgnoreCase);
241         //Debug.Log(objTxt);
242         return DeserializeFromXml<XmlConfig> (Application.dataPath.ToString () + "/StreamingAssets/XMLConfigFiles/data.xml");
243     }
244 
245     /// <summary>
246     /// 从某一XML文件反序列化到某一类型
247     /// </summary>
248     /// <param name="filePath">待反序列化的XML文件名称</param>
249     /// <param name="type">反序列化出的</param>
250     /// <returns></returns>
251     public static T DeserializeFromXml<T> (string filePath)
252     {
253         try {
254             if (!System.IO.File.Exists (filePath))
255                 throw new ArgumentNullException (filePath + " not Exists");
256 
257             using (System.IO.StreamReader reader = new System.IO.StreamReader (filePath)) {
258                 System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer (typeof(T));
259                 T ret = (T)xs.Deserialize (reader);
260                 return ret;
261             }
262         }
263         catch (Exception ex) {
264             return default(T);
265         }
266     }
267 
268     #endregion
269 }
270 
271 
272 #region 序列化需要的model
273 [XmlType (TypeName = "config")]
274 public class XmlConfig
275 {
276     [XmlArray ("datas")]
277     public List<Data> datas { get; set; }
278 }
279 
280 [XmlType (TypeName = "data")]
281 public class Data
282 {
283     [XmlAttribute]
284     public int INDEX;
285     [XmlAttribute]
286     public string Clip1;
287     [XmlAttribute]
288     public int Clip1Count;
289     [XmlAttribute]
290     public string Clip2;
291     [XmlAttribute]
292     public int Clip2Count;
293     [XmlAttribute]
294     public string Clip3;
295     [XmlAttribute]
296     public int Clip3Count;
297 }
298 
299 #endregion

 

4.最后的说明

- 其实整个过程基本就是读取XML文件内容,然后按照第二部分中描述的结构来一点一点构建状态机。

- 在设定具体属性时需要按照具体情况来做。

- 有个天坑,就是如果在Base Layer界面多次点击CreateAnimatorState按钮时会出现Unity的crash,或者出现界面所有元素消失并报错。我找了很多资料应该是UnityEditor的bug。有一个很简单的解决办法,就是创建一个新的Layer,切换到新Layer的界面,然后点击CreateAnimatorState按钮,再切回Base Layer,这样就不会出错了。

 

以上是关于代码生成AnimatorController的主要内容,如果未能解决你的问题,请参考以下文章

Android Adapter代码片

代码片|水波纹

练手小游戏(代码篇之逻辑杂篇

代码片--练习匿名内部类

用java给html文件添加必要的控制html代码片

markdown 放代码片