(:3[kanのメモ帳]

個人ゲーム開発者kan.kikuchiのメモ的技術ブログ。月木更新でUnity関連がメイン。

(:3[kanのメモ帳]

Unityエディタを再生する前に、シーンに問題ないか検証する【Unity】【エディタ拡張】


このエントリーをはてなブックマークに追加


この記事でのバージョン
Unity 2018.4.1f1


はじめに

今回は「シーンを再生する前に問題がないかの検証を挟みたい」という場合の話。


イメージとしては以下のような感じ。

f:id:kan_kikuchi:20190720051702g:plain


なお、記事中の画像は以下のアセットを使っております。





SceneVerifier

早速ですが、Unityエディタを再生する際に検証を挟み、問題があれば再生を止めるクラス

SceneVerifierのプログラムです。

以下の例ではシーンを保存していない時に再生を止めるようにしています。

using UnityEditor;
using UnityEngine.SceneManagement;

/// <summary>
/// Unityエディタを再生する際に検証を挟み、問題があれば再生を止めるクラス
/// </summary>
[InitializeOnLoad]//コンストラクタがエディター起動時等に呼ばれるようになる
public static class SceneVerifier{
  
  //=================================================================================
  //初期化
  //=================================================================================
  
  static SceneVerifier(){
    EditorApplication.playModeStateChanged += OnChangedPlayMode;
  }

  //=================================================================================
  //イベント
  //=================================================================================
  
  //プレイモードが変更された
  private static void OnChangedPlayMode(PlayModeStateChange state) {
    //実行ボタンが押された時のみ処理
    if (state != PlayModeStateChange.ExitingEditMode) {
      return;
    }
    
    //再生を出来るか判定、出来ない場合は強制的に止める
    bool canPlay = CanPlayScene();
    EditorApplication.isPlaying = canPlay;

    //再生出来ない場合は、ウィンドウを出して通知
    if (!canPlay) {
      EditorUtility.DisplayDialog("再生不可", "シーンが保存されていないので、再生できません", "了解");
    }
  }

  //=================================================================================
  //判定
  //=================================================================================

  //シーンを再生する事が出来るか
  private static bool CanPlayScene() {
    //シーンが保存されていない時は再生出来ないように
    return !SceneManager.GetSceneAt(0).isDirty;
  }
  
}


これをEditorディレクトリに配置に配置すれば、

f:id:kan_kikuchi:20190719133612j:plain


シーンを保存してない時は再生出来ないようになります。

f:id:kan_kikuchi:20190719140615g:plain


一応、プログラムについて解説をしてみると、以下のような感じです。

  1. InitializeOnLoadの属性を付けてコンストラクタを再生してない時にも実行されるように
  2. EditorApplication.playModeStateChangedでプレイモードの変更を検知
  3. 検証して再生できなければ、EditorApplication.isPlayingをfalseにして再生を止める
  4. 再生出来ない場合はEditorUtility.DisplayDialogを使ってその事を伝える。


ちなみにEditorApplication.playModeStateChanged

EditorSceneManager.playModeStartSceneEditorUtility.DisplayDialog

については以下の記事を参照のこと。





上記の例ではシーンを保存しているかどうかを検証していましたが、

検証内容をを変えたい場合はCanPlaySceneというメソッドの中を書き換えます。


ただし、検証内容が複数ある場合は、ウィンドウで出すメッセージも複数あった方が良いと思うので、

boolでなくstringでエラーメッセージを返すと良いかもしれません

using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;

/// <summary>
/// Unityエディタを再生する際に検証を挟み、問題があれば再生を止めるクラス
/// </summary>
[InitializeOnLoad]//コンストラクタがエディター起動時等に呼ばれるようになる
public static class SceneVerifier{
  
  //=================================================================================
  //初期化
  //=================================================================================
  
  static SceneVerifier(){
    EditorApplication.playModeStateChanged += OnChangedPlayMode;
  }

  //=================================================================================
  //イベント
  //=================================================================================
  
  //プレイモードが変更された
  private static void OnChangedPlayMode(PlayModeStateChange state) {
    //実行ボタンが押された時のみ処理
    if (state != PlayModeStateChange.ExitingEditMode) {
      return;
    }

    //再生を出来るか判定、出来ない場合は強制的に止める
    var errorMessage = VerifyScene();
    EditorApplication.isPlaying = string.IsNullOrEmpty(errorMessage);

    //再生出来ない場合は、ウィンドウを出して通知
    if (!EditorApplication.isPlaying) {
      EditorUtility.DisplayDialog("再生不可", errorMessage, "了解");
    }
  }

  //=================================================================================
  //検証
  //=================================================================================

  //シーンを再生する事が出来るか検証し、問題があればエラーメッセージを返す
  private static string VerifyScene() {

    //Hierarchy上の有効なオブジェクトの数取得、3つを超えていたらなら再生できないように
    int objectNum = Object.FindObjectsOfType(typeof(GameObject)).Where(obj => (obj as GameObject).activeInHierarchy).Count();
    if (objectNum > 3) {
      return "シーン上のオブジェクトが3つを超えているので、再生できません (現在" + objectNum + "個)";
    }
    
    //シーンが保存されていない時は再生出来ないように
    if (SceneManager.GetSceneAt(0).isDirty) {
      return "シーンが保存されていないので、再生できません";
    }

    //問題がない場合は空文字を返す
    return "";
  }
  
}
f:id:kan_kikuchi:20190720045738g:plain


また、検証で問題があっても再生を出来るようにしたい場合は、

以下のようにウィンドウの選択肢を2つにして、強引に再生できるようにすると良いかもしれません。

using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;

/// <summary>
/// Unityエディタを再生する際に検証を挟み、問題があれば再生を止めるクラス
/// </summary>
[InitializeOnLoad]//コンストラクタがエディター起動時等に呼ばれるようになる
public static class SceneVerifier{
  
  //検証したか
  private static bool _isVerified = false;
  
  //=================================================================================
  //初期化
  //=================================================================================
  
  static SceneVerifier(){
    EditorApplication.playModeStateChanged += OnChangedPlayMode;
  }

  //=================================================================================
  //イベント
  //=================================================================================
  
  //プレイモードが変更された
  private static void OnChangedPlayMode(PlayModeStateChange state) {
    //実行ボタンが押された時のみ処理
    if (state != PlayModeStateChange.ExitingEditMode) {
      return;
    }
    
    //検証済みの場合は検証をスルー
    if (_isVerified) {
      return;
    }
    
    //再生を出来るか判定、出来ない場合は強制的に止める
    var errorMessage = VerifyScene();
    EditorApplication.isPlaying = string.IsNullOrEmpty(errorMessage);

    //再生出来ない場合は、ウィンドウを出して通知し、強引に再生するか聞く
    if (!EditorApplication.isPlaying) {
      _isVerified = true;
      EditorApplication.isPlaying = EditorApplication.isPlaying = EditorUtility.DisplayDialog("シーンに問題有り", errorMessage + "\nそれでも再生しますか?", "再生する", "再生しない");
      _isVerified = false;
    }
  }

  //=================================================================================
  //検証
  //=================================================================================

  //シーンを再生する事が出来るか検証し、問題があればエラーメッセージを返す
  private static string VerifyScene() {

    //Hierarchy上の有効なオブジェクトの数取得、3つを超えていたら問題有り
    int objectNum = Object.FindObjectsOfType(typeof(GameObject)).Where(obj => (obj as GameObject).activeInHierarchy).Count();
    if (objectNum > 3) {
      return "シーン上のオブジェクトが3つを超えています! (現在" + objectNum + "個)";
    }
    
    //シーンが保存されていない時は問題有り
    if (SceneManager.GetSceneAt(0).isDirty) {
      return "シーンが保存されていません!";
    }

    //問題がない場合は空文字を返す
    return "";
  }
  
}
f:id:kan_kikuchi:20190720051702g:plain


なお、InitializeOnLoadではなくExecuteInEditModeを使い、

さらにStaticではなく、MonoBehaviourを継承したクラスに変更し、

それをシーン上のオブジェクトにAddすれば、

全てのシーンではなく、特定のシーンだけ検証する事も可能です。

using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;

#if UNITY_EDITOR
using UnityEditor;
#endif

/// <summary>
/// Unityエディタを再生する際に検証を挟み、問題があれば再生を止めるクラス
/// </summary>
[ExecuteInEditMode]//ExecuteInEditModeを付ける事でOnEnableやOnDestroyが再生していなくても実行されるようになる
public class SceneVerifier : MonoBehaviour{
  #if UNITY_EDITOR
  
  //検証したか
  private bool _isVerified = false;
  
  //=================================================================================
  //初期化、破棄
  //=================================================================================
  
  //有効になった時(シーンを開いた時)に実行
  private void OnEnable() {
    EditorApplication.playModeStateChanged += OnChangedPlayMode;
  }

  //削除されたた時(シーンを閉じた時)に実行
  private void OnDestroy() {
    EditorApplication.playModeStateChanged -= OnChangedPlayMode;
  }


  //=================================================================================
  //イベント
  //=================================================================================
  
  //プレイモードが変更された
  private void OnChangedPlayMode(PlayModeStateChange state) {
    //実行ボタンが押された時のみ処理
    if (state != PlayModeStateChange.ExitingEditMode) {
      return;
    }
    
    //検証済みの場合は検証をスルー
    if (_isVerified) {
      return;
    }
    
    //再生を出来るか判定、出来ない場合は強制的に止める
    var errorMessage = VerifyScene();
    EditorApplication.isPlaying = string.IsNullOrEmpty(errorMessage);

    //再生出来ない場合は、ウィンドウを出して通知し、強引に再生するか聞く
    if (!EditorApplication.isPlaying) {
      _isVerified = true;
      EditorApplication.isPlaying = EditorApplication.isPlaying = EditorUtility.DisplayDialog("シーンに問題有り", errorMessage + "\nそれでも再生しますか?", "再生する", "再生しない");
      _isVerified = false;
    }
  }

  //=================================================================================
  //検証
  //=================================================================================

  //シーンを再生する事が出来るか検証し、問題があればエラーメッセージを返す
  private string VerifyScene() {

    //Hierarchy上の有効なオブジェクトの数取得、3つを超えていたら問題有り
    int objectNum = Object.FindObjectsOfType(typeof(GameObject)).Where(obj => (obj as GameObject).activeInHierarchy).Count();
    if (objectNum > 3) {
      return "シーン上のオブジェクトが3つを超えています! (現在" + objectNum + "個)";
    }
    
    //シーンが保存されていない時は問題有り
    if (SceneManager.GetSceneAt(0).isDirty) {
      return "シーンが保存されていません!";
    }

    //問題がない場合は空文字を返す
    return "";
  }
  
  #endif
}
f:id:kan_kikuchi:20190720053036j:plain