☠️

Unityでシーンを探したくないのでエディタ拡張する

2024/12/01に公開

この記事は、神戸電子専門学校 ゲーム技研部 Advent Calendar 2024の2日目の記事です。
https://qiita.com/advent-calendar/2024/kdgamegiken

はじめに

Unityでゲーム開発をしていると、複数シーンが別のファイルの中にあっていちいち階層を潜って探しに行くの、非常に煩わしいと思います。


1階層潜るだけでももう嫌になる。

というわけで、エディタ拡張でシーンを一括表示してお手軽選択できるようにしていきます。

要件定義(飛ばしていい)

まずはやりたいことをまとめていきます。

  • 存在するシーンをまとめてすべて表示し選んだら切り替え
  • パッケージが増えるとサンプルシーンも増えるのでフォルダ指定したい
  • お気に入り機能を付けてよく使うものを上に表示したい
  • 変更点あったりするときに警告をだしたい

いざ実装

基本

最低限部分なのでたたんでおきます。

1.最低限表示
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using System.IO;

public class EasySceneSelectWindow : EditorWindow
{
    [MenuItem("CustomWindow/SceneSelector")]
    public static void ShowMyEditor()
    {
        EditorWindow.GetWindow(typeof(EasySceneSelectWindow));
    }
    private void OnGUI()
    {
        EditorGUILayout.LabelField("一先ず最低限");
    }
}

2.スクロール対応
private Vector2 scrollPos; // スクロール用の変数

private void OnGUI()
{
// スクロール開始 最初
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);

// 内部表示処理を記述する

// スクロール終了 最後
EditorGUILayout.EndScrollView();
}

表示領域を超えてしまうのを見越してスクロール対応させておきます。

シーンを探して配列にまとめる

private string[] scenePaths; // シーンのパスを格納する配列

private void OnEnable()
{
    SarchScenes();
}

private void SarchScenes()
{
// 指定フォルダ内のシーンを検索
    string[] guids = AssetDatabase.FindAssets("t:Scene", new[] { forderPath });
    scenePaths = new string[guids.Length];
    for (int i = 0; i < guids.Length; i++)
    {
        scenePaths[i] = AssetDatabase.GUIDToAssetPath(guids[i]);
    }
}

ボタンを配置しシーンを変更できるようにする

private readonly float buttonWidth = 200f; // ボタンの幅を制限 値は適当


