[Unity] 유니티 커스텀 에디터 _Custom Editor - create sprite animation clip.cs

2019. 7. 26. 19:03unity/unity script

our team project is base on 2d sprite image

 

craft art resource solution is very simple

 

create 3d model (3d max)

create an image on un4 engine

import image ue4 to unity

make sprite animation

end

 

but our team is not used code sprite animation, use animation clip
because it was more high quality

using animation clip is more efficient pull up quality level

 

but those work was simple and repetitive 

sprite image into the animation clip and control spacing

every character had  4direction = 4 animation clips and 3 level variation = 12 animation clips

 

animation artist has suffered to do that work

 

request

 

completed animation

prefixed condition

- have completed art resource(sprite)

- have fixed animation frame

needs

-  assign animatorcontroller after create animation clip & animatorcontroller

- control sprite interval(look like under)

 

not complete yet

prefixed condition

- had only resource name(from game article designer)

- create image later

needs

- want to do the same action by resource name like when I had a real image(select 1)

- want to be able to control the dummy name, spacing, sprite start number, and number of sprites 

  when creating a sprite.

 

 

 

 

 

 

 

 

 

/////  - code detail explanation //// full code  is under this block

 

 

sing System.Collections; 
using System.Collections.Generic; 
using UnityEngine; 
using UnityEditor; 
using System.IO; 
using System.Linq; 
using UnityEditor.Animations; 

public class CreateSpriteAnimationClip : EditorWindow 

{ 
    static CreateSpriteAnimationClip myWindow; 
/<< declare "use editor window


    Object targetObj; 
    int frameSamples = 10; 
    int spriteInterval = 1; 

    string[] fileTypeNames = { "폴더", "더미생성" }; 
    int fileType = 0; 
/<< variables use in type select 


    // 더미생성용 변수 
    string spritenamefield = "dummyName"; 
    string spritefolderfield = "Assets/artResources/"; 
    string oridummysprite = "Assets/artResources/Characters/spritedefault.png"; 
    int StarSpriteNum = 0; 
    public AnimationClip MyAnimator = null; 
    public Texture2D dummytexture; 
    Sprite Dummysprite; 
    bool RemoveDummySprite = true; 
    int DummynameInterval = 1; 
/<< this block use in Dummy sprite

/<< copy from oridummysprite to dummy sprite and rename it with dummy name


    string outputPath; 
    Sprite[] sprites; 

    [MenuItem("Window/CreateSpriteAnimationClip")] 
    static void Init() 
    { 
        // Get existing open window or if none, make a new one: 
        myWindow = (CreateSpriteAnimationClip)GetWindow(typeof(CreateSpriteAnimationClip)); 
        myWindow.Show(); 
    } 
/<< base setting menuitem name,, base action - open custom editor when you click window menu 

    private void OnDestroy() 
    { 
        CloseSpriteViewerWindow(); 
    } 

    void CloseSpriteViewerWindow() 
    { 
        Reset(); 
    } 

    void Reset() 
    { 
        outputPath = ""; 
        sprites = null; 
        targetObj = null; 
    } 

/<<it's base setting about close, end scripts path, sprite array, target folder reset


