チリペヂィア

リンクフリー。サンプルコードなどは関連記事内でライセンスについて明示されない限り商用利用なども自由に行って構いませんが、自己責任でお願いします。またこれら日記内容の著作権自体は放棄していません。引用部分については引用元の権利に従ってください。

DrawDefaultInspectorで使ってたSerializedPropertyもうちょっと掘り下げ。

以前ネタにしたDrawDefaultInspector()の実装を読み直します。

internal static bool DoDrawDefaultInspector(SerializedObject obj)
{
	EditorGUI.BeginChangeCheck();
	obj.Update();
	SerializedProperty iterator = obj.GetIterator();
	bool enterChildren = true;
	while (iterator.NextVisible(enterChildren))
	{
		EditorGUILayout.PropertyField(iterator, true, new GUILayoutOption[0]);
		enterChildren = false;
	}
	obj.ApplyModifiedProperties();
	return EditorGUI.EndChangeCheck();
}

ン?SerializePropertyはSerializedObject()のイテレータとして振舞います。ただ、NextVisible()で前進する時、子階層に入るかどうかしか指定できません。

「SerializePropertyは終端をどのように設定されるの?同階層の終端時、上階層に戻っちゃうの?戻っちゃわないの?どこまでイテレートするの?」

答え:基本的に終端情報は持たず、SerializedObjectの最後まで進む。

だいたいSerializedObjectってコンポーネント丸ごとの情報です。では当然ながら気になる次の疑問。

ComponentにAとBというクラスフィールドがあって、それらのクラスの中にメンバがあるが、Aのクラスメンバだけをイテレートしたい場合、NextVisible()するだけじゃBの最後までイテレーションが進んじゃう、どうしたら?

これについてはSerializedProperty.depthまたはSerializedProperty.propertyPathメンバを使います。depthはネスト階数。SerializedProperty.propertyPathについては、公式サイト曰く

Full path of the property. (Read Only)

一行。わっかりやすーい!もうちょっと書いてほしいのですが、こんな感じ?

  • コンポーネントにおけるAという名前のフィールドをイテレートする時、SerializedProperty.propertyPathは"A"
  • コンポーネントにおけるA.firstMemberという名前のフィールドをイテレートする時、SerializedProperty.propertyPathは"A.firstMember"

区切り文字がドットである事くらいは明記するか、文字定数を定義しておくれ…ナンカコワイヨゥ。

あえてパスを比較するなら、こんな感じ。

System.String rootPath = iterator.propertyPath + "."; // もしiteratorがコンポーネントの中で、Aという名前のフィールドだったら"A"なので、"A."ではじまるメンバだけ処理する
bool enterChildren = true;
while (property.NextVisible(enterChildren))
{
    if (property.propertyPath.StartsWith(rootPath) == false) break; // パスが不一致、離脱!
    EditorGUILayout.PropertyField(property, true, new GUILayoutOption[0]);
    enterChildren = false;
}

もちろんdepth比較する方が処理も軽いです。

int rootDepth = iterator.depth; // この階層に戻ってきたら抜ける
bool enterChildren = true;
while (property.NextVisible(enterChildren))
{
    if (property.depth <= rootDepth) break; // 離脱!
    EditorGUILayout.PropertyField(property, true, new GUILayoutOption[0]);
    enterChildren = false;
}

これと下記の3系統をおさえておけば、独自のインスペクタ描画はかなり出来るようになるはず。

SerializeProperty.isArray

このプロパティはシリアライズされた配列かどうか返します。配列操作については、ほにゃららArrayElementなんちゃらというメソッドが存在しているのでそれらをSeeAlso。
ClearArray(), DeleteArrayElementAtIndex(), GetArrayElementAtIndex(), InsertArrayElementAtIndex(), MoveArrayElement()
一揃いあるもののUnityはこういうところがホントなんつーかセンスがアレな感じです。

SerializeProperty.hasChildren

このプロパティに子階層が存在するか、ようするにクラス型で内部にメンバが存在してるか返します。この子階層に限り処理をする方法は上述したとおり。

SerializeProperty.isExpanded

表示上重要。配列やクラスはインスペクタで折りたためますが、開いているならtrueを返します。もちろん閉じてる時は子階層をDrawする必要はありませんが、自分で対応する必要があります。「閉じてる時にDrawするな、開いてたらDrawしないとおかしいぞ」というメンバです。

よしこれでPropertyDrawerシリーズは終了だ、と思いきや、実は配列表示カスタマイズするのに、もうひとつやんなきゃいけないトピックを忘れていました。次回に続く…。