UnityのEditorでコルーチンや非同期処理をする

2021年10月8日

Unityの拡張機能の方のEditorエディターのお話。エディターでの命令で非同期処理を行いたい時の対処法。自分の場合はツールでWebから取得したデータをローカルにcsvファイルとして吐き出させる処理を作るのに利用しました。

実装方法

UnityのEditorでコルーチン(StartCoroutine)などを行いたい場合、以下のEditorCoroutineというスクリプトをEditorフォルダ以下に作成して、「(namespace.)EditorCoroutine.start(呼びたい関数)」という感じで実装することで実現可能だそうです。

https://gist.github.com/benblo/10732554

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Swing.Editor
{
	public class EditorCoroutine
	{
		public static EditorCoroutine start( IEnumerator _routine )
		{
			EditorCoroutine coroutine = new EditorCoroutine(_routine);
			coroutine.start();
			return coroutine;
		}
		readonly IEnumerator routine;
		EditorCoroutine( IEnumerator _routine )
		{
			routine = _routine;
		}
		void start()
		{
			//Debug.Log("start");
			EditorApplication.update += update;
		}
		public void stop()
		{
			//Debug.Log("stop");
			EditorApplication.update -= update;
		}
		void update()
		{
			/* NOTE: no need to try/catch MoveNext,
			 * if an IEnumerator throws its next iteration returns false.
			 * Also, Unity probably catches when calling EditorApplication.update.
			 */
			//Debug.Log("update");
			if (!routine.MoveNext())
			{
				stop();
			}
		}
	}
}

実際に利用例のスクリプトもありました。Unityのビルド中は触らないほうが良さそうですが、EditorApplicationのUpdateに引っ掛ける方法は覚えておくと使い所がいつか出てきそう。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Object = UnityEngine.Object;
using UnityEditor;
using Random = UnityEngine.Random;
namespace Swing.Editor.Test
{
	public class TestEditorCoroutine
	{
		[MenuItem("Swing/Test/Test Editor Coroutine")]
		static void testEditorCoroutine()
		{
			EditorCoroutine.start(testRoutine());
		}
		static IEnumerator testRoutine()
		{
			Debug.Log("hello " + DateTime.Now.Ticks);
			yield return null;
			Debug.Log("done " + DateTime.Now.Ticks);
		}
		[MenuItem("Swing/Test/Test Editor Coroutine With Exception")]
		static void testEditorCoroutineWithException()
		{
			EditorCoroutine.start(testRoutineWithException());
		}
		static IEnumerator testRoutineWithException()
		{
			Debug.Log("hello " + DateTime.Now.Ticks);
			yield return null;
			for (int i = 0; i < 10; i++)
			{
				testRandomException();
				yield return null;
			}
			Debug.Log("done " + DateTime.Now.Ticks);
		}
		static void testRandomException()
		{
			if (Random.value < 0.3f)
			{
				throw new Exception("ahah! " + DateTime.Now.Ticks);
			}
			else
			{
				Debug.Log("ok " + DateTime.Now.Ticks);
			}
		}
	}
}

EditorCoroutine.startに指定する関数はIEnumerator型であればゲーム中に使っている処理でもそのまま当てはめることが出来ました。自分の場合は中でwww使って通信してその結果を扱う処理をしていたんですが、エディター用に変更する箇所もなかったのでかなり楽に実現出来ました。