    void OnGUI() 
    { 
        int type = EditorGUILayout.Popup(fileType, fileTypeNames, GUILayout.Height(20)); 

/<<Create popup menu for category selection
        if (fileType != type) 
        { 
            CloseSpriteViewerWindow(); 
            fileType = type; 
            Reset(); 
        } 

/<<if not match type do reset funtion

        if (fileType == 0) 
        { 
            var obj = EditorGUILayout.ObjectField("폴더 :", targetObj, typeof(DefaultAsset), true); 


            if (targetObj != obj) 
            { 
                targetObj = obj; 
                if (targetObj != null) 
                { 
                    var tempPath = AssetDatabase.GetAssetPath(targetObj); 

/<<temppath is targetobject's asset path this case target is folder path because targetobject is pointing folder

ex)d:/work/.../asset/a/

/<<reason for getting folder path was each computer's realpath is different
/<<in this scripts some line needs project path, some line absoulte path

 

/<<use the real path getting from the target object to avoid confusion about the "path" 

 

/<<this block is unity offical guid about 'assetdatabase.find assets'


                    string[] guids = AssetDatabase.FindAssets("t:Texture", new string[] { tempPath }); 

 

/<< find sprites(t:texture = type filter) in the target folder and put in to guid[] each file

/<< get the number of guid[](comand guids[].length)

/<< It will be used to how many times the block should be run


                    List tempSprites = new List(); 
                    for (int i = 0; i < guids.Length; ++i) 
                    { 
                        string path = AssetDatabase.GUIDToAssetPath(guids[i]); 

/<< we know array value is pointer(memory address), not a real value so convert pointer to real data


                        tempSprites.AddRange(AssetDatabase.LoadAllAssetsAtPath(path).OfType().ToArray()); 
                    } 

/<<create path string from guids[i](pointer) every each time

                    if (tempSprites.Count > 0) 
                    { 
                        outputPath = tempPath; 
                        sprites = tempSprites.ToArray(); 

/<<that setting outputpath and sprite array when folder have sprite more than 1 
                    } 
                    else 
                    { 
                        EditorUtility.DisplayDialog("경고", "해당 폴더에 스프라이트 파일 정보가 존재 하지 않습니다.\n폴더 확인 후 다시 이용해주세요.", "확인"); 
                        Reset(); 
                    } 
                } 
                else 
                    Reset(); 
            } 
        } 

/<<under block is using when type is not eaqual 0

/<<create dummy animation clip with dummy sprite name
        else 
        { 
            spritenamefield = EditorGUILayout.TextField("스프라이트이름 : ", spritenamefield); 
            spritefolderfield = EditorGUILayout.TextField("스프라이트지정폴더 : ", spritefolderfield); 
            StarSpriteNum = EditorGUILayout.IntField("시작스프라이트 번호 : ", StarSpriteNum); 
            DummynameInterval = EditorGUILayout.IntField("더미이름간격", DummynameInterval); 
            oridummysprite = EditorGUILayout.TextField("원본스프라이트경로 : ", oridummysprite); 
            RemoveDummySprite = EditorGUILayout.Toggle("더미스프라이트 삭제 : ", RemoveDummySprite); 
/<<variable block appear on custom editor, dummy data control based on variables


            if (GUILayout.Button("에셋 생성", GUILayout.Height(40))) 
            { 
                if (spritenamefield != null) 
                { 
                   var Sourceasset = oridummysprite; 

                    //실질적으로 작동하는 부분 
                    //순서는 스프라이트 에셋 생성 > 애니매이션 키 생성 > 스프라이트 에셋 삭제 

                    for (int i = 0; i < frameSamples; ++i) 
                    { 
                        int filenum = ((i * DummynameInterval) + StarSpriteNum); 
                        string ilocal = string.Format("{0:00}", filenum);                                      

                        var tempsprite = spritefolderfield + "/" + spritenamefield +"_"+ilocal + ".png"; 

                        AssetDatabase.CopyAsset(Sourceasset, tempsprite); 

/<< create temp file name from current count(i) + interval + first number(startspritenum)

/<<copy origin image(sourceasset) to temporary image by current name(tempsprite)

     using 'assetdatbase.copyasset'

/<<so we had temp image file named 'dummy name + i*dummy*strtnum+.png'

/<<we got the temporary image of number 'i' using the for the statement.

 

                   } 

/<<this block is very useful code block, also quoted many times  in official unity_reference


                    var tempPath = spritefolderfield; /<< create path, it used in create final path
                    string[] guids = AssetDatabase.FindAssets("t:Texture", new string[] { tempPath }); 

/<<find assets filtered by texture(T:texture)in folder(temp path) and put it to guides[]  
                    List tempSprites = new List(); 

/<<create new array(list) it using at spritearray

                    for (int i = 0; i < guids.Length; ++i) 
                    { 
                        string path = AssetDatabase.GUIDToAssetPath(guids[i]); 

/<<create path with guid[i] 

/<< we already know array is not real value it just memory address
       so we have to  convert address to the value

/<< depend on 'for statement' path is real value for each gui[i]
                        tempSprites.AddRange(AssetDatabase.LoadAllAssetsAtPath(path).OfType().ToArray()); 
                    } 
/<<tempsprite' each line filled by path[i]

now we take filled array with each sprite's path = tempsprite[] = all spriteimage's path

                    outputPath = tempPath; 
                    sprites = tempSprites.ToArray(); /<<list to array



                    // 배열 리스트 생성 
                      CreateAnimationClip(); 
/<<create clip method


                    //사용하고난 더미 파일 삭제 
                    if (RemoveDummySprite == true) /<<check branch, remove used sprites
                    { 
                        for (int i = 0; i < frameSamples; ++i) 
                        { 
                            int filenum = ((i * DummynameInterval) + StarSpriteNum); 
                            //string ilocal = fileNum.ToString(); 
                            string ilocal = string.Format("{0:00}", filenum); 
/<<same action create situation
                            var tempsprite = spritefolderfield + "/" + spritenamefield + "_" + ilocal + ".png"; 
                            AssetDatabase.DeleteAsset(tempsprite); 

/<<delete all created sprites
                        } 
                    } 
                    else 
                    { 
                        Reset(); /<<reset varialbe value
                    } 
                     
} 
                else 
                { 
                    EditorUtility.DisplayDialog("경고", "생성될 파일 이름을 지정해주세요.", "확인"); 
                    Reset(); 
                } 
            } 
          } 

/<<
        frameSamples = Mathf.Max(0, EditorGUILayout.IntField("프레임 : ", frameSamples)); 
        spriteInterval = Mathf.Max(1, EditorGUILayout.IntField("간격 : ", spriteInterval)); 

