【Unity(C#)】UniRxを使ってEditor上でDebugしてみた②

[前回の記事](https://qiita.com/OKsaiyowa/items/b285ccf9e12f11fbebfc)の続きです。


リファクタリングとバグ修正です。

##PlayMode→EditMode
PlayMode中に動かしたオブジェクトはEditModeに切り替えると元に戻ります。
![MovePlayMode.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/6fcae400-7b3c-3236-4499-1c3cf55d6667.gif)
これはもともとのEditorの機能ですよね。

##EditMode→PlayMode
前回の記事でEditor上でDebugできることは確認できたのですが、
動かしている最中にPlayModeに移ると、その位置で停止してしまいます。
![MoveEditorMode.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/d0b5d20f-d002-85f1-e9de-bfa232b4e28f.gif)

これはなんとかしないと、かえって効率が悪くなりそうです。

##EditorApplication.playModeStateChanged
**Editor上で再生状態が変更されたときに呼びだされるコールバック**です。
EditMode→PlayModeに移る瞬間にポジションをもとに戻してあげればいいので
`EditorApplication.playModeStateChanged`にポジションを戻す処理を登録しとけばいけそうです。

```C#:Editor拡張です
        static MyDebug rootClass;

        //コンストラクタでPlayMode,EditModeの切り替え時の処理を登録
        static MyDebugEditor()
        {
            EditorApplication.playModeStateChanged += OnPlayMode;
        }

        static void OnPlayMode(PlayModeStateChange playModeStateChange)
        {
            //途中でPlayModeに切り替えても元に戻す
            if (playModeStateChange == PlayModeStateChange.ExitingEditMode)
            {
                //ポジションをもとに戻す処理を書いたメソッド
                rootClass.ResetPosition();
            }
        }
```
[PlayMode⇔EditMode](https://docs.unity3d.com/ja/current/ScriptReference/PlayModeStateChange.html)の変更がそれぞれ用意されています。
今回は`ExitingEditMode`を使うのでEditModeが終了してPlayModeが始まる直前です。


##[ExecuteInEditMode]
`[ExecuteInEditMode]`を使うことでコルーチンのコードがスッキリしました。
まずは使う前の記事です。

```C#:コルーチンの処理

        x_Abs = Mathf.Abs(this.gameObject.transform.position.x - targetObj.transform.position.x);
        y_Abs = Mathf.Abs(this.gameObject.transform.position.y - targetObj.transform.position.y);
        z_Abs = Mathf.Abs(this.gameObject.transform.position.z - targetObj.transform.position.z);
        while (x_Abs > 0 || y_Abs > 0 || z_Abs > 0)
        {
            x_Abs = Mathf.Abs(this.gameObject.transform.position.x - targetObj.transform.position.x);
            y_Abs = Mathf.Abs(this.gameObject.transform.position.y - targetObj.transform.position.y);
            z_Abs = Mathf.Abs(this.gameObject.transform.position.z - targetObj.transform.position.z);
            this.gameObject.transform.position = Vector3.MoveTowards(this.gameObject.transform.position, targetObj.transform.position, speed);
            yield return null;
        }
```
**EditModeでは、MonoBehaviourのUpdate内の処理は呼ばれない**ので
本来Update内に記述しておけばスッキリするコードをコルーチン内に長々と書いています。

しかし、`[ExecuteInEditMode]`を使うことでEditModeでもUpdateを呼んでくれます。
下記が修正後です。

```C#:修正後

[ExecuteInEditMode]
public class MyDebug : MonoBehaviour{

void Update()
    {
        x_Abs = Mathf.Abs(this.gameObject.transform.position.x - targetTrasform.position.x);
        y_Abs = Mathf.Abs(this.gameObject.transform.position.y - targetTrasform.position.y);
        z_Abs = Mathf.Abs(this.gameObject.transform.position.z - targetTrasform.position.z);
    }

    IEnumerator MoveCoroutine()
    {
        while (x_Abs > 0 || y_Abs > 0 || z_Abs > 0)
        {
            this.gameObject.transform.position = Vector3.MoveTowards(this.gameObject.transform.position, targetTrasform.position, speed);
            yield return null;
        }
    }
```


##最終的なコード
```C#
using System.Collections;
using UnityEngine;
using UnityEditor;
using UniRx;

[ExecuteInEditMode]
public class MyDebug : MonoBehaviour
{
    [SerializeField]
    Transform targetTrasform;

    [SerializeField, Range(5, 30)]
    float speedParameter = 10;

    [Header("---ここからデバッグ設定---")]
    [SerializeField]
    bool debug_SpaceKey;

    [SerializeField]
    float backTime = 3;

    Vector3 thisObjPos;
    Coroutine runCoroutine;

    float x_Abs;
    float y_Abs;
    float z_Abs;

    bool isStart;

    void Update()
    {
        //エラーで警告
        if (targetTrasform == null)
        {
            Debug.LogError("Please set target");
            return;
        }

        x_Abs = Mathf.Abs(this.gameObject.transform.position.x - targetTrasform.position.x);
        y_Abs = Mathf.Abs(this.gameObject.transform.position.y - targetTrasform.position.y);
        z_Abs = Mathf.Abs(this.gameObject.transform.position.z - targetTrasform.position.z);


        //プレイモード時テスト用
        if (Input.GetKeyDown(KeyCode.Space) && debug_SpaceKey)
        {
            Fall();
        }
    }

    //Editor上でのみ使うメソッド。座標を開始位置まで戻す
    public void ResetPosition()
    {
        this.gameObject.transform.position = thisObjPos;
    }

    //利用時はこれを呼ぶ
    public void Fall()
    {
        if (runCoroutine == null)
        {
            runCoroutine = StartCoroutine(MoveCoroutine());
        }

    }

    IEnumerator MoveCoroutine()
    {
        //Targetまで移動
        while (x_Abs > 0 || y_Abs > 0 || z_Abs > 0)
        {
            float speed = speedParameter * Time.fixedDeltaTime;
            this.gameObject.transform.position = Vector3.MoveTowards(this.gameObject.transform.position, targetTrasform.position, speed);
            yield return null;
        }

        //Editor上で動かした場合のみここを通る。元の座標に戻るまでの秒数
        if (EditorApplication.isPlaying == false)
        {
            yield return new WaitForSeconds(backTime);
        }

        //PlayMode中はrunCoroutineにMoveCoroutineが入ってるので止めて空にしとく
        if (runCoroutine != null)
        {
            StopCoroutine(runCoroutine);
            runCoroutine = null;
        }
    }

    //ここからEditor拡張
#if UNITY_EDITOR
    [CustomEditor(typeof(MyDebug))]
    public class MyDebugEditor : Editor
    {
        static MyDebug rootClass;

        //コンストラクタでPlayMode,EditModeの切り替え時の処理を登録
        static MyDebugEditor()
        {
            EditorApplication.playModeStateChanged += OnPlayMode;
        }

        static void OnPlayMode(PlayModeStateChange playModeStateChange)
        {
            //まだ一回もTargetのpositionを保存していなかった時の為
            if (rootClass.isStart == false)
            {
                rootClass.thisObjPos = rootClass.gameObject.transform.position;
            }

            //途中でPlayModeに切り替えても元に戻す
            if (playModeStateChange == PlayModeStateChange.ExitingEditMode)
            {
                rootClass.ResetPosition();
            }
        }

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            rootClass = target as MyDebug;
            EditorGUILayout.Space();

            // 押下時に実行したい処理
            if (GUILayout.Button("Test") && EditorApplication.isPlaying == false)
            {
                //エラーで警告
                if (rootClass.targetTrasform == null)
                {
                    Debug.LogError("Please set target");
                    return;
                }

                if (rootClass.isStart == false)
                {
                    //開始位置の座標保存
                    rootClass.thisObjPos = rootClass.gameObject.transform.position;

                    rootClass.isStart = true;

                    Observable.FromMicroCoroutine(rootClass.MoveCoroutine).Subscribe(_ =>
                    {
                        rootClass.isStart = false;
                        rootClass.ResetPosition();
                    }).AddTo(rootClass);
                }
            }

            serializedObject.Update();
            serializedObject.ApplyModifiedProperties();
        }
    }
#endif
}

```


![MovePerfect.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/ac48aa6d-cc9d-e160-4d65-a74fbda43ed1.gif)

ばっちりですね。