0.出發(fā)點(diǎn)
現(xiàn)在的項(xiàng)目需要設(shè)置多套動(dòng)畫組合重抖,全部是由策劃在XML文件中設(shè)置完成露氮,如果完全的手動(dòng)在AnimatorController中去做不但工作量大而且如果將來(lái)有配置修改了還要一個(gè)個(gè)去找到對(duì)應(yīng)的自狀態(tài)機(jī)并且修改。因此就萌生了用代碼去生成狀態(tài)機(jī)的想法钟沛,而且在網(wǎng)上也有了很多的教程可以參考畔规,只是每個(gè)項(xiàng)目都不同,且對(duì)于一些參數(shù)和屬性的設(shè)置也不盡相同恨统,因此還是把自己的代碼進(jìn)行一些修改后分享出來(lái)叁扫,基本上應(yīng)該是包含了狀態(tài)機(jī)常用的功能。
需要注意我的具體代碼中是在一個(gè)已有的AnimatorController基礎(chǔ)上創(chuàng)建的畜埋。如果完全是從0開始可以參考別的資料莫绣,其實(shí)道理是一樣的都是代碼創(chuàng)建對(duì)象。
1.數(shù)據(jù)來(lái)源
一個(gè)典型的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.動(dòng)畫控制器中的主要元素
Unity中editor的功能十分的強(qiáng)大悠鞍,能夠加載項(xiàng)目中的各種資源对室,而AnimatorController就是其中之一。
-
一個(gè)AnimatorController的結(jié)構(gòu)基本如下
AnimatorControllerLayer:一個(gè)AnimatorController由多個(gè)Layer組成咖祭,但是除了BaseLayer外其它的Layer并不主要負(fù)責(zé)動(dòng)畫邏輯掩宜,而是多用于動(dòng)畫遮罩。
AnimatorControllerParameter:顧名思義是狀態(tài)機(jī)中使用的參數(shù)么翰,這個(gè)參數(shù)可以在不同的Layer和子狀態(tài)機(jī)中使用牺汤。在代碼添加參數(shù)時(shí)會(huì)選擇參數(shù)類型,它是個(gè)枚舉
AnimatorControllerParameterType
浩嫌。AnimatorStateMachine:動(dòng)畫狀態(tài)機(jī)檐迟,核心邏輯實(shí)線層戴已。在一個(gè)狀態(tài)機(jī)中可以有多個(gè)state,也可以有多個(gè)Sub AnimatorStateMachine锅减。通過(guò)AddStateMachine方法來(lái)生成并添加子狀態(tài)機(jī)糖儡。
AnimatorState:動(dòng)畫狀態(tài),也是這個(gè)系統(tǒng)中的基礎(chǔ)單元怔匣。其可以設(shè)定各種屬性握联,比較常用的是AnimationClip和Speed等。
AnimatorStateTransition:也就是動(dòng)畫轉(zhuǎn)換每瞒,其中可以設(shè)定觸發(fā)參數(shù)金闽,而且其中還有一個(gè)很重要的東西就是動(dòng)畫過(guò)度的設(shè)定。
3.完整代碼
using UnityEngine;
using System.Collections;
using UnityEditor;
using System;
using UnityEditor.Animations;
using System.IO;
using System.Xml;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Linq;
//[CustomEditor(typeof(EditorTools))]
public class EditorTools : MonoBehaviour
{
#region 創(chuàng)建動(dòng)畫控制器
/// <summary>
/// 記錄上一個(gè)state剿骨,用于自狀態(tài)機(jī)中
/// </summary>
static AnimatorState lastAnimatorState = null;
static string ParameterName;
// 動(dòng)畫片段
static AnimationClip die;
static AnimationClip jump;
static AnimationClip BackLeap;
static AnimationClip ForwardLeap;
/// <summary>
/// base layer AnimatorStateMachine
/// </summary>
static AnimatorStateMachine mainASM;
static int stateHeight = 100;
static int stateWidth = 220;
/// <summary>
/// 根據(jù)配置文件創(chuàng)建特技組
/// </summary>
[MenuItem ("Tools/CreateAnimatorState")]
static void CreateAnimatorState ()
{
// 獲取動(dòng)畫片段
List<object> allAssets = new List<object> (AssetDatabase.LoadAllAssetsAtPath ("Assets/Charactors/player2.FBX"));
var animationClips = allAssets.Where (o => o.GetType () == typeof(AnimationClip)).ToList ();
foreach (var item in animationClips) {
AnimationClip x = item as AnimationClip;
switch (x.name) {
case "die":
die = x;
break;
case "jump":
jump = x;
break;
case "BackLeap":
BackLeap = x;
break;
case "ForwardLeap":
ForwardLeap = x;
break;
default:
break;
}
}
// 當(dāng)每個(gè)動(dòng)畫是一個(gè)單獨(dú)的FBX文件中時(shí)可以用下面的方法來(lái)獲取
//die = AssetDatabase.LoadAssetAtPath ("Assets/Charactors/player2.FBX", typeof(AnimationClip)) as AnimationClip;
// 獲取狀態(tài)機(jī)
AnimatorController animatorController = AssetDatabase.LoadAssetAtPath ("Assets/AnimatorController/demo.controller", typeof(AnimatorController)) as AnimatorController;
AnimatorControllerLayer layer = animatorController.layers [0];
mainASM = layer.stateMachine;
// 獲取當(dāng)前所有的參數(shù)
AnimatorControllerParameter[] paras = animatorController.parameters;
List<AnimatorControllerParameter> listParas = new List<AnimatorControllerParameter> (paras);
// 刪除指定的參數(shù)
var acps = listParas.Where (p => p.name.Contains ("GroupParameter")).ToArray ();
foreach (AnimatorControllerParameter item in acps) {
animatorController.RemoveParameter (item);
}
// 刪除指定的子狀態(tài)機(jī)
ChildAnimatorStateMachine[] childASM = mainASM.stateMachines;
List<ChildAnimatorStateMachine> listCASM = new List<ChildAnimatorStateMachine> (childASM);
var casms = listCASM.Where (c => c.stateMachine.name.Contains ("Group")).ToArray ();
foreach (ChildAnimatorStateMachine item in casms) {
mainASM.RemoveStateMachine (item.stateMachine);
}
// 讀配置文件
XmlConfig xc = ReadXml ();
Vector3 startPos = mainASM.anyStatePosition;
// 根據(jù)配置創(chuàng)建自狀態(tài)機(jī)
for (int index = 0; index < xc.datas.Count; index++) {
Data data = xc.datas [index];
// 設(shè)置特技參數(shù)代芜,
ParameterName = "GroupParameter" + data.INDEX.ToString ();
animatorController.AddParameter (ParameterName, AnimatorControllerParameterType.Trigger);
// 創(chuàng)建子狀態(tài)機(jī)
AnimatorStateMachine sub = AddSubStateMachine<AnimatorEvent> ("Group_" + data.INDEX, ParameterName, mainASM, startPos + new Vector3 (stateWidth * index, -stateHeight, 0));
// 創(chuàng)建子狀態(tài)機(jī)中的state
SetStateInSubMachine (sub, data);
lastAnimatorState = null;
}
}
/// <summary>
/// 創(chuàng)建sub state machine用于放置特效組中的動(dòng)畫
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="stateName"></param>
/// <param name="sm"></param>
/// <param name="position"></param>
/// <param name="data"></param>
/// <returns></returns>
private static AnimatorStateMachine AddSubStateMachine<T> (string stateName, string para, AnimatorStateMachine sm, Vector3 position) where T : StateMachineBehaviour
{
AnimatorStateMachine sub = sm.AddStateMachine (stateName, position);
sub.AddStateMachineBehaviour<T> ();
AnimatorStateTransition transition = mainASM.defaultState.AddTransition (sub, false);
transition.AddCondition (AnimatorConditionMode.If, 0, para);
return sub;
}
/// <summary>
/// 根據(jù)配置數(shù)據(jù)在子狀態(tài)機(jī)中創(chuàng)建state
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="subSM"></param>
/// <param name="data"></param>
private static void SetStateInSubMachine (AnimatorStateMachine subSM, Data data)
{
AnimatorState newState;
string stateName;
Vector3 pos;
List<AnimationClip> acArray = new List<AnimationClip> ();
SetAnimationClip (data.Clip1, data.Clip1Count, ref acArray);
SetAnimationClip (data.Clip2, data.Clip2Count, ref acArray);
SetAnimationClip (data.Clip3, data.Clip3Count, ref acArray);
for (int x = 1; x <= acArray.Count; x++) {
stateName = "GroupState_" + data.INDEX + "_" + x.ToString ();
pos = subSM.entryPosition + new Vector3 (stateWidth, -stateHeight * x, 0);
newState = AddState (stateName, subSM, pos, acArray [x - 1], x, acArray.Count);
lastAnimatorState = newState;
}
}
static void SetAnimationClip (string clipName, int count, ref List<AnimationClip> acArray)
{
for (int i = 0; i < count; i++) {
if (clipName == die.name) {
acArray.Add (die);
}
if (clipName == jump.name) {
acArray.Add (jump);
}
if (clipName == BackLeap.name) {
acArray.Add (BackLeap);
}
if (clipName == ForwardLeap.name) {
acArray.Add (ForwardLeap);
}
}
}
static AnimatorState AddState<T> (string stateName, AnimatorStateMachine sm, float threshold, string parameter, Vector3 position,
AnimationClip clip, bool first = false, bool last = false) where T : StateMachineBehaviour
{
AnimatorStateTransition animatorStateTransition;
// 生成AnimatorState
AnimatorState animatorState = sm.AddState (stateName, position);
// 設(shè)置動(dòng)畫片段
animatorState.motion = clip;
// 創(chuàng)建AnimatorStateTransition
// entry連接到特技組的第一個(gè)動(dòng)畫
if (first) {
animatorStateTransition = sm.AddAnyStateTransition (animatorState);
animatorStateTransition.AddCondition (AnimatorConditionMode.Equals, threshold, parameter);
}
// 最后一個(gè)動(dòng)畫連接到stand
if (last) {
animatorStateTransition = animatorState.AddTransition (mainASM.defaultState);
}
// 特技組內(nèi)的連接創(chuàng)建
if (!first && !last) {
animatorStateTransition = animatorState.AddTransition (mainASM.defaultState);
}
animatorStateTransition = lastAnimatorState.AddTransition (animatorState, true);
//AnimatorStateTransition 的設(shè)置
animatorStateTransition.canTransitionToSelf = false;
animatorState.AddStateMachineBehaviour<T> ();
return animatorState;
}
static AnimatorState AddState (string stateName, AnimatorStateMachine sm, Vector3 position, AnimationClip clip, int index, int count)
{
AnimatorStateTransition animatorStateTransition = null;
// 生成AnimatorState
AnimatorState animatorState = sm.AddState (stateName, position);
// 設(shè)置動(dòng)畫片段
animatorState.motion = clip;
// 創(chuàng)建AnimatorStateTransition
// AnyState連接到特技組的第一個(gè)動(dòng)畫
if (index == 1) {
//animatorStateTransition = sm.AddAnyStateTransition(animatorState);
//animatorStateTransition.canTransitionToSelf = false;
}
// 最后一個(gè)動(dòng)畫連接到main animator machine的default state
if (index == count) {
animatorStateTransition = animatorState.AddTransition (mainASM.defaultState);
animatorStateTransition.hasExitTime = true;
}
// 特技組內(nèi)的連接創(chuàng)建
if (lastAnimatorState != null) {
animatorStateTransition = lastAnimatorState.AddTransition (animatorState, true);
}
return animatorState;
}
#endregion
#region public method
static XmlConfig ReadXml ()
{
//string xmlStr = File.ReadAllText(Application.dataPath.ToString() + "/StreamingAssets/XMLConfigFiles/Stunt.xml");
//Debug.Log(xmlStr);
//string objTxt = Regex.Replace(xmlStr, @"<!--[^-]*-->", string.Empty, RegexOptions.IgnoreCase);
//Debug.Log(objTxt);
return DeserializeFromXml<XmlConfig> (Application.dataPath.ToString () + "/StreamingAssets/XMLConfigFiles/data.xml");
}
/// <summary>
/// 從某一XML文件反序列化到某一類型
/// </summary>
/// <param name="filePath">待反序列化的XML文件名稱</param>
/// <param name="type">反序列化出的</param>
/// <returns></returns>
public static T DeserializeFromXml<T> (string filePath)
{
try {
if (!System.IO.File.Exists (filePath))
throw new ArgumentNullException (filePath + " not Exists");
using (System.IO.StreamReader reader = new System.IO.StreamReader (filePath)) {
System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer (typeof(T));
T ret = (T)xs.Deserialize (reader);
return ret;
}
}
catch (Exception ex) {
return default(T);
}
}
#endregion
}
#region 序列化需要的model
[XmlType (TypeName = "config")]
public class XmlConfig
{
[XmlArray ("datas")]
public List<Data> datas { get; set; }
}
[XmlType (TypeName = "data")]
public class Data
{
[XmlAttribute]
public int INDEX;
[XmlAttribute]
public string Clip1;
[XmlAttribute]
public int Clip1Count;
[XmlAttribute]
public string Clip2;
[XmlAttribute]
public int Clip2Count;
[XmlAttribute]
public string Clip3;
[XmlAttribute]
public int Clip3Count;
}
#endregion
4.最后的說(shuō)明
其實(shí)整個(gè)過(guò)程基本就是讀取XML文件內(nèi)容,然后按照第二部分中描述的結(jié)構(gòu)來(lái)一點(diǎn)一點(diǎn)構(gòu)建狀態(tài)機(jī)浓利。
在設(shè)定具體屬性時(shí)需要按照具體情況來(lái)做挤庇。
有個(gè)天坑,就是如果在Base Layer界面多次點(diǎn)擊CreateAnimatorState按鈕時(shí)會(huì)出現(xiàn)Unity的crash贷掖,或者出現(xiàn)界面所有元素消失并報(bào)錯(cuò)嫡秕。我找了很多資料應(yīng)該是UnityEditor的bug。有一個(gè)很簡(jiǎn)單的解決辦法苹威,就是創(chuàng)建一個(gè)新的Layer昆咽,切換到新Layer的界面,然后點(diǎn)擊CreateAnimatorState按鈕牙甫,再切回Base Layer掷酗,這樣就不會(huì)出錯(cuò)了。