        GUILayout.Label("총 스프라이트 갯수 : " + (sprites != null ? sprites.Length : 0)); 
        GUI.enabled = targetObj != null; 
/<< this block is show up variables(used on gui)

/<<
        if (GUILayout.Button("애니메이션 생성", GUILayout.ExpandHeight(true))) 
        { 
             CreateAnimationClip(); 
             } 
    } 

    void CreateAnimationClip() /<<create clip method
    { 
        AnimationClip animClip = new AnimationClip(); 
        animClip.frameRate = 30; 
        animClip.wrapMode = WrapMode.Loop; 
/<<base variable set

/<<we using this variables at create animartion clip

 

/<< Now, we were asked to create an animation clip and assign it to the controller.

/<<proccess is 1. create animationcontroller/2.createclips/3.create animation key/4.assign to controller

       AnimatorController controller = new AnimatorController(); /<<create new controller
        controller.AddLayer("newlayer"); 

/<<create new unity animation controller is don't have any layer first create new layer

/<<we had useable animationcontroller with empty layer it using later 
       
        var spriteBinding = EditorCurveBinding.PPtrCurve("", typeof(SpriteRenderer), "m_Sprite"); 

/<<set variable for creating animation clips track 

/<<'spritebinding' define tracks properties for what type use animation

 


/ << This code block is familiar to us, creates array ~, sets a variable, and repeats it by the number of [i].

/ << there are only a few codes that we meet the first time


        ObjectReferenceKeyframe[] spriteKeyFrames = new ObjectReferenceKeyframe[sprites.Length]; /array
        for (int i = 0; i < sprites.Length; i++) /repeat i times
        { 
            spriteKeyFrames[i] = new ObjectReferenceKeyframe(); 
            float unitTime = 1f / 30; /make frame time(match time to frame time)


            spriteKeyFrames[i].time = spriteInterval * i * unitTime; 

/we go to exact the time and create key(not every frame)
            spriteKeyFrames[i].value = sprites[i]; 

/set sprite on key

/if you make this action on 'runtime' you must using time.deltatime

but i'm not use time, just divide 'time/frame' because receiv request is only run in editor 
        } 

 

        string filePathLocal; 
        string filePathConLocal; 
        AnimationUtility.SetObjectReferenceCurve(animClip, spriteBinding, spriteKeyFrames); 

//create animation track (see official reference), here using parameters what we created on first time


         
            if (fileType == 0) 
            { 
                filePathLocal = Path.Combine(outputPath, (targetObj.name) + ".anim"); 
                filePathConLocal = Path.Combine(outputPath, (targetObj.name) + ".controller"); 
            } 
            else 
            { 
                filePathLocal = Path.Combine(outputPath, spritenamefield + ".anim"); 
                filePathConLocal = Path.Combine(outputPath, spritenamefield + ".controller"); 
            } 
// filetype = 1 = make real data, 2 = dummy data


        AssetDatabase.CreateAsset(controller, filePathConLocal); 
        AssetDatabase.CreateAsset(animClip, filePathLocal); 
        AssetDatabase.SaveAssets(); 
        AssetDatabase.Refresh(); 
//finally save assets
        controller.AddMotion(animClip, 0); 

// clip assign to layer 0

    }

 

 

 

