代码生成AnimatorController

0.出发点

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

  • 需要注意我的具体代码中是在一个已有的AnimatorController基础上创建的。如果完全是从0开始可以参考别的资料,其实道理是一样的都是代码创建对象。

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.动画控制器中的主要元素

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

  • 一个AnimatorController的结构基本如下


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

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

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

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

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

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 创建动画控制器

    /// <summary>
    ///  记录上一个state,用于自状态机中
    /// </summary>
    static AnimatorState lastAnimatorState = null;
    static string ParameterName;
    // 动画片段
    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>
    /// 根据配置文件创建特技组
    /// </summary>
    [MenuItem ("Tools/CreateAnimatorState")]
    static void CreateAnimatorState ()
    {
        // 获取动画片段
        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;
            }
        }

        // 当每个动画是一个单独的FBX文件中时可以用下面的方法来获取
        //die = AssetDatabase.LoadAssetAtPath ("Assets/Charactors/player2.FBX", typeof(AnimationClip)) as AnimationClip;


        // 获取状态机
        AnimatorController animatorController = AssetDatabase.LoadAssetAtPath ("Assets/AnimatorController/demo.controller", typeof(AnimatorController)) as AnimatorController;
        AnimatorControllerLayer layer = animatorController.layers [0];
        mainASM = layer.stateMachine;

        // 获取当前所有的参数
        AnimatorControllerParameter[] paras = animatorController.parameters;
        List<AnimatorControllerParameter> listParas = new List<AnimatorControllerParameter> (paras);

        // 删除指定的参数
        var acps = listParas.Where (p => p.name.Contains ("GroupParameter")).ToArray ();
        foreach (AnimatorControllerParameter item in acps) {
            animatorController.RemoveParameter (item);
        }

        // 删除指定的子状态机
        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;


        // 根据配置创建自状态机
        for (int index = 0; index < xc.datas.Count; index++) {
            Data data = xc.datas [index];

            // 设置特技参数,
            ParameterName = "GroupParameter" + data.INDEX.ToString ();
            animatorController.AddParameter (ParameterName, AnimatorControllerParameterType.Trigger);

            // 创建子状态机
            AnimatorStateMachine sub = AddSubStateMachine<AnimatorEvent> ("Group_" + data.INDEX, ParameterName, mainASM, startPos + new Vector3 (stateWidth * index, -stateHeight, 0));
            // 创建子状态机中的state
            SetStateInSubMachine (sub, data);
            lastAnimatorState = null;
        }
    }

    /// <summary>
    ///  创建sub state machine用于放置特效组中的动画
    /// </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>
    ///  根据配置数据在子状态机中创建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);
        // 设置动画片段
        animatorState.motion = clip;
        // 创建AnimatorStateTransition
        // entry连接到特技组的第一个动画
        if (first) {
            animatorStateTransition = sm.AddAnyStateTransition (animatorState);
            animatorStateTransition.AddCondition (AnimatorConditionMode.Equals, threshold, parameter);
        }
        // 最后一个动画连接到stand
        if (last) {
            animatorStateTransition = animatorState.AddTransition (mainASM.defaultState);
        }

        // 特技组内的连接创建
        if (!first && !last) {
            animatorStateTransition = animatorState.AddTransition (mainASM.defaultState);
        }

        animatorStateTransition = lastAnimatorState.AddTransition (animatorState, true);

        //AnimatorStateTransition 的设置
        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);
        // 设置动画片段
        animatorState.motion = clip;
        // 创建AnimatorStateTransition
        // AnyState连接到特技组的第一个动画
        if (index == 1) {
            //animatorStateTransition = sm.AddAnyStateTransition(animatorState);
            //animatorStateTransition.canTransitionToSelf = false;
        }
        // 最后一个动画连接到main animator machine的default state
        if (index == count) {
            animatorStateTransition = animatorState.AddTransition (mainASM.defaultState);
            animatorStateTransition.hasExitTime = true;
        }

        // 特技组内的连接创建
        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.最后的说明

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容