うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】Editor 上で再生停止を繰り返す

スクリプトから UnityEditor を再生する場合は以下の処理をすればよい

EditorApplication.isPlaying = true;

しかし、UnityEditor が再生されるタイミングで、static なインスタンスが初期化されるため、Unity API にイベントを登録してあったとしても消えてしまう

そのため、以下の処理は正しく実行されず再生されたままになってしまう

EditorApplication.isPlaying = true;

var count = 10000;
EditorApplication.update += While; 
            
void While()
{
    if (--count > 0)
        return;
            
    EditorApplication.update -= While;
    EditorApplication.isPlaying = false;
};

ここで利用できるのが EditorWindow.OnEnableEditorApplication.playModeStateChanged である

UnityEditor を再生時に EditorWindow.OnEnable が呼び出されるのでこのタイミングで
EditorApplication.playModeStateChanged に登録すればいい感じに再生後処理をすることができる

public class PlayStopLoopTool : EditorWindow
{
    private void OnEnable()
    {
        EditorApplication.playModeStateChanged += PlayModeStateChanged;
    }

    private void PlayModeStateChanged(PlayModeStateChange stateChange)
    {
        switch (stateChange)
        {
            case PlayModeStateChange.EnteredEditMode:
                // ここでなら Unityの static なイベントに登録しても削除されない
                break;
        }
    }

指定回数再生と停止を繰り返すサンプル

先程の処理を利用して、再生停止を指定回数繰り返す EditorWindow のサンプルが以下になる

f:id:hacchi_man:20211222012635p:plain:w300

PlayLoopCount を指定して 「Play」を押すと残り回数と停止するまでの時間が表示される

f:id:hacchi_man:20211222012935p:plain:w300

using System;
using UnityEditor;
using UnityEngine;
 
public class PlayStopLoopTool : EditorWindow
{
    [MenuItem("Tools/PlayStopLoop")]
    private static void ShowWindow()
    {
        var window = GetWindow<PlayStopLoopTool>();
        window.Show();
    }
 
    /// <summary>
    /// このタイミングで登録しないと再生時にイベントが消える
    /// </summary>
    private void OnEnable()
    {
        EditorApplication.playModeStateChanged += PlayModeStateChanged;
    }
 
    private void OnDisable()
    {
        EditorApplication.playModeStateChanged -= PlayModeStateChanged;
    }
 
    private void PlayModeStateChanged(PlayModeStateChange stateChange)
    {
        if (!_isPlaying)
            return;
        
        switch (stateChange)
        {
            case PlayModeStateChange.EnteredEditMode:
                Play();
                break;
            case PlayModeStateChange.ExitingEditMode:
                break;
            case PlayModeStateChange.EnteredPlayMode:
                EndCheck();
                break;
            case PlayModeStateChange.ExitingPlayMode:
                break;
        }
    }
 
    /// <summary>
    /// 再生する
    /// </summary>
    private void Play()
    {
        EditorApplication.isPlaying = true;
        // これ以降再生が始まると シリアライズされてないものは初期化される
        // そのため EditorApplication.update を利用していたら呼ばれなくなってしまう
    }
 
    /// <summary>
    /// 終了待ち
    /// </summary>
    private void EndCheck()
    {
        _endTime = DateTime.Now.AddSeconds(5);
        EditorApplication.update += Wait;
    }
 
    private void Wait()
    {
        Repaint();
         
        if (DateTime.Now < _endTime)
            return;
         
        EditorApplication.update -= Wait;
 
        _restPlayCount--;
        EditorApplication.isPlaying = false;
        if (_restPlayCount <= 0)
            Debug.Log("終了しました");
    }
 
    private int _playLoopCount = 1;
    private int _restPlayCount;
    private DateTime _endTime;
    private bool _isPlaying => _restPlayCount > 0;
    
    private void OnGUI()
    {
        _playLoopCount = EditorGUILayout.IntField("PlayLoopCount", _playLoopCount);
        using (new EditorGUI.DisabledScope(_playLoopCount <= 0))
        {
            if (GUILayout.Button("Play"))
            {
                _restPlayCount = _playLoopCount;
                Play();
            }
        }

        if (_isPlaying)
        {
            EditorGUILayout.LabelField($"残り {_restPlayCount}回");
            if (_endTime > DateTime.Now)
                EditorGUILayout.LabelField($"終了まで {(_endTime - DateTime.Now).TotalSeconds:F}s");
        }
    }
}