RYO620
DESIGN & DEVELOPMENT
Unityエディタ拡張でシーンビュー上にGameObject生成ボタンUIを作る
Ryosuke

Unityエディタ拡張でシーンビュー上にGameObject生成ボタンUIを作る

Unity において Prefab から GameObject を生成する操作、

  • プロジェクトビューから Prefab を格納しているディレクトリを探す
  • クリックで開く
  • 任意のPrefabを探す
  • シーンビューにドラッグ&ドロップ

ってちょっと面倒くさいですよね。 使用頻度の高い Prefab はもうちょっとかんたんに生成したいなと思い、シーンビュー上に Prefab をインスタンス化できるボタンUIを作ってみました。

↑完成形はこんな感じです。


もくじ - Unityエディタ拡張 覚書全3回の2回目

  • GameObject 生成ボタンUIを作ろう
    • シーンビューをカスタマイズ
    • 画面下部に水平中央寄せでボタンを並べる
    • 生成したい GameObject のリストを ScriptableObject で作る
    • ボタンを押して GameObject をインスタンス化する
  • 完成形

第1回: Unityエディタ拡張でキャラクターのグリッド移動ツールを作る
第2回: Unityエディタ拡張でシーンビュー上にGameObject生成ボタンUIを作る* (この記事)
第3回: Unityエディタ拡張で NPC の移動ルートを Gizmos を使って可視化し、 シーンビューで編集可能にする


GameObject 生成ボタンUIを作ろう

シーンビューをカスタマイズ

CreateButtonUi というクラスを作成します。

前回記事 でもシーンビュー拡張を行いましたが、あちらは特定コンポーネント選択時のみ表示されるUIでした。 今回は常時表示するUIが必要なので、 SceneView.duringSceneGui に描画処理を登録することでシーンビューに独自UIを追加します。

Assets/Scripts/Editor/CreateButtonUi.cs

