Customizable Navigation Bar

Wednesday, April 13, 2011

Wednesday's Editor Tools 1: Import Utility

Alright, so since the poll said that you guys wanted bi-weekly updates, here they are! Starting now, I will be posting every Sunday and Wednesday. Wednesday's are going to cover editor tools, so that your life is made easier during production.

This first script is going to be called ImportUtility. Now a brief description of what this class does is check to see what is currently being imported, and making materials and prefabs if necessary. This will save you a ton of time in the long run, since creating multiple prefabs or materials at a time can be very tedious.

So, let's get started and create a new C# script called ImportUtility.cs.

using UnityEngine;

/*Since this is extending an Editor class, we are going to have to include the UnityEditor directory*/

using UnityEditor;

/*Since this class is going to need to know when an object is imported, we extend from AssetPostprocessor. AssetPostprocessor fires events when a resource is imported and even fires of different ones depending on whether or not it is a texture, a model or an audio asset.*/

public class ImportUtility : AssetPostprocessor
{

        /*The first event that we are going to overwrite is called OnPreprocessTexture. This event is called right before a texture asset is going to be imported so you can fiddle around with settings such as converting the texture to a normal map, which is what we are going to be doing*/

void OnPreprocessTexture()
{

        /*One thing that all projects should have is a naming convention, it makes things more organized and neat, and when you hand your assets off to someone else they know what they have before they even open up the file! So what we are going to do is check the name of the asset being imported, and if the name contains "_norm" we will convert the texture to a normal map on import*/

                if(assetPath.ToLower().Contains("_norm"))
                {

                /*Cast assetImporter as TextureImporter for extra options when importing a texture*/

                        ((TextureImporter)assetImporter).convertToNormalmap = true;
                }
        }

        /*The next event we overwrite is called OnPostProcessTexture. This is similar to the OnPreProcessTexture event, except this one isn't called until after the object is imported. So what are we going to do once we import this asset? On thing that I can think of that would be helpful is if it automatically created a material and applied the texture to it on import, so guess what we are going to do.*/

        void OnPostprocessTexture(Texture2D texture)
        {

                /*If the name of the asset contains _diff, then we will create a diffuse material for it*/

                if(assetPath.ToLower().Contains("_diff"))
                {
                        /*So let's create a new diffuse material and apply the texture being imported to it*/

                        Material mat = new Material(Shader.Find("Diffuse"));
                        mat.mainTexture = texture;

                        /*What we want to do next is set up a path to where the new material is going to go in our project folder. So we will remove the _diff from the path and add _MAT.mat so you know that it is a material just by looking at it, and Unity knows to save this asset as a material*/

                        string path = assetPath;
                        path = path.Remove(path.LastIndexOf("_"));
                        path += "_MAT.mat";

                        /*Once that is finished, we will use a class called AssetDatabase to create the material in the project folder, and rename the material to the texture's name so that they match*/

                        AssetDatabase.CreateAsset(mat, path);
                        AssetDatabase.RenameAsset(path, texture.name);
                }
        }

        /*So now we've completed the texture importing part of this class. Now we are going to be moving onto the models*/


        /*Before we import any model, let's make sure that we have a few settings set up a certain way*/

        void OnPreprocessModel()
        {

                /*cast assetImporter as ModelImporter to get special model importing tools and functions*/

                ModelImporter importer = (ModelImporter)assetImporter;

                /*We want unity to import the models using the units that maya or max uses, just to keep everything consistent between tool to engine*/

                importer.useFileUnits = true;

                /*We also don't want to automatically add colliders unless specified, so let's turn this option off too*/

                importer.addCollider = false;

                /*Unity will also import any materials applied in the hypershade(Maya) or material editor(Max) automatically. Now if this is what you want, then go nuts and use it, but I find it to be a pain since i create my own materials anyways, so I turn this option off as well*/

                importer.generateMaterials = 0;

                /*Now there are also options for for automatically importing animations, compression rates etc. that you can player around with. For the project that I was working on when i created this, this was more or less enough*/

        }

        /*So once we have those import options set up, we need to create prefabs for the models that are being imported, because they are handy, and more efficient*/

