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,這樣就不會出錯了。