【Unityエディター拡張】キーボードでアセットを移動させるエディター拡張を作ってみた

#はじめに
みなさん、アセットを移動させるときどうしていますか？  マウスを使ってドラッグ＆ドロップしてませんか？

マウスを使うの面倒くさいですよね。

<br>
<img width = 700 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/d6ccca06-2c92-d89b-1198-12fba54adade.png">


というわけでアセットの移動をキーボード操作でできるようにするエディター拡張を作ってみました。

#使い方

1.移動させたいアセットをクリック。
![image](https://qiita-image-store.s3.amazonaws.com/0/44288/9ee9c473-add7-45cb-7488-6aa8947dda7f.png)

2.キーボードのMを押下。
![image](https://qiita-image-store.s3.amazonaws.com/0/44288/98fad31d-28e6-4643-e32c-7d18896b816f.png)
押すとウィンドウが出てきます.

3.移動先のパスを入力してEnter。
![image](https://qiita-image-store.s3.amazonaws.com/0/44288/0d475947-a107-e7bd-33a0-7d210d68fc44.png)

4.アセットが移動しました。　楽ですね。(完)
![image](https://qiita-image-store.s3.amazonaws.com/0/44288/2c2a643c-f619-6c67-afa2-ed3398fd5766.png)

#ソースコード
```AssetMoveWindow.cs
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
using System.Reflection;

[InitializeOnLoad]
public class AssetMoveWindow : EditorWindow
{
    readonly static char SeparateChar = Path.AltDirectorySeparatorChar;
    private const string FocusName = "focus";
    private const string RootPath = "Assets";
    private const KeyCode KeyCodeRename = KeyCode.M;
    private const int SlashSize = 24;
    private const int WindowPositionX = 0;
    private const int WindowPositionY = 16;
    private const int WindowSizeX = 800;
    private const int WindowSizeY = 300;
    private string titleText;
    private string baseText;
    private string editText;
    private List<string> paths;
    private string basePath;
    private bool needSetFocus;

    /// <summary>
    /// エディター起動に呼ばれる
    /// </summary>
    static AssetMoveWindow()
    {
        EditorApplication.CallbackFunction function = () =>
        {
            // キーが押されたらEditorWindowを開く
            if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCodeRename && Selection.activeObject != null)
            {
                OpenWindow();
            }
        };

        FieldInfo info = typeof(EditorApplication).GetField("globalEventHandler", BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic);
        EditorApplication.CallbackFunction functions = (EditorApplication.CallbackFunction)info.GetValue(null);
        functions += function;
        info.SetValue(null, (object)functions);
    }

    /// <summary>
    /// ウィンドウ 初期化
    /// </summary>
    void Init()
    {
        string path = AssetDatabase.GetAssetPath(Selection.activeObject);
        if (File.Exists(path))
        {
            this.titleText = "Enter the new Path for the file";
        }
        else
        {
            this.titleText = "Enter the new Path for the folder";
        }

        this.baseText = path;
        this.editText = path;
        this.paths = new List<string>(path.Split(SeparateChar));
        this.basePath = path;

        this.needSetFocus = true;
    }

    /// <summary>
    /// ウィンドウ　描画処理
    /// </summary>
    void OnGUI()
    {
        bool changed = false;

        EditorGUI.indentLevel = 0;
        EditorGUILayout.LabelField(this.titleText);

        EditorGUI.indentLevel = 1;
        EditorGUILayout.LabelField(this.baseText);

        EditorGUILayout.BeginHorizontal();

        int focusIndex = this.paths.Count - 2;
        int assetIndex = this.paths.Count - 1;
        EditorGUI.BeginChangeCheck();
        for (int pi = 0; pi < this.paths.Count; pi++)
        {
            var path = this.paths[pi];
            string nextPath = path;
            int textWidth = GetTextWidth(path);
            int slashWidth = SlashSize;

            // 選択アセットの親ディレクトリをフォーカスをのせる対象にする
            if (pi == focusIndex)
            {
                GUI.SetNextControlName(FocusName);
            }

            if (pi == assetIndex)
            {
                EditorGUILayout.LabelField(path, GUILayout.Width(textWidth));
            }
            else
            {
                nextPath = EditorGUILayout.TextField(path, GUILayout.Width(textWidth));
                EditorGUILayout.LabelField(SeparateChar.ToString(), GUILayout.Width(slashWidth));
            }
            this.paths[pi] = nextPath;
        }
        EditorGUILayout.EndHorizontal();

        if (GUI.changed)
        {
            this.paths.RemoveAll(x => x == string.Empty);
        }

        // Enterキーを押したらAsset移動
        bool enter = Event.current.type == EventType.KeyUp && Event.current.keyCode == KeyCode.Return;
        if (enter)
        {
            string oldPath = this.basePath;
            string newPath = string.Join(Path.DirectorySeparatorChar.ToString(), this.paths.ToArray());
            string message = AssetDatabase.MoveAsset(oldPath, newPath);

            if (message != string.Empty)
            {
                Debug.LogWarning("Couldn't  MoveAsset : " + message);
                this.Init();
            }
            else
            {
                Debug.LogFormat("Move : {0} -> {1}", oldPath, newPath);
                this.Close();
            }

        }

        // ESCキーを押したらウィンドウを閉じる
        bool esc = Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape;
        if (esc)
        {
            this.Close();
        }


        if (needSetFocus)
        {
            // TextFieldにフォーカスをのせる
            EditorGUI.FocusTextInControl(FocusName);

            needSetFocus = false;
        }
    }

    ///// <summary>
    ///// ウィンドウ 開く
    ///// </summary>
    //[MenuItem("Window/Asset Move Window %m")]
    static void OpenWindow()
    {
        var window = GetWindow<AssetMoveWindow>();
        window.position = new Rect(WindowPositionX, WindowPositionY, WindowSizeX, WindowSizeY);
        window.Init();
    }

    ///// <summary>
    ///// ウィンドウが開けるかチェック
    ///// </summary>
    //[MenuItem("Window/Asset Move Window %m", true)]
    //static bool ChkOpenWindow()
    //{
    //    return Selection.activeObject != null && Selection.activeGameObject == null;
    //}

    /// <summary>
    /// テキストサイズ取得
    /// </summary>
    static int GetTextWidth(string text)
    {
        return text.Length * 10;
    }
}
#endif //UNITY_EDITOR
```