ひつじTips

技術系いろいろつまみ食います。

[Unity]CustomEditorを使わずに,bool変数の状態によってインスペクタ上の変数を動的に編集不可にしたり,非表示にしたりする方法

こういう実装で,

public class ConditionalDisableAttrTest : MonoBehaviour
{
    public bool flag = false;

    [FlagConditionalDisableInInspector("flag")]
    public string editableStrIfTrue = "a";
}


こういう感じに編集可/不可を制御でき,

flagがfalseのときは編集不可になる

こうすれば,

public class ConditionalDisableAttrTest : MonoBehaviour
{
    [System.Serializable]
    public class TestClass
    {
        public Vector3 p;
        public Quaternion q;
    }

    [Header("Flag control")]
    public bool flag = false;

    [FlagConditionalDisableInInspector("flag", conditionalInvisible: true)]
    public TestClass invisibleClsIfFalse;
}


表示/非表示も制御できる(自作クラス/構造体でもOK)

flagがfalseのときはインスペクタから見えなくなる

というのを実現するネタです.


こういうことをしたくて「Unity インスペクタ 変数 動的」とかでググると,CustomEditorを使う方法が出てきます.

が,これだとCustomEditorで対応したクラスでしか使えない = 他のクラスの変数に使い回しができなくて,ちょろっと使いたいときに不便な感じです.

(プロダクトレベルとかで,しっかり作り込む場合はこちらの方がいいと思いますが)

ということで,今回はCustomEditorは使わずに,任意のクラスの変数に適用できるAttributeを使って実装しています.

正直Unityのエディタ拡張は全然詳しくないので,バグってたらすみません.
一通りの型で使えることは確認しましたが,漏れもあるだろうし,基本正常系しか確認できてません.他のAttributeとの組合せ時とかも未検証です(免責事項)


この記事ではbool型変数での制御だけなのですが,boolに加えて,intやenum,string,float変数などでも制御したい方は,こちらの記事に解説がありますので,ご覧ください

mu-777.hatenablog.com

使い方

まず,AttributeとEditor拡張のクラスを用意します.その後,編集不可にしたり非表示にしたい変数に対してAttributeをつけます.

というだけです.順番に見ていきます.

AttributeとEditor拡張のクラスの作成

以下に貼ったコードのファイルを作成します.

1つ目(FlagConditionalDisableDrawer.cs)をEditorフォルダに作ってください.2つ目(FlagConditionalDisableInInspectorAttribute.cs)は普通にScriptフォルダなどでOKです.

gist8646f03f00a855ab3b8f21ecb1179f3c

アトリビュートの利用

あとは,任意のMonoBehaviourなクラスに,

  • 表示制御用のbool変数(以下でいうflagメンバ変数)を用意
  • flag変数によって編集不可にしたり非表示させたりしたい変数(以下でいうeditableStrIfTrue)を用意
    • FlagConditionalDisableInInspectorアトリビュートをつけ,第一引数に制御用フラグの変数名(この例だと"flag")をstringで設定

してください

public class ConditionalDisableAttrTest : MonoBehaviour
{
    public bool flag = false;

    [FlagConditionalDisableInInspector("flag")]
    public string editableStrIfTrue = "a";
}


このConditionalDisableAttrTestをUnityのHierarchyに配置すると,flagがtrueだとeditableStrIfTrueが編集可に,falseだと編集不可になると思います.

flagがfalseのときは編集不可になる


挙動を逆にしたい場合,つまり,flagがfalseのときに編集可にしたい場合は,AttributeのtrueThenDisable引数をtrueに設定してください.

    [FlagConditionalDisableInInspector("flag", trueThenDisable: true)]
    public Rect editableRectIfFalse;


また,編集可/不可ではなく,表示/非表示を切り替えたい場合は,conditionalInvisibleをtrueに設定してください.trueThenDisableも併用可能です.

    [FlagConditionalDisableInInspector("flag", conditionalInvisible: true)]
    public float visibleFloatIfTrue = 0f;

    [FlagConditionalDisableInInspector("flag", trueThenDisable: true, conditionalInvisible: true)]
    public Vector3 visibleVec3IfFalse;


この4つを並べると,以下のようになると思います.

flag conditional control in inspector
flag変数の状態によって,各変数の表示が編集できたりできなかったり,表示されたりされなかったり

こうなると期待通りの挙動です🎉

簡単な解説

Attributeを使ってInspector上の表示のさせ方を変えるEditor拡張については,PropertyDrawerの使い方を解説しているUnity公式, docs.unity3d.com

もしくは「PropertyDrawer Editor拡張」とかでググって出てきたブログ記事などをご参考ください.


今回のネタでややこしいのは,制御用変数のステートの取得です.

つまり,これ↓のeditableStrIfTrueのEditor表示制御している処理で,どうやってflag変数の値を取得するか,というところです

    public bool flag = false;

    [FlagConditionalDisableInInspector("flag")]
    public string editableStrIfTrue = "a";


で,これをやってるのが,FlagConditionalDisableDrawerの以下の箇所です.

    var attr = base.attribute as FlagConditionalDisableInInspectorAttribute;
    var prop = property.serializedObject.FindProperty(attr.FlagVariableName);


property,つまりeditableStrIfTrueのSerializedPropertyから,これが所属しているSerializedObjectをたどり,そのSerializedObjectが持つPropertyを変数名を使って取得しています.

変数名は,Attributeの第一引数で設定した値がattr.FlagVariableNameに格納されているので,それを使います.

とまぁこれだけっちゃこれだけです.UnityのReferenceとにらめっこしつつ見つけた感じです.


あとは,非表示時にInspectorの変数表示位置を詰めたりするために,GetPropertyHeightに手を入れてるぐらいですかね.

非表示の場合(ConditionalInvisibleがtrueかつ条件に適合する場合)は,高さをSpacing分だけ詰めてあげると,見た目に変なスペースができなくて良さげです.表示する場合はEditorGUI.GetPropertyHeightでpropertyに適した高さを返します.

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        var attr = base.attribute as FlagConditionalDisableInInspectorAttribute;
        var prop = property.serializedObject.FindProperty(attr.FlagVariableName);
        if(attr.ConditionalInvisible && IsDisable(attr, prop))
        {
            return -EditorGUIUtility.standardVerticalSpacing;
        }
        return EditorGUI.GetPropertyHeight(property, true);
    }


まぁコード行数もめちゃ短いので,特段解説することもないですね.