        void OnPostprocessModel(GameObject root)
        {

                /*recursiveColliderCheck() is a function declared below that will go through each object in the model and check to see if the name of it contains collider. If it does it adds a MeshCollider component to it and moves to the next one*/

                recursiveColliderCheck(root.transform);

                /*We are going to take the same approach for generating the prefab asset path as we did with the material*/


                string prefabPath = assetPath;
                prefabPath = prefabPath.Remove(prefabPath.LastIndexOf("."));
                prefabPath += ".prefab";

                /*If the prefab already exists then we don't need to create a new one, otherwise go through the steps*/


                if(AssetDatabase.LoadAssetAtPath(prefabPath, typeof(object)) == null)
                {

                        /*First we will create an empty prefab to store the mesh in*/

                        Object prefab = EditorUtility.CreateEmptyPrefab(prefabPath);

                        /*This is a temporary GameObject that will be the parent of the mesh. I had explained in a previous tutorial why we want to do this but I will explain again. Animations are done locally, so if the object animating is not parented, it will jump to where ever it was during that animation and play it. So if the animation started at the origin (0,0,0), the object will jump there and animate. If the object is parented, it will stay where the parent of the object is and animate at (0,0,0) locally.*/

                        GameObject obj = new GameObject("____delete me");

                        /*We move the gameobject to the root's position and parent it*/

                        obj.transform.position = root.transform.position;
                        root.transform.parent = obj.transform;

                        /*In order to store the GameObject in the prefab we need to use a static function EditorUtility.ReplacePrefab(). You pass the object going in and the prefab that you are replacing, and you are good to go*/

                        EditorUtility.ReplacePrefab(obj, prefab);

                        /*Before you do this line, go into the editor and create a new tag called delete. We need this so that we know what to delete after importing*/

                        obj.tag = "delete";

                        /*Then we will rename the prefab to the name of the mesh*/

                        AssetDatabase.RenameAsset(prefabPath, root.name);
                }
        }

        /*Now that we have done this to textures and models, we are going to do the same to audio. For me, the default audio settings were fine, so I leave OnPreprocessAudio out. Instead I just skip ahead to OnPostProcessAudio and create a prefab for it just like I did with the models*/

        void OnPostProcessAudio(AudioClip clip)
        {

                /*So once again, edit the asset path and create a new empty prefab*/

                string path = assetPath;
                path.Remove(path.LastIndexOf("."));
                path += ".prefab";
                Object prefab = EditorUtility.CreateEmptyPrefab(path);

                /*And create a new GameObject*/

                GameObject obj = new GameObject("____delete me");

                /*What you are going to want to do then is add an AudioSource component and set the clip to the clip being imported*/

                AudioSource audio = obj.AddComponent<AudioSource>();
                audio.clip = clip;

                /*Once that is complete we will replace the prefab and set the tag to delete*/

                EditorUtility.ReplacePrefab(obj, prefab);
                obj.tag = "delete";
                AssetDatabase.RenameAsset(path, clip.name);
        }

        /*This function is called in the OnPostprocessModel event. It is recursive, so what it does is check through each child of the object and add a MeshCollider if the name of the child contains collider*/

        private void recursiveColliderCheck(Transform tran)
        {
                if(tran.name.ToLower().Contains("collider"))
                {
                        tran.gameObject.AddComponent<MeshCollider>();

                        /*If the object has grandchildren we want to check those too, so for each child in this object, we call the function again*/

                        foreach(Transform currentTran in tran)
                        {
                                recursiveColliderCheck(currentTran);
                        }
                }
        }

        /*This event is called once every asset is imported. What we use this event for is deleting all of the GameObjects we created with the tag delete. We cannot delete the objects during the events because Unity will just completely crash. Of it works for you, then thats awesome, but that was the workaround that i ended up trying out and it works like a charm*/

        static void OnPostprocessAllAssets (string[] importedAssets , string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
        {

                /*So let's find all of the objects in the scene with the tag delete*/

                GameObject[] list = GameObject.FindGameObjectsWithTag("delete");

                /*We will then destroy these objects and you will never know that they existed*/

                foreach(GameObject obj in list)
                {
                        Editor.DestroyImmediate(obj);
                }
        }
}
        
That's it for this script. It seems simple, but it will save you a lot of time in the long run. Making prefabs individually is a real pain, and is very tedious, so why not get something to do it for you.

This is it for Wednesday, keep coding!