2023/06/05 : 初稿
Unity : 2021.3.15f1
やりたいこと
自作のクラスや構造体をMonoBehaviorのシリアライズパラメータとして持たせるが、
何かフラグなどをみてその内容を表示したりしなかったりしたい。
カスタムエディターを作ればいいけど面倒。
実現方法
カスタムアトリビュートを作成。
表示するかしないかの判定は
MonoBehaviourにinterfaceを継承させてそのメソッドで。
ToggleShowInspector.cs
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Utils
{
public sealed class ToggleShowInspectorAttribute : PropertyAttribute
{
public interface IHandler
{
bool ShouldShowInInspector(UnityEngine.Object obj, string propName);
}
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(ToggleShowInspectorAttribute))]
public sealed class ToggleShowInspectorAttributeDrawer : PropertyDrawer
{
// OnGUI
public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
{
// 所有するMonoBehavior取得
var owner = _property.serializedObject.targetObject;
// そいつがIHandlerを継承しているならそれを取得
var handler = owner as ToggleShowInspectorAttribute.IHandler;
// そいつに表示すべきかどうかを確認
var shouldShow = (handler != null && handler.ShouldShowInInspector(owner, _property.name));
// 表示しない
if (!shouldShow) {
return;
}
// 表示する
if (_property.hasVisibleChildren) {
DrawDefaultGUI(_position, _property, _label);
} else {
EditorGUI.PropertyField(_position, _property, _label);
}
}
// GetPropertyHeight
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return GetDefaultPropertyHeight(property, label);
}
// プロパティの描画
static void DrawDefaultGUI(Rect position, SerializedProperty property, GUIContent label)
{
// プロパティ取得
property = property.serializedObject.FindProperty(property.propertyPath);
// 表示エリア
var fieldRect = position;
fieldRect.height = EditorGUIUtility.singleLineHeight;
using (new EditorGUI.PropertyScope(fieldRect, label, property)) {
// ラベル
if (property.hasVisibleChildren) { // 子要素があれば折り畳み表示
property.isExpanded = EditorGUI.Foldout(fieldRect, property.isExpanded, label);
} else { // 子要素が無ければラベルだけ表示
EditorGUI.PropertyField(fieldRect, property);
return;
}
fieldRect.y += EditorGUIUtility.singleLineHeight;
fieldRect.y += EditorGUIUtility.standardVerticalSpacing;
// 展開しているなら中身
if (property.isExpanded) {
using (new EditorGUI.IndentLevelScope()) {
// 最初の要素を描画
if (!property.NextVisible(true)) {
EditorGUI.PropertyField(fieldRect, property, true);
} else {
var depth = property.depth;
EditorGUI.PropertyField(fieldRect, property, true);
fieldRect.y += EditorGUI.GetPropertyHeight(property, true);
fieldRect.y += EditorGUIUtility.standardVerticalSpacing;
// それ以降の要素を描画
while (property.NextVisible(false)) {
// depthが最初の要素と同じもののみ処理
if (property.depth != depth) {
break;
}
// 要素描画
EditorGUI.PropertyField(fieldRect, property, true);
fieldRect.y += EditorGUI.GetPropertyHeight(property, true);
fieldRect.y += EditorGUIUtility.standardVerticalSpacing;
}
}
}
}
}
}
// プロパティの高さを取得
static float GetDefaultPropertyHeight(SerializedProperty property, GUIContent label)
{
var height = 0.0f;
// プロパティ取得
property = property.serializedObject.FindProperty(property.propertyPath);
// ラベルの高さ
height += EditorGUIUtility.singleLineHeight;
height += EditorGUIUtility.standardVerticalSpacing;
// 子要素が無い/畳んでいるならラベルだけ
if (!property.hasVisibleChildren || !property.isExpanded) {
return height;
}
// 最初の要素
if (property.NextVisible(true)) {
var depth = property.depth;
height += EditorGUI.GetPropertyHeight(property, true);
height += EditorGUIUtility.standardVerticalSpacing;
// それ以降の要素
while (property.NextVisible(false)) {
// depthが最初の要素と同じもののみ処理
if (property.depth != depth) {
break;
}
height += EditorGUI.GetPropertyHeight(property, true);
height += EditorGUIUtility.standardVerticalSpacing;
}
// 最後はスペース不要なので削除
height -= EditorGUIUtility.standardVerticalSpacing;
}
// 高さを返す
return height;
}
}
#endif
}
使い方はこんな感じ。
Test.cs
using UnityEngine;
using System;
using Utils;
public class Test : MonoBehaviour, ToggleShowInspectorAttribute.IHandler
{
// トグル
[SerializeField] bool Toggle;
// なんかデータ
[Serializable]
struct SomethingData
{
public string StrData;
}
[SerializeField][ToggleShowInspector] SomethingData Data;
// ToggleShowInspectorAttribute.IHandler
public bool ShouldShowInInspector(UnityEngine.Object instance, string propName)
{
// get instance
var main = instance as Test;
if (main == null) {
return false;
}
// Toggleがtrueの時だけDataを表示
if (propName == "Data") {
return main.Toggle;
}
// 表示しない
return false;
}
}