使用バージョン:Unity 4.5.2f1
製作中のゲームで海面に大量のブイを並べて配置したいのですが、手作業ではとても無理なのでエディタ拡張を利用して、一定間隔でGameObjectを複製して配置する機能を作ってみました。

初めてのエディタ拡張で間違ってる部分もありそうなので、ご意見など頂けたら幸いです。(@mokusan)
先ず完成イメージは以下。

このように必要なパラメータを設定して「Create」を押下すると、先に挙げたゲーム画面のようにブイが一定間隔で配置される。
・エディタ拡張用のスクリプト
「Editor」という名前のフォルダ配下に置く必要がある。
Resourcesと同様で他のフォルダを掘って、その下に「Editor」フォルダを配置しても大丈夫。

(スクリプト”CreateBuoys.cs”の内容全文はこの投稿の最後に掲載しておきます。)
・UnityEditor名前空間の型の使用を許可
通常のUnityEngine以外にUnityEditorを指定しておく。
using UnityEngine;
using UnityEditor;
using System.Collections;
・メニューアイテムの追加

[MenuItem("GameObject/Create Other/Create Buoys")]
static void Init() {
EditorWindow.GetWindow<CreateBuoys>(true, "Create Buoys");
}
“GameObject/Create Other/”配下を指定することで、Hierarchyビューの「Create」のメニューにも追加される。
・ウィンドウの表示
メニューから「Create Buoys」が選択された時に専用のウィンドウを表示する。
[MenuItem("GameObject/Create Other/Create Buoys")]
static void Init() {
EditorWindow.GetWindow<CreateBuoys>(true, "Create Buoys");
}
EditorWindow.GetWindow
・ウィンドウ内容の表示
必要なメンバ変数を定義し、OnGUI()内で記述する。
public class CreateBuoys : EditorWindow {
private GameObject parent;
private GameObject prefab;
private int numX = 1;
private int numY = 1;
private int numZ = 1;
private float intervalX = 1;
private float intervalY = 1;
private float intervalZ = 1;
void OnGUI() {
try {
parent = EditorGUILayout.ObjectField("Parent", parent, typeof(GameObject), true) as GameObject;
prefab = EditorGUILayout.ObjectField("Prefab", prefab, typeof(GameObject), true) as GameObject;
GUILayout.Label("X : ", EditorStyles.boldLabel);
numX = int.Parse(EditorGUILayout.TextField("num", numX.ToString()));
intervalX = int.Parse(EditorGUILayout.TextField("interval", intervalX.ToString()));
GUILayout.Label("Y : ", EditorStyles.boldLabel);
numY = int.Parse(EditorGUILayout.TextField("num", numY.ToString()));
intervalY = int.Parse(EditorGUILayout.TextField("interval", intervalY.ToString()));
GUILayout.Label("Z : ", EditorStyles.boldLabel);
numZ = int.Parse(EditorGUILayout.TextField("num", numZ.ToString()));
intervalZ = int.Parse(EditorGUILayout.TextField("interval", intervalZ.ToString()));
GUILayout.Label("", EditorStyles.boldLabel);
if (GUILayout.Button("Create")) Create();
} catch (System.FormatException) {}
}
通常のOnGUI()でGUIを作るのと同じ要領だが、EditorGUILayoutのパーツが使える。
詳細はリファレンスを参照。
GUILayout
EditorGUILayout
・ウィンドウ表示開始時に選択していたオブジェクトを取得

void OnEnable() {
if (Selection.gameObjects.Length > 0) parent = Selection.gameObjects[0];
}
ウィンドウ表示時に自動で呼ばれるメソッド。
Selection.gameObjects
・ウィンドウ表示後に選択したオブジェクトを反映

void OnSelectionChange() {
if (Selection.gameObjects.Length > 0) prefab = Selection.gameObjects[0];
Repaint();
}
選択内容の変更時に自動で呼ばれるメソッド。
・「Create」ボタン押下時に実際にブイを作成する
private void Create() {
if (prefab == null) return;
int count = 0;
Vector3 pos;
pos.x = -(numX - 1) * intervalX / 2;
for (int x = 0; x < numX; x++) {
pos.y = -(numY - 1) * intervalY / 2;
for (int y = 0; y < numY; y++) {
pos.z = -(numZ - 1) * intervalZ / 2;
for (int z = 0; z < numZ; z++) {
GameObject obj = Instantiate(prefab, pos, Quaternion.identity) as GameObject;
obj.name = prefab.name + count++;
if (parent) obj.transform.parent = parent.transform;
Undo.RegisterCreatedObjectUndo(obj, "Create Buoys");
pos.z += intervalZ;
}
pos.y += intervalY;
}
pos.x += intervalX;
}
}
普通にInstantiate()すれば、Hierarchyに追加される。
この際、Undoを設定しておくことにより、後で取り消しができる。(66行目)
Editor拡張を利用したのは初めてだが、食わず嫌いだったと反省している。
Unityで新規にGameObjectを作った時にHierarchyのトップに作成されてしまうというのは、良く聞く不満だが、Editor拡張を使えばその辺りも何とかできそうだと言うのが分かった。
using UnityEngine;
using UnityEditor;
using System.Collections;
public class CreateBuoys : EditorWindow {
private GameObject parent;
private GameObject prefab;
private int numX = 1;
private int numY = 1;
private int numZ = 1;
private float intervalX = 1;
private float intervalY = 1;
private float intervalZ = 1;
[MenuItem("GameObject/Create Other/Create Buoys")]
static void Init() {
EditorWindow.GetWindow<CreateBuoys>(true, "Create Buoys");
}
void OnEnable() {
if (Selection.gameObjects.Length > 0) parent = Selection.gameObjects[0];
}
void OnSelectionChange() {
if (Selection.gameObjects.Length > 0) prefab = Selection.gameObjects[0];
Repaint();
}
void OnGUI() {
try {
parent = EditorGUILayout.ObjectField("Parent", parent, typeof(GameObject), true) as GameObject;
prefab = EditorGUILayout.ObjectField("Prefab", prefab, typeof(GameObject), true) as GameObject;
GUILayout.Label("X : ", EditorStyles.boldLabel);
numX = int.Parse(EditorGUILayout.TextField("num", numX.ToString()));
intervalX = int.Parse(EditorGUILayout.TextField("interval", intervalX.ToString()));
GUILayout.Label("Y : ", EditorStyles.boldLabel);
numY = int.Parse(EditorGUILayout.TextField("num", numY.ToString()));
intervalY = int.Parse(EditorGUILayout.TextField("interval", intervalY.ToString()));
GUILayout.Label("Z : ", EditorStyles.boldLabel);
numZ = int.Parse(EditorGUILayout.TextField("num", numZ.ToString()));
intervalZ = int.Parse(EditorGUILayout.TextField("interval", intervalZ.ToString()));
GUILayout.Label("", EditorStyles.boldLabel);
if (GUILayout.Button("Create")) Create();
} catch (System.FormatException) {}
}
private void Create() {
if (prefab == null) return;
int count = 0;
Vector3 pos;
pos.x = -(numX - 1) * intervalX / 2;
for (int x = 0; x < numX; x++) {
pos.y = -(numY - 1) * intervalY / 2;
for (int y = 0; y < numY; y++) {
pos.z = -(numZ - 1) * intervalZ / 2;
for (int z = 0; z < numZ; z++) {
GameObject obj = Instantiate(prefab, pos, Quaternion.identity) as GameObject;
obj.name = prefab.name + count++;
if (parent) obj.transform.parent = parent.transform;
Undo.RegisterCreatedObjectUndo(obj, "Create Buoys");
pos.z += intervalZ;
}
pos.y += intervalY;
}
pos.x += intervalX;
}
}
}