using UnityEditor; using UnityEngine; [InitializeOnLoad] public static class CreateButtonUi { static CreateButtonUi() { SceneView.duringSceneGui += OnGui; } private static void OnGui(SceneView sceneView) { Handles.BeginGUI(); // ここに UIを描画する処理を記述 Handles.EndGUI(); } }

SceneView.duringSceneGui への登録はエディタ起動時に行います。 static コンストラクタを持つクラスに [InitializeOnLoad] 属性を付与することでエディタが起動したタイミングでスクリプトを実行できます。


画面下部に水平中央寄せでボタンを並べる

マインクラフトのインベントリのような位置に、ひとまず数字のボタンを並べてみます。

Assets/Scripts/Editor/CreateButtonUi.cs

・・・ private static void OnGui(SceneView sceneView) { Handles.BeginGUI(); ShowButtons(sceneView.position.size); Handles.EndGUI(); } /// <summary> /// ボタンの描画関数 /// </summary> private static void ShowButtons(Vector2 sceneSize) { var count = 10; var buttonSize = 40; foreach (var i in Enumerable.Range(0, count)) { // 画面下部、水平、中央寄せをコントロールする Rect var rect = new Rect( sceneSize.x / 2 - buttonSize * count / 2 + buttonSize * i, sceneSize.y - buttonSize * 1.6f, buttonSize, buttonSize); if (GUI.Button(rect, i.ToString())) { Debug.Log("おされた"); } } } ・・・

sceneView.position.size でスクリーンビューのサイズを Vector2 で取得できます。 GUI.Button でボタンを生成し、その第1引数の Rect でスクリーンのサイズを使って、描画位置を画面下部水平中央にコントロールしています。

別の方法でレイアウトする方法

単純に水平に並べるだけであれば、複雑な位置計算ナシで下記記述でいけます。 ただ、GUILayout を使うと細かいレイアウトの調整が難しいので、 Rect でピッタリ合わせる方法を取りました。

・・・ // こういう書き方もできますよという例です // このブロックの中は要素は水平に並ぶ GUILayout.BeginHorizontal(); { // このブロックの中の要素は中央寄せになる GUILayout.FlexibleSpace(); { foreach (var i in Enumerable.Range(0, count)) { // GUI.Button ではなく、 自動調整されるGUILayout.Buttonを使っている if (GUILayout.Button(i.ToString())) { Debug.Log("おされた"); } } } GUILayout.FlexibleSpace(); } GUILayout.EndHorizontal(); ・・・

生成したい GameObject のリストを ScriptableObject で作る

Prefab とボタンのアイコン画像のリストを ScriptableObject で作ります。 ScriptableObject で管理することで、ButtonUIの増減/切り替えが楽になります。編集するシーンによって差し替えたりしてもよさそう。

Assets/Scripts/CharacterDataTable.cs

using System; using System.Collections.Generic; using UnityEngine; [Serializable] public class CharacterData { public Sprite icon; public GameObject gameObject; } [CreateAssetMenu(fileName = "CharacterDataTable", menuName = "ScriptableObject/CharacterDataTable")] public class CharacterDataTable: ScriptableObject { public List<CharacterData> dataList = new List<CharacterData>(); }

↓CreateButtonUI のスクリプトではこのアセットを AssetDatabase.LoadAssetAtPath<T>("[ファイルパス]"); で取得します。

Assets/Scripts/Editor/CreateButtonUi.cs

・・・ private static CharacterDataTable _characterDataTable; ・・・ private static void OnGui(SceneView sceneView) { if (_characterDataTable == null) _characterDataTable = LoadDataTable(); Handles.BeginGUI(); ShowButtons(sceneView.position.size); Handles.EndGUI(); } private static CharacterDataTable LoadDataTable() { return AssetDatabase.LoadAssetAtPath<CharacterDataTable>("Assets/Data/CharacterDataTable.asset"); } ・・・

この取得したデータを使ってさっきの数字ボタンをアイコン画像に書き換えます。

Assets/Scripts/Editor/CreateButtonUi.cs

・・・ /// <summary> /// ボタンの描画関数 /// </summary> private static void ShowButtons(Vector2 sceneSize) { var count = _characterDataTable.dataList.Count; var buttonSize = 40; var padding = 2; for (var i = 0; i < count; i++) { var data = _characterDataTable.dataList[i]; var rect = new Rect(sceneSize.x / 2 - buttonSize * count / 2 + buttonSize * i + padding * i, sceneSize.y - buttonSize * 1.6f, buttonSize, buttonSize); if (GUI.Button(rect, data.icon.texture)) { Debug.Log("おされた"); } } } ・・・

ScriptableObject に登録したアイコン画像ScriptableObject に登録したアイコン画像

これで画像ボタンが並びました。


ボタンを押して GameObject をインスタンス化する

Prefab 状態のままインスタンス化するには、 PrefabUtility.InstantiatePrefab を使用します。 生成しただけだと Undo が効かないので Undo.RegisterCreatedObjectUndo にインスタンス化した GameObject を登録します。

Assets/Scripts/Editor/CreateButtonUi.cs

・・・ if (GUI.Button(rect, data.icon.texture)) { var go = (GameObject) PrefabUtility.InstantiatePrefab(data.gameObject); Selection.activeGameObject = go; Undo.RegisterCreatedObjectUndo(go, "Create Character"); } ・・・

完成形

ボタンを押すと GameObject が生成されるボタンを押すと GameObject が生成される

開発モチベーションが上がるように(大事)、デフォルトのボタンUIではなく専用のボタン背景画像を作ってみました。 また、TilemapのUIとかぶりそうだったので、60px ほど左にずらしています。

Assets/Scripts/Editor/CreateButtonUi.cs

using UnityEditor; using UnityEngine; [InitializeOnLoad] public static class CreateButtonUi { private static CharacterDataTable _characterDataTable; static CreateButtonUi() { SceneView.duringSceneGui += OnGui; } private static void OnGui(SceneView sceneView) { if (_characterDataTable == null) _characterDataTable = LoadDataTable(); Handles.BeginGUI(); ShowButtons(sceneView.position.size); Handles.EndGUI(); } /// <summary> /// ボタンの描画関数 /// </summary> private static void ShowButtons(Vector2 sceneSize) { var count = _characterDataTable.dataList.Count; var buttonSize = 48; var style = new GUIStyle(); style.normal.background = _characterDataTable.normalBackground.texture; for (var i = 0; i < count; i++) { var data = _characterDataTable.dataList[i]; var rect = new Rect(sceneSize.x / 2 - buttonSize * count / 2 + buttonSize * i - 60, sceneSize.y - buttonSize * 1.5f, buttonSize, buttonSize); if (GUI.Button(rect, data.icon.texture, style)) { var go = (GameObject) PrefabUtility.InstantiatePrefab(data.gameObject); Selection.activeGameObject = go; Undo.RegisterCreatedObjectUndo(go, "Create Character"); } } } private static CharacterDataTable LoadDataTable() { return AssetDatabase.LoadAssetAtPath<CharacterDataTable>("Assets/Data/CharacterDataTable.asset"); } }

これでGameObjectの生成が快適になりました! 特定のSceneや特定のPrefab内でのみButtonUIが表示されるようにしたり、生成できるオブジェクト数を制限したり、開発しているゲームによって色んな拡張ができそうです。