///////////////////////// full code ////////////////////////

 

 

sing System.Collections; 
using System.Collections.Generic; 
using UnityEngine; 
using UnityEditor; 
using System.IO; 
using System.Linq; 
using UnityEditor.Animations; 

public class CreateSpriteAnimationClip : EditorWindow 
{ 
    static CreateSpriteAnimationClip myWindow; 
    Object targetObj; 


    int frameSamples = 10; 
    int spriteInterval = 1; 

    string[] fileTypeNames = { "폴더", "더미생성" }; 
    int fileType = 0; 

    // 더미생성용 변수 
    string spritenamefield = "dummyName"; 
    string spritefolderfield = "Assets/artResources/"; 
    string oridummysprite = "Assets/artResources/Characters/spritedefault.png"; 
    int StarSpriteNum = 0; 
    public AnimationClip MyAnimator = null; 
    public Texture2D dummytexture; 
    Sprite Dummysprite; 
    bool RemoveDummySprite = true; 
    int DummynameInterval = 1; 



    string outputPath; 
    Sprite[] sprites; 

    [MenuItem("Window/CreateSpriteAnimationClip")] 
    static void Init() 
    { 
        // Get existing open window or if none, make a new one: 
        myWindow = (CreateSpriteAnimationClip)GetWindow(typeof(CreateSpriteAnimationClip)); 
        myWindow.Show(); 
    } 

    private void OnDestroy() 
    { 
        CloseSpriteViewerWindow(); 
    } 

    void CloseSpriteViewerWindow() 
    { 
        Reset(); 
    } 

    void Reset() 
    { 
        outputPath = ""; 
        sprites = null; 
        targetObj = null; 
    } 


    void OnGUI() 
    { 
        int type = EditorGUILayout.Popup(fileType, fileTypeNames, GUILayout.Height(20)); 
        if (fileType != type) 
        { 
            CloseSpriteViewerWindow(); 
            fileType = type; 
            Reset(); 
        } 

        if (fileType == 0) 
        { 
            var obj = EditorGUILayout.ObjectField("폴더 :", targetObj, typeof(DefaultAsset), true); 
            if (targetObj != obj) 
            { 
                targetObj = obj; 
                if (targetObj != null) 
                { 
                    var tempPath = AssetDatabase.GetAssetPath(targetObj); 
                    string[] guids = AssetDatabase.FindAssets("t:Texture", new string[] { tempPath }); 
                    List tempSprites = new List(); 
                    for (int i = 0; i < guids.Length; ++i) 
                    { 
                        string path = AssetDatabase.GUIDToAssetPath(guids[i]); 
                        tempSprites.AddRange(AssetDatabase.LoadAllAssetsAtPath(path).OfType().ToArray()); 
                    } 

                    if (tempSprites.Count > 0) 
                    { 
                        outputPath = tempPath; 
                        sprites = tempSprites.ToArray(); 
                    } 
                    else 
                    { 
                        EditorUtility.DisplayDialog("경고", "해당 폴더에 스프라이트 파일 정보가 존재 하지 않습니다.\n폴더 확인 후 다시 이용해주세요.", "확인"); 
                        Reset(); 
                    } 
                } 
                else 
                    Reset(); 
            } 
        } 
        else 
        { 
            spritenamefield = EditorGUILayout.TextField("스프라이트이름 : ", spritenamefield); 
            spritefolderfield = EditorGUILayout.TextField("스프라이트지정폴더 : ", spritefolderfield); 
            StarSpriteNum = EditorGUILayout.IntField("시작스프라이트 번호 : ", StarSpriteNum); 
            DummynameInterval = EditorGUILayout.IntField("더미이름간격", DummynameInterval); 
            oridummysprite = EditorGUILayout.TextField("원본스프라이트경로 : ", oridummysprite); 
            RemoveDummySprite = EditorGUILayout.Toggle("더미스프라이트 삭제 : ", RemoveDummySprite); 

            if (GUILayout.Button("에셋 생성", GUILayout.Height(40))) 
            { 
                if (spritenamefield != null) 
                { 
                    // Create a simple material asset 

                
                    var Sourceasset = oridummysprite; 

                    //실질적으로 작동하는 부분 
                    //순서는 스프라이트 에셋 생성 > 애니매이션 키 생성 > 스프라이트 에셋 삭제 

                    for (int i = 0; i < frameSamples; ++i) 
                    { 
                        int filenum = ((i * DummynameInterval) + StarSpriteNum); 
                        //string ilocal = fileNum.ToString(); 
                        string ilocal = string.Format("{0:00}", filenum); 
                         
                         
                        var tempsprite = spritefolderfield + "/" + spritenamefield +"_"+ilocal + ".png"; 
                        AssetDatabase.CopyAsset(Sourceasset, tempsprite); 

                    } 


                    var tempPath = spritefolderfield; 
                    string[] guids = AssetDatabase.FindAssets("t:Texture", new string[] { tempPath }); 
                    List tempSprites = new List(); 
                    for (int i = 0; i < guids.Length; ++i) 
                    { 
                        string path = AssetDatabase.GUIDToAssetPath(guids[i]); 
                        tempSprites.AddRange(AssetDatabase.LoadAllAssetsAtPath(path).OfType().ToArray()); 
                    } 

                    outputPath = tempPath; 
                    sprites = tempSprites.ToArray(); 

                    // 배열 리스트 생성 
                      CreateAnimationClip(); 

                    //사용하고난 더미 파일 삭제 
                    if (RemoveDummySprite == true) 
                    { 
                        for (int i = 0; i < frameSamples; ++i) 
                        { 
                            int filenum = ((i * DummynameInterval) + StarSpriteNum); 
                            //string ilocal = fileNum.ToString(); 
                            string ilocal = string.Format("{0:00}", filenum); 


                            var tempsprite = spritefolderfield + "/" + spritenamefield + "_" + ilocal + ".png"; 
                            AssetDatabase.DeleteAsset(tempsprite); 
                        } 
                    } 
                    else 
                    { 
                        Reset(); 
                    } 
                     
} 
                else 
                { 
                    EditorUtility.DisplayDialog("경고", "생성될 파일 이름을 지정해주세요.", "확인"); 
                    Reset(); 
                } 
            } 
          } 


        frameSamples = Mathf.Max(0, EditorGUILayout.IntField("프레임 : ", frameSamples)); 

        spriteInterval = Mathf.Max(1, EditorGUILayout.IntField("간격 : ", spriteInterval)); 

        GUILayout.Label("총 스프라이트 갯수 : " + (sprites != null ? sprites.Length : 0)); 

        GUI.enabled = targetObj != null; 


        if (GUILayout.Button("애니메이션 생성", GUILayout.ExpandHeight(true))) 
        { 
             CreateAnimationClip(); 
             } 
    } 

