はじめに
とあるプロジェクトでエンジニアをやっている者です。
同プロジェクトのプランナー方より、ステージ作成をスムーズに行いたいという要望を受けまして、
オブジェクトを3D空間のグリッドに沿って配置するツールを作成、そして改良を続けています。
使用するのはプランナー方ですが、プロジェクトメンバーなら誰でもステージ作成ができる作りになっています。
・アサインされた頃はメンバーが少なく、スケジュールも決まっていない。
・大量のステージが一つ一つシーンごと作られていた。
・Unityを扱える者が少なかった。
このような状況で、メンバーが増える前にステージを作る手法を確立して欲しいと言われたのが始まりです。
この記事はその作成過程で思った事、身についた事の紹介です。
Unity 2019.1.9f1
作った(作っている)もの
Tiled Map Editor などの表現方法を参考にしつつ、Z軸を増やしてグリッドを3面にした自作ツールです。
XYZ面をそれぞれの軸上で移動させることで、3D空間での位置・範囲の指定を可能にしました。
面と線はGizmoで表現しています。
(類似物は既にたくさん存在すると思いますが)
アイコンやテクスチャが写り込まないように、スクショは表示物を消してありますが、
配置物リストやファイル読み書き、勝敗条件設定などの機能が画面端に並んでいます。
上空を囲ったり、Y面の高さを変えたり
機能の追加といえばエディタ拡張
Unityで自作機能を追加するとなると、エディタ拡張を行うのが一般的かと思います。
自分も活用箇所は多いです。
非エンジニアの方に向けて微調整用のコンポーネントを提供する場合は特に。
値の設定・微調整ならエディタ拡張でなんら問題ないのですが、
「ステージを自由に作ってそのままテストプレイしたい!!」程の規模となると…?
多数の表示物と操作が…
初めは何の疑問もなくエディタ拡張で作っていました。
- 別ウィンドウを出したり、SceneView上にボタンを出したり。
- マウス左ボタンをオブジェクト選択からグリッド上の範囲指定に変え。右ボタンはそのままカメラ操作。
- XYZ軸の選択や配置・削除など処理切り替えはキーボード。
- UndoRedoも必要らしいのでキーボードにしよう。(元々備わってるけどそのままじゃ自作物には効かん…)
- 画面表示物へのクリック。配置数を表示物に反映。(画面更新のタイミングがややこしい…)
- 各ViewやInspectorへの入力も生きている。
考慮する点が段々と増えてきます。
Unityにはあらかじめ多数のショートカットキーが設定されているのでキー操作がカブる。
しかもOS別のショートカットキーもあるのでそれとカブったりカブらなかったり。
油断すると関係ない機能が暴発します。
何より、毎フレームの処理系がやりづらい印象でした。
エディタ上の表示更新タイミングがわかりにくい。入力を受け取るタイミングはいつ?
なんで再生時とこんなに違うん…?
正直ゲーム自体を作る方が簡単… ってことは?
再生時にやれば自在ですやん
入力、更新などを全てこちらで制御できる!
ゲーム内の演出を行うのと同様の処理で表示物が動かせる!
ということで、思い切ってツールを丸ごと作り直し、再生ボタンを押してゲームとして動作するツールに変えることで問題点が全部解決しました。
新たな要望が来てもUnityの動作と衝突せず取り入れられます。
全てSceneViewの拡張でなんとかしようと、囚われ過ぎてた気がしました。
Gameタブ設定の動的切り替え
ステージ編集をエディタ再生時に持ってきたことで、再生中にGameタブのGizmosと解像度を動的に変更する必要が出てきました。毎回手動で切り替えるのは面倒です。
・ステージ編集にはGizmosのXYZ面が必要。(GameタブのGizmosは普段Offにしてることが多い)
・ステージ編集は出来るだけ大きく かつ PCのモニターで行うので横長のサイズ(1920x1080)が理想。
・テストプレイはスマホ端末のサイズで行う必要がある。
GameタブはGameViewというクラスですが、非公開クラスでアクセスできないので、C#のリフレクションで対応しました。
GameViewクラスを覗く
Type型からGameViewのインスタンスと MethodInfo PropertyInfoを取ります。
GetMethods()や GetProperties()で調べていくと"SetShowGizmos" "selectedSizeIndex"がそれらしいと判明します。
var type_GameView = Assembly.Load ("UnityEditor.dll").GetType ("UnityEditor.GameView");
var instance = EditorWindow.GetWindow (type_GameView);
// type_GameView.GetMethods(BindingFlags.Public | BindingFlags.Instance);
// type_GameView.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance);
var gizmosInfo = type_GameView.GetMethod ("SetShowGizmos", BindingFlags.Public | BindingFlags.Instance);
var sizeIndexInfo = type_GameView.GetProperty ("selectedSizeIndex", BindingFlags.NonPublic | BindingFlags.Instance);
Gameタブの設定を変える
SetShowGizmosはbool型なので二択だけです。
selectedSizeIndexはGameタブの解像度プルダウンの上からの順番のようです。
この↑画像の場合現在値は 4 です。
上記で取得したInfoから処理を呼べば、Gameタブの設定を変えることができます。
// ステージ編集中は true テストプレイ中は false
gizmosInfo.Invoke(instance, new object[] { true } );
// ステージ編集中は 4
// iPhoneXとしてテストプレイするなら 5 か 6
sizeIndexInfo.SetValue(instance, 4, null);
まとめ
エディタ拡張では入力や表示更新の制御がよくわからない…
∟ツールを丸ごとゲーム扱いにして、制御しやすいエディター再生中に動かしてしまおう
∟使用者に説明がしやすく、要望も受け入れやすくなった
普通のゲーム中では意識しないUnityEditorの内部も操作したい…
∟リフレクションを利用すればある程度はいじれる
∟ツールがより便利に
ツール使用者にゲーム的な操作で扱ってもらえるので有意義な思い切りだったと思っています。
特にリフレクションの知識が得られましたし。
何より、時間に余裕があったからできたことですね。ありがたいです。