はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】エディタ拡張でドット絵を書くやつを作ってみた

はじめに

最近エディタ拡張の勉強をしていて、ドット絵を書くEditor Windowを作ってみたので備忘録としてここにそのコードを残しておきます。


コード

これらのコードをEditorフォルダの中にいれます。

using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text.RegularExpressions;

public class DrawPicture : EditorWindow
{

    Color col = new Color(1,1,1);

    //作成時に参照するドット絵の幅の値
    int x_previous = 5;
    int y_previous = 5;

    //ドット絵作成時にx,yの値を入れる実際に用いるx,yの値
    int x;
    int y;

    //すでにドット絵のパレットを作成しているかどうかのフラグ
    bool _isCeated = false;

    //ドット絵の色を保存しておく変数
    Color[,] dotColors;

    //最初に開いたときに前の最後の状態を読み込むためのスクリプタブルオブジェクト
    [SerializeField] PictureScriptableObject pictureScriptableObject = null;
    private static readonly string SAVE_ASSET_PATH = "Assets/Hanachiru/Editor/PictureScriptableObject.asset";


    [MenuItem("Editor/DrawPicture")]
    static public void Open()
    {
        var window = EditorWindow.GetWindow<DrawPicture>();

        //前回の最後の状態をロード
        var saveAsset = AssetDatabase.LoadAssetAtPath<PictureScriptableObject>(SAVE_ASSET_PATH);
        if (saveAsset == null)
        {
            saveAsset = CreateInstance<PictureScriptableObject>();
            AssetDatabase.CreateAsset(saveAsset, SAVE_ASSET_PATH);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            window.pictureScriptableObject = saveAsset;
        }
        else
        {
            window.pictureScriptableObject = saveAsset;
            window.x = saveAsset.X;
            window.y = saveAsset.Y;
            window.dotColors = new Color[saveAsset.X, saveAsset.Y];
            window.ArrayCopyToArray(saveAsset.DotsColor, window.dotColors);
            window._isCeated = true;
        }
    }

    private void OnGUI()
    {
        DrawToolbar();

        EditorGUI.BeginChangeCheck();
        DrawPalette();
        if (EditorGUI.EndChangeCheck())
        {
            UpdateScriptableObject();
        }
    }

    //パラメータを更新したときに呼ばれる
    private void UpdateScriptableObject()
    {
        if(pictureScriptableObject != null)
        {
            pictureScriptableObject.X = x;
            pictureScriptableObject.Y = y;
            pictureScriptableObject.ResetArray();
            ArrayCopyToArray(dotColors, pictureScriptableObject.DotsColor);
            EditorUtility.SetDirty(pictureScriptableObject);
        }
    }

