Tuesday, December 17, 2013

Create AnimationState Enum T4 Template Generator in Unity

I find slow and not practical manual defining Animation states:


  //        static int idleState = Animator.StringToHash("Base Layer.Idle");    
        //        static int locoState = Animator.StringToHash("Base Layer.Locomotion");            // these integers are references to our animator's states
        //        static int jumpState = Animator.StringToHash("Base Layer.Jump");                // and are used to check state for various actions to occur
        //        static int jumpDownState = Animator.StringToHash("Base Layer.JumpDown");        // within our FixedUpdate() function below
        //        static int fallState = Animator.StringToHash("Base Layer.Fall");
        //        static int rollState = Animator.StringToHash("Base Layer.Roll");
        //        static int waveState = Animator.StringToHash("Layer2.Wave");



and couldn't find logic why Unity masters teach using this way.
I couldn't remember all those names and really I wanted to be free redesigning nodes in Animator without to define/remove those states again and again in code. Also I wanted Intellisense in editor, readable states and still have speed of working with integer hashes not strings.

You can create even much complex generation and compilation with use of T4 Templates under the .NET, but Mono.TextTemplating is still unfinished so I needed to update/upgrade in order to reach my goal.

Next create .tpl (some extension don't use original .tt cos MonoDevelop will try to compile right away and isn't stable at all) text file with content:

<#@ template language="C#" debug="true" hostspecific="true" #>
//THIS IS AUTO-GENERATED FILE. DON'T CHANGE


namespace <#= Namespace #>{
 
  public enum <#= EnumName #>:int{
<#= Values #>

  }


}


In Editor Script you can loop thru AnimatorControler's States and build StringBuilder of comma separated StateName=HashValue,...pairs.

    AnimatorControllerLayer layer;
                        StateMachine stateMachine;    



                        AnimatorController ac = controller as AnimatorController;
                        numLayers = ac.layerCount;
            
                        for (; i<numLayersi++) {
                                layer = ac.GetLayer (i);
                

                                stateMachine = layer.stateMachine;
                
                                numStates = stateMachine.stateCount;
                

                
                                for (j=0j<numStatesj++) {

                                        stateMachine.GetState (j).uniqueNameHash


Function that would generate .cs file for you with Enum of AnimationController AnimationStates would look like:

static void generateCSharpStatesEnum (string namespaceNamestring enumNamestring fileNameStringBuilder statesStringBuilder)
        {

                //Create template generator
                TemplateGenerator generator = new TemplateGenerator ();
                generator.Session.Add ("Namespace"namespaceName);
                //generator.Session.Add ("ClassName", controller.name.Replace (" ", "") + "Class");
                generator.Session.Add ("EnumName"enumName);        
                generator.Session.Add ("Values"statesStringBuilder.ToString ());
        
                string generatedFileName = fileName;

                string generated;

                //split ws.winx.input.states
                string[] namespacePath = namespaceName.Split ('.');
                int i = 0;
                generated = Application.dataPath;

                //search for directory
                string[] directoriesFound = Directory.GetDirectories (generatednamespacePath [0], SearchOption.AllDirectories);
                
                //find if file already exist
                if (directoriesFound.Length > 0) {
                        if (directoriesFound.Length > 1)
                                UnityEngine.Debug.LogWarning ("Only first found " + generatedFileName + " will be used");
                    
                        generated = directoriesFound [0];
                        i = 1;
                    
                } else {
                        generated = Path.Combine (Application.dataPath"Scripts");
                            
                        //if Scripts doesn't exist
                        if (!Directory.Exists (generated))
                                Directory.CreateDirectory (generated);
                }
        
        
        
                //generate subdirs
                for (; i<namespacePath.Lengthi++) {
                        generated = Path.Combine (generatednamespacePath [i]);

                        if (!Directory.Exists (generated))
                                Directory.CreateDirectory (generated);
                }
        

            
            
                generated = Path.GetFullPath (Path.Combine (generatedgeneratedFileName));

                

                 


        
                try {
                        generator.ProcessTemplate (Path.Combine (Path.Combine (Application.dataPath"Editor"), "StatesEnumTemplate.tpl"), generated);
            
                        UnityEngine.Debug.Log ("Generated States Enum at:" + generated);
                } catch (Exception e) {
                        UnityEngine.Debug.LogError (e.Message);
                }
        
                AssetDatabase.Refresh ();

        }


In template #Variables will be replaced with content of the generator Session variable.
You need to refresh AssetDatabase cos even file is generate wouldn't be seen by Unity until refreshed. You can also track AssetDatabase for changes and then generate when Scene/Animator is saved. I've such solution so its possible but changed to generate when click Save inside Editor Window.

/THIS IS AUTO-GENERATED FILEDON'T CHANGE


namespace ws.winx.input.states{
   
  public enum States:int{
        Wave=1397315813,

    MyCustomState=-1624475888,


    
  }
  
}


In you code you can use it with Intellisense when you write States. <autocomplete> , will be also readable and undercover compiler will access state by integer hash.







No comments:

Post a Comment