Programming Serendipity

気まぐれに大まかに生きるブログ

NGUIのようにTransformを便利に拡張する

f:id:q7z:20160706222759p:plain

Transformをこのようにする方法を紹介します。

このスクリプトをEditorフォルダ内に入れてください。

gist.github.com

もろもろ

  • [CustomEditor(typeof(Transform))]によって、「Transformの描画処理を置き換える」ことを高らかに宣言します。Editorを継承するのを忘れずに。
  • 実際の描画処理はOnInspectorGUI()をoverrideします。
  • serializedObject.Update();でインスペクタからこのスクリプトへ最新データの取得、serializedObject.ApplyModifiedProperties();でこのスクリプトの変更をインスペクタに反映します。
  • targetというのが実際にインスペクタで対象になっているオブジェクト(今回はTransform)なので、必要な型にキャストして使用します。
  • Vector3Field()などの描画系関数の戻り値が、インスペクタ上で値を変更したときの変更後の値になるので、それを適用することで変更の反映を行います。
  • Vector3の仕様からか、インスペクタの幅が狭くなると自動的に2行分の高さで描画されてしまうので、GUILayout.Height(16)で明示的にどの幅でも1行分の高さとして描画させます。
  • RotationでVector3Field()に渡す値をtransform.rotation.eulerAnglesにしてしまってしばらくはまった。なぜだめなのかを理解するにはUnityの内部と四元数の理解が要るかも…
  • 値の変更もこのスクリプトが一手に請け負っているので、Undoの登録もこのスクリプトのやりたいタイミングでやる必要があります。
  • 流れは、1.Undo.RecordObjects(targets, "Change Transform");を「値を変更する前に」呼ぶ 2. 値を変更する(すると変更を検知してUndoに登録される) 3.SetDirty()してシーンに保存すべき変更があることをエディターに伝える
  • なのでRecordObjects()RecordObjectsOnNextChange()というほうが適切って感じ。
  • 複数選択もありうるのでそれぞれtargets, RecordObjects()にしてます。要[CanEditMultipleObjects]
  • RecordObjects()の第2引数の文字列はEdit > Undo/Redoに表示される文字列です。

NGUIの実装は、いまよりも制約の多い時代のUnityの時に書かれたためか、かなり複雑なコードになっていますが、やっていることは基本的には同じです。簡単かと思いきや、意外と考えることの多いTransform拡張でした。