    void CreateAnimationClip() 
    { 
        AnimationClip animClip = new AnimationClip(); 
        animClip.frameRate = 30; 
        animClip.wrapMode = WrapMode.Loop; 


        AnimatorController controller = new AnimatorController(); 

        controller.AddLayer("newlayer"); 
       
        var spriteBinding = EditorCurveBinding.PPtrCurve("", typeof(SpriteRenderer), "m_Sprite"); 

        ObjectReferenceKeyframe[] spriteKeyFrames = new ObjectReferenceKeyframe[sprites.Length]; 
        for (int i = 0; i < sprites.Length; i++) 
        { 
            spriteKeyFrames[i] = new ObjectReferenceKeyframe(); 
            float unitTime = 1f / 30; 
            spriteKeyFrames[i].time = spriteInterval * i * unitTime; 
            spriteKeyFrames[i].value = sprites[i]; 
        } 
        string filePathLocal; 
        string filePathConLocal; 
        AnimationUtility.SetObjectReferenceCurve(animClip, spriteBinding, spriteKeyFrames); 
         
            if (fileType == 0) 
            { 
                filePathLocal = Path.Combine(outputPath, (targetObj.name) + ".anim"); 
                filePathConLocal = Path.Combine(outputPath, (targetObj.name) + ".controller"); 
            } 
            else 
            { 
                filePathLocal = Path.Combine(outputPath, spritenamefield + ".anim"); 
                filePathConLocal = Path.Combine(outputPath, spritenamefield + ".controller"); 
            } 

        AssetDatabase.CreateAsset(controller, filePathConLocal); 
         
        AssetDatabase.CreateAsset(animClip, filePathLocal); 

        AssetDatabase.SaveAssets(); 
        AssetDatabase.Refresh(); 
 
        controller.AddMotion(animClip, 0); 

    }