    //上のツールバーを表示する
    private void DrawToolbar()
    {
        using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar, GUILayout.ExpandWidth(true)))
        {
            if (GUILayout.Button("New...", EditorStyles.toolbarButton))
            {
                Debug.Log("Create New Picture.");
                CreateNewPicture();
            }
            if (GUILayout.Button("Load...", EditorStyles.toolbarButton))
            {
                Debug.Log("Load New Picture.");
                SerchScriptableObject.Open();
            }
            if (GUILayout.Button("Save...", EditorStyles.toolbarButton))
            {
                Debug.Log("Save Picture.");
                SavePicture();

            }
            int leftValue = 5;
            int rightValue = 64;
            EditorGUILayout.LabelField("X: ",GUILayout.Width(15));
            x_previous = EditorGUILayout.IntSlider(x_previous, leftValue, rightValue);
            EditorGUILayout.LabelField("Y: ", GUILayout.Width(15));
            y_previous = EditorGUILayout.IntSlider(y_previous, leftValue, rightValue);
            col = EditorGUILayout.ColorField("Color: ", col);
        }

    }

    private void DrawPalette()
    {
        if (_isCeated)
        {
            EditorGUILayout.BeginVertical();
            {
                for (int i = 0; i < y; i++)
                {
                    EditorGUILayout.BeginHorizontal();
                    {
                        for (int j = 0; j < x; j++)
                        {
                            if (dotColors[j, i].a == 0)
                            {
                                CreateTrigger(j,i);
                            }
                            else
                            {
                                using (new BackgroundColorScope(dotColors[j, i]))
                                {
                                    CreateTrigger(j, i);
                                }
                            }
                        }
                    }
                    EditorGUILayout.EndHorizontal();
                }
                EditorGUILayout.EndVertical();
            }
        }
    }

    private void CreateNewPicture()
    {
        x = x_previous;
        y = y_previous;
        dotColors = new Color[x, y];
        _isCeated = true;
    }

    public static void LoadPicture(PictureScriptableObject loadAsset)
    {
        if (loadAsset != null)
        {
            var window = EditorWindow.GetWindow<DrawPicture>();
            var saveAsset = AssetDatabase.LoadAssetAtPath<PictureScriptableObject>(SAVE_ASSET_PATH);
            window.pictureScriptableObject.X = loadAsset.X;
            window.pictureScriptableObject.Y = loadAsset.Y;
            window.pictureScriptableObject.ResetArray();
            window.ArrayCopyToArray(loadAsset.DotsColor, window.pictureScriptableObject.DotsColor);
            EditorUtility.SetDirty(window.pictureScriptableObject);
            window.pictureScriptableObject = saveAsset;
            window.x = loadAsset.X;
            window.y = loadAsset.Y;
            window.ArrayCopyToArray(loadAsset.DotsColor, window.dotColors);
        }
    }

    private void SavePicture()
    {
        // 保存先のファイルパスを取得する
        var filePath = EditorUtility.SaveFilePanel("Save", "Assets", "PictureDate", "asset");

        string[] hogePath = Regex.Split(filePath,"/Assets/");
        string assetPath = "Assets/" + hogePath[1];

        // パスが入っていれば選択されたということ(キャンセルされたら入ってこない)
        if (!string.IsNullOrEmpty(filePath))
        {
            //保存処理

            //変更ここから
            AssetDatabase.StartAssetEditing();

            PictureScriptableObject saveAsset = new PictureScriptableObject();
            AssetDatabase.CreateAsset((ScriptableObject)saveAsset, assetPath);
            saveAsset.X = x;
            saveAsset.Y = y;
            saveAsset.ResetArray();
            //saveAsset.DotsColor = dotColors;
            ArrayCopyToArray(dotColors, saveAsset.DotsColor);

            //変更ここまで
            AssetDatabase.StopAssetEditing();

            EditorUtility.SetDirty(saveAsset);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }
    }

    private void CreateTrigger(int x, int y)
    {
        if (GUILayout.Toggle(false, "", (GUIStyle)"OL Titleleft", GUILayout.MinHeight(10), GUILayout.MaxHeight(15), GUILayout.MinWidth(5), GUILayout.MaxWidth(20), GUILayout.MinWidth(5)))
        {
            //色を着色する
            if (col.a == 0)
            {
                dotColors[x, y] = new Color(0, 0, 0, 0);
            }
            else
            {
                dotColors[x, y] = col;
            }

        }
    }

    private void ArrayCopyToArray(PictureScriptableObject.Colors[] array1, Color[,] array2)
    {
        if (array1 != null && array2 != null)
        {
            for (int i = 0; i < array2.GetLength(0); i++)
            {
                for (int j = 0; j < array2.GetLength(1); j++)
                {
                    array2[i, j] = array1[i].color[j];
                }
            }
        }
        else
        {
            Debug.Log("配列がnullです");
        }
    }
    private void ArrayCopyToArray(Color[,] array1 , PictureScriptableObject.Colors[] array2)
    {
        if (array1 != null && array2 != null)
        {
            for (int i = 0; i < array1.GetLength(0); i++)
            {
                for (int j = 0; j < array1.GetLength(1); j++)
                {
                   array2[i].color[j] = array1[i,j];
                }
            }
        }
        else
        {
            Debug.Log("配列がnullです");
        }
    }
    private void ArrayCopyToArray(PictureScriptableObject.Colors[] array1, PictureScriptableObject.Colors[] array2)
    {
        if (array1 != null && array2 != null)
        {
            for (int i = 0; i < array1.Length; i++)
            {
                for (int j = 0; j < array1[0].color.Length; j++)
                {
                    array2[i].color[j] = array1[i].color[j];
                }
            }
        }
        else
        {
            Debug.Log("配列がnullです");
        }
    }

}