private void OnGUI(){

    // シーン群を表示
    foreach (var scenePath in scenePaths)
    {
        // 階層構造(Asset/OOO/example.scene)を無視して表示する(example.scene)
        string sceneName = Path.GetFileNameWithoutExtension(scenePath);

        if (GUILayout.Button(sceneName, GUILayout.Width(buttonWidth)))
        {
            OpenScene(scenePath);
        }
}

private void OpenScene(string scenePath)
{
    // シーンを開く(保存していない変更がある場合は確認)
    if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
    {
        EditorSceneManager.OpenScene(scenePath);
    }
}


ボタンが表示されてシーン変更できるようになりました。編集が保存されていなかったら警告も出るようになっています。

割ともう完成と言えますが、最後に少し便利にしていきましょう。

お気に入り機能実装

private List<string> favoriteScenes = new List<string>(); // お気に入りシーンリスト

private void OnGUI()
{

// お気に入りシーンのリストを表示
if (favoriteScenes.Count > 0)
{
    EditorGUILayout.LabelField("Favorite Scenes:");
    foreach (var favScene in favoriteScenes)
    {
        string sceneName = Path.GetFileNameWithoutExtension(favScene);
        if (GUILayout.Button(sceneName, GUILayout.Width(buttonWidth)))
        {
            OpenScene(favScene);
        }
    }
    EditorGUILayout.Space();
}

// スクロール開始
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);

// シーンリスト表示
foreach (var scenePath in scenePaths)
{
    string sceneName = Path.GetFileNameWithoutExtension(scenePath);

    // お気に入り追加ボタンとシーン名を横並びにする
    EditorGUILayout.BeginHorizontal();

    // お気に入りチェックボックス
    bool isFavorite = favoriteScenes.Contains(scenePath);
    bool newIsFavorite = EditorGUILayout.Toggle(isFavorite, GUILayout.Width(20));

    if (newIsFavorite != isFavorite)
    {
    if (newIsFavorite)
        favoriteScenes.Add(scenePath); // お気に入りに追加
    else
        favoriteScenes.Remove(scenePath); // お気に入りから削除
    }
    // シーン名のボタン表示
    if (GUILayout.Button(sceneName, GUILayout.Width(buttonWidth)))
    {
        OpenScene(scenePath);
    }
    EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
}


お気に入り登録用のチェックボックスとお気に入りの一番上表示ができました。

お気に入り設定の保存/ロードをできるように

このままだとウィンドウを閉じるたびにお気に入りがリセットされてしまうので保存できるようにします。

private const string FavoriteScenesKey = "FavoriteScenes"; // お気に入りシーンのEditorPrefs用キー
// 同一文字だと他プロジェクト間でも共有されてしまうので注意
void OnEnable()
{
LoadFavorites();// お気に入りロード
}

void OnGUI()
{
//~~
// お気に入りボタン押下時
SaveFavorites(); // 変更があればお気に入りをセーブ
}


private void SaveFavorites()
{
string favoritesString = string.Join(";", favoriteScenes.ToArray());
EditorPrefs.SetString(FavoriteScenesKey, favoritesString);
}

private void LoadFavorites()
{
favoriteScenes.Clear();
// 保存された文字列を取得、分割してリストに格納
if (EditorPrefs.HasKey(FavoriteScenesKey))
    {
    string favoriteString = EditorPrefs.GetString(FavoriteScenesKey);
    favoriteScenes.AddRange(favoriteString.Split(';'));
    }
}

エディタを閉じても無事変更が残ってくれるようになりました!
まだ親切にできる部分もありますがきれいに成型して完成です。

完成!!(まとめコード)

using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using System.IO;
public class EasySceneSelectWindow : EditorWindow
{
    [MenuItem("CustomWindow/SceneSelector")]
    public static void ShowMyEditor()
    {
        EditorWindow.GetWindow(typeof(EasySceneSelectWindow));
    }

    private Vector2 scrollPos; // スクロール用の変数
    private string[] scenePaths; // シーンのパスを格納する配列
    private readonly string forderPath = "Assets/scenes"; // このフォルダの子階層にあるシーンを検索
    private readonly float buttonWidth = 200f; // ボタンの幅を制限
    private readonly List<string> favoriteScenes = new List<string>(); // お気に入りシーンリスト
    private const string FavoriteScenesKey = "EasySceneSelect_FavoriteScenes"; // お気に入りシーンのEditorPrefs用キー



    private void OnEnable()
    {
        LoadFavorites();
        SarchScenes();
    }
    private void OnGUI()
    {
        ShowFavoriteScenesSelectButton();

        // スクロール開始
        scrollPos = EditorGUILayout.BeginScrollView(scrollPos);

        ShowAllScenesSelectButton();

        EditorGUILayout.EndScrollView();
    }




    // シーンを開く (保存していない変更がある場合は確認)
    private void OpenScene(string scenePath)
    {
        // シーンを開く(保存していない変更がある場合は確認)
        if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
        {
            EditorSceneManager.OpenScene(scenePath);
        }
    }
    // シーンを検索
    private void SarchScenes()
    {
        // 指定フォルダ内のシーンを検索
        string[] guids = AssetDatabase.FindAssets("t:Scene", new[] { forderPath });
        scenePaths = new string[guids.Length];
        for (int i = 0; i < guids.Length; i++)
        {
            scenePaths[i] = AssetDatabase.GUIDToAssetPath(guids[i]);
        }

    }
    // シーンボタンを表示/作業シーン変更
    private void SceneButton(string scenePath, string sceneName = null)
    {
        if (sceneName == null)
        {
            sceneName = Path.GetFileNameWithoutExtension(scenePath);
        }

        if (GUILayout.Button(sceneName, GUILayout.Width(buttonWidth)))
        {
            OpenScene(scenePath);
        }
    }
    // お気に入りシーン群表示
    private void ShowFavoriteScenesSelectButton()
    {
        // お気に入りシーンのリストを表示
        if (favoriteScenes.Count > 0)
        {
            EditorGUILayout.LabelField("Favorite Scenes:");
            foreach (var favScene in favoriteScenes)
            {
                SceneButton(favScene);
            }
            EditorGUILayout.Space();
        }
    }
    // 全シーンボタン表示
    private void ShowAllScenesSelectButton()
    {
        // シーンリスト表示
        foreach (var scenePath in scenePaths)
        {
            string sceneName = Path.GetFileNameWithoutExtension(scenePath);

            // お気に入りボタンとシーン名を横並びにする
            EditorGUILayout.BeginHorizontal();

            // お気に入りチェックボックス
            bool isFavorite = favoriteScenes.Contains(scenePath);
            bool newIsFavorite = EditorGUILayout.Toggle(isFavorite, GUILayout.Width(20));

            if (newIsFavorite != isFavorite)
            {
                if (newIsFavorite)
                    favoriteScenes.Add(scenePath); // お気に入りに追加
                else
                    favoriteScenes.Remove(scenePath); // お気に入りから削除

                SaveFavorites(); // 変更があればお気に入りをセーブ
            }

            SceneButton(scenePath, sceneName);

            EditorGUILayout.EndHorizontal();

        }
    }
    // お気に入りシーンを保存
    private void SaveFavorites()
    {
        string favoritesString = string.Join(";", favoriteScenes.ToArray());
        EditorPrefs.SetString(FavoriteScenesKey, favoritesString);
    }
    // お気に入りシーンを読み込み
    private void LoadFavorites()
    {
        favoriteScenes.Clear();
        // 保存された文字列を取得、分割してリストに格納
        if (EditorPrefs.HasKey(FavoriteScenesKey))
        {
            string favoriteString = EditorPrefs.GetString(FavoriteScenesKey);
            favoriteScenes.AddRange(favoriteString.Split(';'));
        }
    }

}


(完成図)

最後に

いかがでしたでしょうか。
多人数で開発していればやはりどうしても多くなってしまうシーン群。
少しでも手間が減る助けになれば幸いです。

以下に思いついた改善策を残しておくのでより便利にするのも良きでしょう。

  • 実行時でのシーン切り替えにも対応する
  • 検索するフォルダを後から変更できるようにする
  • 重複する名称があれば上の階層の名称を入れる
  • EditorPrefsキーワードをプロジェクト毎で自動で変更されるよう対応する
  • お気に入りと全部で左右に分割して画面を分ける
  • 説明を書く
  • 見栄えをよくする
神戸電子専門学校ゲーム技術研究部

Discussion