UnityのEditorCoroutineUtilityを使い、何か重要なバッチを走らせている風なダイアログを作る

- 使用Unityバージョン：
2019.3.1f1

Editor拡張はお好きでしょうか？
Editor拡張はとても便利ですが、拡張内でコルーチンが使えないという問題が昔からあると思います。
そんな中、UnityのPackageManagerにEditorCoroutineという機能がUnity2018.1ごろから追加されています。
今回はそのEditorCoroutineを使って、「何かわからないけど重要なことしてる風」なプログレスバーを出すEditor拡張を書きます。

## この記事で得られるもの
<img width="757" alt="DummyProgress.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/92e670e1-2434-0a79-096a-74d8f4bc3b37.png">
このような、時間を指定するとその時間だけプログレスバーをUnityEditor上で出す事が出来ます。
ちょっとした離席時などに検査走らせているからUnity内での作業が進められない。と言ったアリバイ作りが出来ます

## EditorCoroutineの何が嬉しいの？
前述した通り、Editor拡張は公式ではコルーチンの機能が使えませんでした。
（UniRxのFromCoroutineを使ったりする必要がありました）
なので、Editor拡張中に1秒待ちたいと言ったような実装が必要な場合、手間がかかってしまっていました。
EditorCoroutineはPackageManagerから入れる事ができるので、手間としては大分楽になったと思います。

## EditorCoroutineの導入方法
UnityのPackageManagerから導入が出来ます。2020年3月11日現在、プレビュー版です。
<img width="698" alt="editorコルーチン.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/748ed947-cb0e-8bf5-db85-1409b14548fa.png">

## EditorCoroutineの使い方
まずEditorフォルダ内に`EditorWindow`を継承したEditor拡張用のクラスを作ります。
そうしたらusingに
`using Unity.EditorCoroutines.Editor;`
を追加します。
<img width="333" alt="スクリーンショット 2020-03-11 3.04.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/ad9b8358-ce55-71a6-22ac-a70c953cdd3e.png">
ここから先は通常のEditor拡張の分野ですので、割愛しますがEditorWindowの処理とGUIを作ります。

GUIを設定したら、コルーチンの処理を記述します。 関数は通常のコルーチンと同じく、IEnumeratorにします。

```C#
private IEnumerator ShowProgressBar(string progressTime)
    {
        float loopTime = 100f;
        float.TryParse(progressTime, out loopTime);

        for (int i = 0; i < 100; i++)
        {
            float progress = (float)i / 100;
            EditorUtility.DisplayProgressBar (
                "検査中", 
                (progress * 100).ToString("F2") + "%", 
                progress
            );
            yield return new EditorWaitForSeconds(loopTime / 100f);
        }
        EditorUtility.DisplayProgressBar (
            "検査が完了しました", 
            (100).ToString("F2") + "%)", 
            100
        );
        if (EditorUtility.DisplayDialog("検査が完了しました", "異常は見つかりませんでした", "OK"))
        {
            EditorUtility.ClearProgressBar();
        }
    }
```

ここでは`EditorUtility.DisplayProgressBar`でプログレスバーを表示しています。
注目するべきは
`yield return new EditorWaitForSeconds(loopTime / 100f);`
の部分です。
こちらは通常の`WaitForSecounds`では無く、`EditorWaitForSeconds`を指定する事でEditor拡張の中で指定秒数待機します。
WaitForSecoundsではコンパイルエラーにはなりませんが、正しく動かないので注意しましょう。

ここで作成したコルーチンを呼び出すには、以下のような`EditorCoroutine`型の変数とボタンを用意します。

```C#
private EditorCoroutine m_coroutine;
```
```C#
        using (new GUILayout.HorizontalScope())
        {
            if (GUILayout.Button("表示"))
            {
                m_coroutine = EditorCoroutineUtility.StartCoroutine(ShowProgressBar(progressTime), this);
            }
            if (GUILayout.Button("Stop"))
            {
                if (m_coroutine != null)
                {
                    EditorCoroutineUtility.StopCoroutine(m_coroutine);
                    EditorUtility.ClearProgressBar();
                }
            }
        }
```
この処理をOnGUIに書く事で、表示とStopのボタンが出てくると思います。

そして先ほど作成した`ShowProgressBar`を呼ぶ際には、
`EditorCoroutineUtility.StartCoroutine(ShowProgressBar(progressTime), this);`
と言った指定が必要です。
基本的には通常のStartCoroutineと同じですが、第二引数のコルーチンのオーナーを指定する必要があります。
(`StartCoroutineOwnerless`と言うのもあります)
ここはthisを指定してあげればほぼほぼ間違いはないと思います。

Stopの処理も同様に
`EditorCoroutineUtility.StopCoroutine`
を呼び、`EditorUtility.ClearProgressBar();`でプログレスバーを閉じています。
（ProgressBarは必ず閉じる際に`EditorUtility.ClearProgressBar();`を呼びましょう）

ここまでやると開始ボタンを押す事で、以下のような挙動になります。指定した秒数でプログレスバーが100%になれば成功です。
（指定した値が1秒など小さすぎると1秒以上かかってしまったりします。数字の処理は厳密ではありません）

<img width="757" alt="DummyProgress.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/5e76e5ec-5ca9-2404-dec6-f1c32ae52c6f.png">

<img width="485" alt="DummyComplete.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/e2f24eb3-dc00-e370-8b4b-526cb3f4ca01.png">

## 今回のサンプルのソースコード
今回作成したサンプルのソースコードは以下になります。

```C#
using System.Collections;
using Unity.EditorCoroutines.Editor;
using UnityEngine;
using UnityEditor;

public class ShowDummyProgress : EditorWindow
{
    private string progressTime = "100";
    
    private EditorCoroutine m_coroutine;
    
    [MenuItem("Editor/DummyProgress")]
    private static void Create()
    {
        GetWindow<ShowDummyProgress>("ダミープログレスバー");
    }

    private void OnGUI()
    {
        using (new GUILayout.HorizontalScope())
        {
            GUILayout.Label("時間");
            progressTime = GUILayout.TextField(progressTime);
        }

        using (new GUILayout.HorizontalScope())
        {
            if (GUILayout.Button("表示"))
            {
                m_coroutine = EditorCoroutineUtility.StartCoroutine(ShowProgressBar(progressTime), this);
            }
            if (GUILayout.Button("Stop"))
            {
                if (m_coroutine != null)
                {
                    EditorCoroutineUtility.StopCoroutine(m_coroutine);
                    EditorUtility.ClearProgressBar();
                }
            }
        }
    }

    private IEnumerator ShowProgressBar(string progressTime)
    {
        float loopTime = 100f;
        float.TryParse(progressTime, out loopTime);

        for (int i = 0; i < 100; i++)
        {
            
            float progress = (float)i / 100;
            EditorUtility.DisplayProgressBar (
                "検査中", 
                (progress * 100).ToString("F2") + "%", 
                progress
            );
            yield return new EditorWaitForSeconds(loopTime / 100f);
        }
        EditorUtility.DisplayProgressBar (
            "検査が完了しました", 
            (100).ToString("F2") + "%", 
            100
        );
        if (EditorUtility.DisplayDialog("検査が完了しました", "異常は見つかりませんでした", "OK"))
        {
            EditorUtility.ClearProgressBar();
        }
    }
}