代碼生成AnimatorController

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ò)了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窟哺,一起剝皮案震驚了整個(gè)濱河市泻轰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌脏答,老刑警劉巖糕殉,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異殖告,居然都是意外死亡阿蝶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門黄绩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)羡洁,“玉大人,你說(shuō)我怎么就攤上這事爽丹≈螅” “怎么了辛蚊?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)真仲。 經(jīng)常有香客問(wèn)我袋马,道長(zhǎng),這世上最難降的妖魔是什么秸应? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任虑凛,我火速辦了婚禮,結(jié)果婚禮上软啼,老公的妹妹穿的比我還像新娘桑谍。我一直安慰自己,他們只是感情好祸挪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布锣披。 她就那樣靜靜地躺著,像睡著了一般贿条。 火紅的嫁衣襯著肌膚如雪雹仿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天闪唆,我揣著相機(jī)與錄音盅粪,去河邊找鬼。 笑死悄蕾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的础浮。 我是一名探鬼主播帆调,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼豆同!你這毒婦竟也來(lái)了番刊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤影锈,失蹤者是張志新(化名)和其女友劉穎芹务,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸭廷,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枣抱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辆床。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佳晶。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖讼载,靈堂內(nèi)的尸體忽然破棺而出轿秧,到底是詐尸還是另有隱情中跌,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布菇篡,位于F島的核電站漩符,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏驱还。R本人自食惡果不足惜嗜暴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铝侵。 院中可真熱鬧灼伤,春花似錦、人聲如沸咪鲜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疟丙。三九已至颖侄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間享郊,已是汗流浹背览祖。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炊琉,地道東北人展蒂。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像苔咪,于是被迫代替她去往敵國(guó)和親锰悼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容