public class BackgroundColorScope : GUI.Scope
{
    public BackgroundColorScope(Color color)
    {
        GUI.backgroundColor = color;
    }

    protected override void CloseScope()
    {
        GUI.backgroundColor = new Color(1, 1, 1, 1);
    }
}

public class SerchScriptableObject : EditorWindow
{
    PictureScriptableObject pictureScriptableObject;
    static SerchScriptableObject serchWindow;

    public static void Open()
    {
        if(serchWindow == null)
        {
            serchWindow = CreateInstance<SerchScriptableObject>();
        }
        serchWindow.ShowUtility();
        serchWindow.maxSize = new Vector2(300,38);
        serchWindow.minSize = new Vector2(300,38);
    }

    private void OnGUI()
    {
        pictureScriptableObject = EditorGUILayout.ObjectField("Load PictureScriptObject", pictureScriptableObject, typeof(PictureScriptableObject), true) as PictureScriptableObject;
        if (GUILayout.Button("Load...", EditorStyles.toolbarButton))
        {
            DrawPicture.LoadPicture(pictureScriptableObject);
            var window = GetWindow<SerchScriptableObject>();
            window.Close();
        }
    }
}
using UnityEngine;

[CreateAssetMenu(menuName = "Editor/PictureParameter", fileName = "PictureParameter"),System.Serializable]
public class PictureScriptableObject : ScriptableObject
{

    [SerializeField] private int x = 5;
    [SerializeField] private int y = 5;
    //dotsColor[x].color[y] 理由は2次元配列はこうしないとシリアライズ化されないので保存されないから
    [SerializeField] private Colors[] dotColors;

    [System.Serializable]
    public struct Colors
    {
        public Color[] color;
    }

    public int X
    {
        get { return x; }
        set { x = value; }
    }
    public int Y
    {
        get { return y; }
        set { y = value; }
    }
    public Colors[] DotsColor
    {
        get { return this.dotColors; }
        set { dotColors = value; }
    }

    public void ResetArray()
    {
        DotsColor = new Colors[x];
        for(int i = 0;i < x; i++)
        {
            DotsColor[i].color = new Color[y];
        }
    }

}
using UnityEngine;
using UnityEditor;
using System.Text.RegularExpressions;

public class PngConverter : EditorWindow
{
    PictureScriptableObject pictureScriptableObject;

    [MenuItem("Editor/ConvertDateToPNG")]
    public static void Open()
    {
        var window = EditorWindow.GetWindow<PngConverter>();
        window.maxSize = new Vector2(300, 38);
        window.minSize = new Vector2(300, 38);
    }

    private void OnGUI()
    {
        pictureScriptableObject = EditorGUILayout.ObjectField("Load PictureScriptObject", pictureScriptableObject, typeof(PictureScriptableObject), true) as PictureScriptableObject;
        if (GUILayout.Button("Convert Date To PNG...", EditorStyles.toolbarButton))
        {
            CreatePNG();
        }
    }

    private void CreatePNG()
    {
        if(pictureScriptableObject != null)
        {
            //バイト配列に変換する
            var bytes = CreateTexture2d().EncodeToPNG();

            // 保存先のファイルパスを取得する
            var filePath = EditorUtility.SaveFilePanel("Save", "Assets", "image", "png");

            string[] hogePath = Regex.Split(filePath, "/Assets/");
            string assetPath = "Assets/" + hogePath[1];

            //ファイルを保存
            System.IO.File.WriteAllBytes(assetPath, bytes);

            AssetDatabase.Refresh();

            Debug.Log("PNGの変換に成功しました");
        }
    }

    private Texture2D CreateTexture2d()
    {
        Texture2D texture;
        texture = new Texture2D(pictureScriptableObject.X, pictureScriptableObject.Y, TextureFormat.ARGB32, false);
        for (int y = 0; y < texture.height; y++)
        {
            for (int x = 0; x < texture.width; x++)
            {
                try
                {
                    texture.SetPixel(x, y, pictureScriptableObject.DotsColor[x].color[texture.height - y - 1]);
                }
                catch
                {
                    Debug.Log("なんらかの理由でデータが壊れています");
                }
            }
        }
        texture.Apply();

        return texture;
    }
}

さいごに

暇があったら細かい場所の説明や注意点をかいてみようと思います。