アマゾンバナーリンク

ディスプレイ広告

スポンサーリンク

UnityのInspectorを超便利にするアセットodin【第3回】

こんにちは!ジェイです。odinのCustom Editor Windows、Odin Project Validator、などについて解説しました。今回は説明よりも実践的に使える例を解説していきます。

記事内広告

Type Specifics

Asset List Attribute

AssetListsは、リストや配列、単一タイプの要素に使用され、デフォルトのリストドロワーを、指定されたフィルターで可能なすべてのアセットのリストに置き換えます。これを使うと、プロジェクトウィンドウを操作することなく、リストや配列からアセットをフィルタリングしたり、包含したり除外したりすることができます。

[AssetList]
[PreviewField(70, ObjectFieldAlignment.Center)]
public Texture2D SingleObject;

[AssetList(Path = "/Plugins/Sirenix/")]
public List<ScriptableObject> AssetList;

[FoldoutGroup("Filtered Odin ScriptableObjects", expanded: false)]
[AssetList(Path = "Plugins/Sirenix/")]
public ScriptableObject Object;

[AssetList(AutoPopulate = true, Path = "Plugins/Sirenix/")]
[FoldoutGroup("Filtered Odin ScriptableObjects", expanded: false)]
public List<ScriptableObject> AutoPopulatedWhenInspected;

[AssetList(LayerNames = "MyLayerName")]
[FoldoutGroup("Filtered AssetLists examples")]
public GameObject[] AllPrefabsWithLayerName;

[AssetList(AssetNamePrefix = "Rock")]
[FoldoutGroup("Filtered AssetLists examples")]
public List<GameObject> PrefabsStartingWithRock;

[FoldoutGroup("Filtered AssetLists examples")]
[AssetList(Tags = "MyTagA, MyTabB", Path = "/Plugins/Sirenix/")]
public List<GameObject> GameObjectsWithTag;

[FoldoutGroup("Filtered AssetLists examples")]
[AssetList(CustomFilterMethod = "HasRigidbodyComponent")]
public List<GameObject> MyRigidbodyPrefabs;

private bool HasRigidbodyComponent(GameObject obj)
{
    return obj.GetComponent<Rigidbody>() != null;
}

Asset Selector Attribute

AssetSelector属性は、オブジェクトフィールドの横に小さなボタンを付加し、属性からカスタマイズできるアセットのドロップダウンをユーザーに提示します。

[AssetSelector]
public Material AnyAllMaterials;

[AssetSelector]
public Material[] ListOfAllMaterials;

[AssetSelector(FlattenTreeView = true)]
public PhysicMaterial NoTreeView;

[AssetSelector(Paths = "Assets/MyScriptableObjects")]
public ScriptableObject ScriptableObjectsFromFolder;

[AssetSelector(Paths = "Assets/MyScriptableObjects|Assets/Other/MyScriptableObjects")]
public Material ScriptableObjectsFromMultipleFolders;

[AssetSelector(Filter = "name t:type l:label")]
public UnityEngine.Object AssetDatabaseSearchFilters;

[Title("Other Minor Features")]

[AssetSelector(DisableListAddButtonBehaviour = true)]
public List<GameObject> DisableListAddButtonBehaviour;

[AssetSelector(DrawDropdownForListElements = false)]
public List<GameObject> DisableListElementBehaviour;

[AssetSelector(ExcludeExistingValuesInList = false)]
public List<GameObject> ExcludeExistingValuesInList;

[AssetSelector(IsUniqueList = false)]
public List<GameObject> DisableUniqueListBehaviour;

[AssetSelector(ExpandAllMenuItems = true)]
public List<GameObject> ExpandAllMenuItems;

[AssetSelector(DropdownTitle = "Custom Dropdown Title")]
public List<GameObject> CustomDropdownTitle;

Child Game Objects Only Attribute

ChildGameObjectsOnly属性はComponentsとGameObjectフィールドで使用でき、オブジェクトフィールドの横に小さなボタンが表示され、すべての子ゲームオブジェクトから割り当て可能なオブジェクトを検索し、ドロップダウンで表示してユーザーが選択できるようにします。

[ChildGameObjectsOnly]
public Transform ChildOrSelfTransform;

[ChildGameObjectsOnly]
public GameObject ChildGameObject;

[ChildGameObjectsOnly(IncludeSelf = false)]
public Light[] Lights;

Color Palette Attribute

ColorPaletteは、任意のColorプロパティで使用され、定義可能なさまざまなパレットから色を選択できるようにします。ユーザーが定義済みのカラーオプションから選択できるようにするには、これを使用します。

[ColorPalette]
public Color ColorOptions;

[ColorPalette("Underwater")]
public Color UnderwaterColor;

[ColorPalette("My Palette")]
public Color MyColor;

public string DynamicPaletteName = "Clovers";

// The ColorPalette attribute supports both 
// member references and attribute expressions.
[ColorPalette("$DynamicPaletteName")]
public Color DynamicPaletteColor;

[ColorPalette("Fall"), HideLabel]
public Color WideColorPalette;

[ColorPalette("Clovers")]
public Color[] ColorArray;

// ------------------------------------
// カラーパレットはコードからアクセスして変更することができます。
// カラーパレットは、ビルドに自動的に含まれないことに注意してください。
// しかし、ColorPaletteManagerを使ってすべてのカラーパレットを簡単に取得し、次のようにゲームに含めることができます。
// を使ってすべてのカラーパレットを取得し、ゲームに組み込むことができます。
// ------------------------------------

[FoldoutGroup("Color Palettes", expanded: false)]
[ListDrawerSettings(IsReadOnly = true)]
[PropertyOrder(9)]
public List<ColorPalette> ColorPalettes;

[Serializable]
public class ColorPalette
{
    [HideInInspector]
    public string Name;

    [LabelText("$Name")]
    [ListDrawerSettings(IsReadOnly = true, Expanded = false)]
    public Color[] Colors;
}

[FoldoutGroup("Color Palettes"), Button(ButtonSizes.Large), GUIColor(0, 1, 0), PropertyOrder(8)]
private void FetchColorPalettes()
{
    this.ColorPalettes = Sirenix.OdinInspector.Editor.ColorPaletteManager.Instance.ColorPalettes
        .Select(x => new ColorPalette()
        {
            Name = x.Name,
            Colors = x.Colors.ToArray()
        })
        .ToList();
}

Display As String Attribute

DisplayAsStringは、任意のプロパティに使用され、文字列をテキストとしてインスペクタに表示します。インスペクタに文字列を表示するが、編集はできないようにしたい場合に使用します。

[InfoBox(
    "Instead of disabling values in the inspector in order to show some information or debug a value. " +
    "You can use DisplayAsString to show the value as text, instead of showing it in a disabled drawer")]
[DisplayAsString]
public Color SomeColor;

[BoxGroup("SomeBox")]
[HideLabel]
[DisplayAsString]
public string SomeText = "Lorem Ipsum";

[InfoBox("The DisplayAsString attribute can also be configured to enable or disable overflowing to multiple lines.")]
[HideLabel]
[DisplayAsString]
public string Overflow = "A very very very very very very very very very long string that has been configured to overflow.";

[HideLabel]
[DisplayAsString(false)]
public string DisplayAllOfIt = "A very very very very very very very very long string that has been configured to not overflow."

Enum Paging Attribute

[EnumPaging]
public SomeEnum SomeEnumField;

public enum SomeEnum
{
    A, B, C
}

Enum Toggle Buttons Attribute

[Title("Default")]
public SomeBitmaskEnum DefaultEnumBitmask;

[Title("Standard Enum")]
[EnumToggleButtons]
public SomeEnum SomeEnumField;

[EnumToggleButtons, HideLabel]
public SomeEnum WideEnumField;

[Title("Bitmask Enum")]
[EnumToggleButtons]
public SomeBitmaskEnum BitmaskEnumField;

[EnumToggleButtons, HideLabel]
public SomeBitmaskEnum EnumFieldWide;

public enum SomeEnum
{
    First, Second, Third, Fourth, AndSoOn
}

[System.Flags]
public enum SomeBitmaskEnum
{
    A = 1 << 1,
    B = 1 << 2,
    C = 1 << 3,
    All = A | B | C
}

File Path Attribute

FilePathは、文字列プロパティで使用され、ファイルパスのためのインターフェースを提供します。

// デフォルトでは、FolderPathは、Unityプロジェクトへの相対パスを提供します。
[FilePath]
public string UnityProjectPath;

// カスタムの親パスを指定することも可能です。親パスは、Unityプロジェクトからの相対パスでも、絶対パスでも構いません。
[FilePath(ParentFolder = "Assets/Plugins/Sirenix")]
public string RelativeToParentPath;

// 親パスを使って、FilePathはリソースフォルダへの相対パスを指定することもできます。
[FilePath(ParentFolder = "Assets/Resources")]
public string ResourcePath;

// 許可される拡張子をカンマで区切って指定します。ドットはオプションです。
[FilePath(Extensions = "cs")]
[BoxGroup("Conditions")]
public string ScriptFiles;

// By setting AbsolutePath to true, the FilePath will provide an absolute path instead.
[FilePath(AbsolutePath = true)]
[BoxGroup("Conditions")]
public string AbsolutePath;

// FilePath can also be configured to show an error, if the provided path is invalid.
[FilePath(RequireExistingPath = true)]
[BoxGroup("Conditions")]
public string ExistingPath;

// By default, FilePath will enforce the use of forward slashes. It can also be configured to use backslashes instead.
[FilePath(UseBackslashes = true)]
[BoxGroup("Conditions")]
public string Backslashes;

// FilePath also supports member references with the $ symbol.
[FilePath(ParentFolder = "$DynamicParent", Extensions = "$DynamicExtensions")]
[BoxGroup("Member referencing")]
public string DynamicFilePath;

[BoxGroup("Member referencing")]
public string DynamicParent = "Assets/Plugins/Sirenix";

[BoxGroup("Member referencing")]
public string DynamicExtensions = "cs, unity, jpg";

// FilePath also supports lists and arrays.
[FilePath(ParentFolder = "Assets/Plugins/Sirenix/Demos/Odin Inspector")]
[BoxGroup("Lists")]
public string[] ListOfFiles;

Folder Path Attribute

FolderPathは、文字列プロパティで使用され、ディレクトリパスのためのインターフェースを提供します。

// デフォルトでは、FolderPathは、Unityプロジェクトへの相対パスを提供します。
[FolderPath]
public string UnityProjectPath;

// カスタムの親パスを提供することも可能です。親パスは、Unityプロジェクトからの相対パスでも、絶対パスでも構いません。
[FolderPath(ParentFolder = "Assets/Plugins/Sirenix")]
public string RelativeToParentPath;

// 親パスを利用して、FolderPathはリソースフォルダへの相対パスを提供することもできます。
[FolderPath(ParentFolder = "Assets/Resources")]
public string ResourcePath;

// AbsolutePathをtrueに設定すると、FolderPathは絶対パスを提供します。
[FolderPath(AbsolutePath = true)]
[BoxGroup("Conditions")]
public string AbsolutePath;

// FolderPathは、提供されたパスが無効な場合に、エラーを表示するように設定することもできます。
[FolderPath(RequireExistingPath = true)]
[BoxGroup("Conditions")]
public string ExistingPath;

// デフォルトでは、FolderPathはフォワードスラッシュの使用を強制します。また、バックスラッシュを使用するように設定することもできます。
[FolderPath(UseBackslashes = true)]
[BoxGroup("Conditions")]
public string Backslashes;

// FolderPathは、$シンボルを使ったメンバー参照や属性表現にも対応しています。
[FolderPath(ParentFolder = "$DynamicParent")]
[BoxGroup("Member referencing")]
public string DynamicFolderPath;

[BoxGroup("Member referencing")]
public string DynamicParent = "Assets/Plugins/Sirenix";

// FolderPathはリストや配列もサポートしています。
[FolderPath(ParentFolder = "Assets/Plugins/Sirenix")]
[BoxGroup("Lists")]
public string[] ListOfFolders;

Hide In Inline Editors Attribute

プロパティがのInlineEditorAttribute中に描かれている場合、そのプロパティを非表示にします。

[InfoBox("Click the pen icon to open a new inspector window for the InlineObject too see the differences these attributes make.")]
[InlineEditor(Expanded = true)]
public MyInlineScriptableObject InlineObject;

[OnInspectorInit]
private void CreateData()
{
    InlineObject = ExampleHelper.GetScriptableObject<MyInlineScriptableObject>("Inline Object");
}

[OnInspectorDispose]
private void CleanupData()
{
    if (InlineObject != null) Object.DestroyImmediate(InlineObject);
}

Hide In Tables Attribute

HideInTables属性は、TableListAttributeを使用して描画されたテーブルで、メンバーが列として表示されないようにするために使用されます。

public MyItem Item = new MyItem();

[TableList]
public List<MyItem> Table = new List<MyItem>()
{
    new MyItem(),
    new MyItem(),
    new MyItem(),
};

[Serializable]
public class MyItem
{
    public string A;

    public int B;

    [HideInTables]
    public int Hidden;
}

Hide Mono Script Attribute

HideMonoScriptをクラスに適用して、インスペクタにScriptプロパティが表示されないようにします。

[InfoBox("Click the pencil icon to open new inspector for these fields.")]
public HideMonoScriptScriptableObject Hidden;

// MonoScripts がグローバルに隠されている場合は、ShowMonoScript オブジェクトでもスクリプトが隠されます。
public ShowMonoScriptScriptableObject Shown;

[OnInspectorInit]
private void CreateData()
{
    Hidden = ExampleHelper.GetScriptableObject<HideMonoScriptScriptableObject>("Hidden");
    Shown = ExampleHelper.GetScriptableObject<ShowMonoScriptScriptableObject>("Shown");
}

[OnInspectorDispose]
private void CleanupData()
{
    if (Hidden != null) Object.DestroyImmediate(Hidden);
    if (Shown != null) Object.DestroyImmediate(Shown);
}

Hide Network Behaviour Fields Attribute

HideNetworkBehaviourFieldsをクラスに適用すると、NetworkBehaviourのインスペクタに特殊な「Network Channel」および「Network Send Interval」プロパティが表示されなくなります。この属性は、NetworkBehaviourから派生していないクラスには影響しません。

Hide Reference Object Picker Attribute

Unity以外のシリアル化された参照型のプロパティの上に表示されているポリモーフィックなオブジェクトピッカーを非表示にします。

[Title("Hidden Object Pickers")]
[HideReferenceObjectPicker]
public MyCustomReferenceType OdinSerializedProperty1 = new MyCustomReferenceType();

[HideReferenceObjectPicker]
public MyCustomReferenceType OdinSerializedProperty2 = new MyCustomReferenceType();

[Title("Shown Object Pickers")]
public MyCustomReferenceType OdinSerializedProperty3 = new MyCustomReferenceType();

public MyCustomReferenceType OdinSerializedProperty4 = new MyCustomReferenceType();

// Protip: クラス定義自体にHideInInspector属性を置くことで、すべてのメンバーに対してグローバルに非表示にすることもできます。
// [HideReferenceObjectPicker]を使用しています。
public class MyCustomReferenceType
{
    public int A;
    public int B;
    public int C;
}

Inline Editor Attribute

InlineAttributeは、UnityEngine.Objectを継承したタイプのプロパティやフィールドで使用されます。これにはコンポーネントやアセットなどが含まれます。

[Title("Boxed / Default")]
[InlineEditor(InlineEditorObjectFieldModes.Boxed)]
public ExampleTransform Boxed;

[Title("Foldout")]
[InlineEditor(InlineEditorObjectFieldModes.Foldout)]
public ExampleTransform Foldout;

[Title("Hide ObjectField")]
[InlineEditor(InlineEditorObjectFieldModes.CompletelyHidden)]
public ExampleTransform CompletelyHidden;

[Title("Show ObjectField if null")]
[InlineEditor(InlineEditorObjectFieldModes.Hidden)]
public ExampleTransform OnlyHiddenWhenNotNull;

[OnInspectorInit]
private void CreateData()
{
    Boxed = ExampleHelper.GetScriptableObject<ExampleTransform>("Boxed");
    Foldout = ExampleHelper.GetScriptableObject<ExampleTransform>("Foldout");
    CompletelyHidden = ExampleHelper.GetScriptableObject<ExampleTransform>("Completely Hidden");
    OnlyHiddenWhenNotNull = ExampleHelper.GetScriptableObject<ExampleTransform>("Only Hidden When Not Null");
}

[OnInspectorDispose]
private void CleanupData()
{
    if (Boxed != null) Object.DestroyImmediate(Boxed);
    if (Foldout != null) Object.DestroyImmediate(Foldout);
    if (CompletelyHidden != null) Object.DestroyImmediate(CompletelyHidden);
    if (OnlyHiddenWhenNotNull != null) Object.DestroyImmediate(OnlyHiddenWhenNotNull);
}

Multi Line Property Attribute

MultiLinePropertyは、任意の文字列プロパティに使用されます。ユーザーが複数行のテキストボックスで文字列を編集できるようにするには、これを使用します。

[Multiline(10)]
public string UnityMultilineField = "";

[Title("Wide Multiline Text Field", bold: false)]
[HideLabel]
[MultiLineProperty(10)]
public string WideMultilineTextField = "";

[InfoBox("Odin supports properties, but Unity's own Multiline attribute only works on fields.")]
[ShowInInspector]
[MultiLineProperty(10)]
public string OdinMultilineProperty { get; set; }

Preview Field Attribute

UnityEngine.Objectタイプのプレビューをレンダリングする正方形のObjectFieldを描画します。このオブジェクトフィールドは、ドラッグ&ドロップにも対応しており、オブジェクトを別の四角いオブジェクトフィールドにドラッグすると、値が入れ替わります。コントロールキーを押しながら離すと値が入れ替わり、コントロールキーを押しながらオブジェクトフィールドをクリックすると、保持している値をすぐに削除することができます。

[PreviewField]
public Object RegularPreviewField;

[VerticalGroup("row1/left")]
public string A, B, C;

[HideLabel]
[PreviewField(50, ObjectFieldAlignment.Right)]
[HorizontalGroup("row1", 50), VerticalGroup("row1/right")]
public Object D;

[HideLabel]
[PreviewField(50, ObjectFieldAlignment.Left)]
[HorizontalGroup("row2", 50), VerticalGroup("row2/left")]
public Object E;

[VerticalGroup("row2/right"), LabelWidth(-54)]
public string F, G, H;

[OnInspectorInit]
private void CreateData()
{
    RegularPreviewField = ExampleHelper.GetTexture();
    D = ExampleHelper.GetTexture();
    E = ExampleHelper.GetTexture();
}

[InfoBox(
    "These object fields can also be selectively enabled and customized globally " +
    "from the Odin preferences window.\n\n" +
    " - Hold Ctrl + Click = Delete Instance\n" +
    " - Drag and drop = Move / Swap.\n" +
    " - Ctrl + Drag = Replace.\n" +
    " - Ctrl + drag and drop = Move and override.")]
[PropertyOrder(-1)]
[Button(ButtonSizes.Large)]
private void ConfigureGlobalPreviewFieldSettings()
{
    Sirenix.OdinInspector.Editor.GeneralDrawerConfig.Instance.OpenInEditor();   
}

Scene Objects Only Attribute

SceneObjectsOnly は、オブジェクトのプロパティに使用され、そのプロパティをプロジェクトアセットではなく、シーンオブジェクトに制限します。オブジェクトがプロジェクトアセットではなく、シーンオブジェクトであることを確認したい場合に使用します。

[Title("Assets only")]
[AssetsOnly]
public List<GameObject> OnlyPrefabs;

[AssetsOnly]
public GameObject SomePrefab;

[AssetsOnly]
public Material MaterialAsset;

[AssetsOnly]
public MeshRenderer SomeMeshRendererOnPrefab;

[Title("Scene Objects only")]
[SceneObjectsOnly]
public List<GameObject> OnlySceneObjects;

[SceneObjectsOnly]
public GameObject SomeSceneObject;

[SceneObjectsOnly]
public MeshRenderer SomeMeshRenderer;

Table List Attribute

インスペクタのリストや配列をテーブルとして表示します。

[TableList(ShowIndexLabels = true)]
public List<SomeCustomClass> TableListWithIndexLabels = new List<SomeCustomClass>()
{
    new SomeCustomClass(),
    new SomeCustomClass(),
};

[TableList(DrawScrollView = true, MaxScrollViewHeight = 200, MinScrollViewHeight = 100)]
public List<SomeCustomClass> MinMaxScrollViewTable = new List<SomeCustomClass>()
{
    new SomeCustomClass(),
    new SomeCustomClass(),
};

[TableList(AlwaysExpanded = true, DrawScrollView = false)]
public List<SomeCustomClass> AlwaysExpandedTable = new List<SomeCustomClass>()
{
    new SomeCustomClass(),
    new SomeCustomClass(),
};

[TableList(ShowPaging = true)]
public List<SomeCustomClass> TableWithPaging = new List<SomeCustomClass>()
{
    new SomeCustomClass(),
    new SomeCustomClass(),
};

[Serializable]
public class SomeCustomClass
{
    [TableColumnWidth(57, Resizable = false)]
    [PreviewField(Alignment = ObjectFieldAlignment.Center)]
    public Texture Icon;

    [TextArea]
    public string Description;

    [VerticalGroup("Combined Column"), LabelWidth(22)]
    public string A, B, C;

    [TableColumnWidth(60)]
    [Button, VerticalGroup("Actions")]
    public void Test1() { }

    [TableColumnWidth(60)]
    [Button, VerticalGroup("Actions")]
    public void Test2() { }

    [OnInspectorInit]
    private void CreateData()
    {
        Description = ExampleHelper.GetString();
        Icon = ExampleHelper.GetTexture();
    }
}

Table Matrix Attribute

[TableMatrix(HorizontalTitle = "Square Celled Matrix", SquareCells = true)]
public Texture2D[,] SquareCelledMatrix;

[TableMatrix(SquareCells = true)]
public Mesh[,] PrefabMatrix;

[OnInspectorInit]
private void CreateData()
{
    SquareCelledMatrix = new Texture2D[8, 4]
    {
        { ExampleHelper.GetTexture(), null, null, null },
        { null, ExampleHelper.GetTexture(), null, null },
        { null, null, ExampleHelper.GetTexture(), null },
        { null, null, null, ExampleHelper.GetTexture() },
        { ExampleHelper.GetTexture(), null, null, null },
        { null, ExampleHelper.GetTexture(), null, null },
        { null, null, ExampleHelper.GetTexture(), null },
        { null, null, null, ExampleHelper.GetTexture() },
    };

    PrefabMatrix = new Mesh[8, 4]
    {
        { ExampleHelper.GetMesh(), null, null, null },
        { null, ExampleHelper.GetMesh(), null, null },
        { null, null, ExampleHelper.GetMesh(), null },
        { null, null, null, ExampleHelper.GetMesh() },
        { null, null, null, ExampleHelper.GetMesh() },
        { null, null, ExampleHelper.GetMesh(), null },
        { null, ExampleHelper.GetMesh(), null, null },
        { ExampleHelper.GetMesh(), null, null, null },
    };
}

Toggle Attribute

トグルは、任意のフィールドやプロパティで使用され、インスペクタでプロパティを有効または無効にすることができます。オフにしたりオンにしたりできるプロパティを作成する場合に使用します。

[Toggle("Enabled")]
public MyToggleable Toggler = new MyToggleable();

public ToggleableClass Toggleable = new ToggleableClass();

[Serializable]
public class MyToggleable
{
    public bool Enabled;
    public int MyValue;
}

// You can also use the Toggle attribute directly on a class definition.
[Serializable, Toggle("Enabled")]
public class ToggleableClass
{
    public bool Enabled;
    public string Text;
}

Toggle Left Attribute

チェックボックスをラベルの後ではなく、前に描画します。

[InfoBox("Draws the toggle button before the label for a bool property.")]
[ToggleLeft]
public bool LeftToggled;

[EnableIf("LeftToggled")]
public int A;

[EnableIf("LeftToggled")]
public bool B;

[EnableIf("LeftToggled")]
public bool C;

Assets Only Attribute

AssetsOnlyは、オブジェクトのプロパティに使用され、プロパティの対象をシーンオブジェクトではなく、プロジェクトのアセットに制限します。オブジェクトがプロジェクトのものであり、シーンのものではないことを確認したい場合に使用します。

[Title("Assets only")]
[AssetsOnly]
public List<GameObject> OnlyPrefabs;

[AssetsOnly]
public GameObject SomePrefab;

[AssetsOnly]
public Material MaterialAsset;

[AssetsOnly]
public MeshRenderer SomeMeshRendererOnPrefab;

[Title("Scene Objects only")]
[SceneObjectsOnly]
public List<GameObject> OnlySceneObjects;

[SceneObjectsOnly]
public GameObject SomeSceneObject;

[SceneObjectsOnly]
public MeshRenderer SomeMeshRenderer;

Essentials

Custom Value Drawer Attribute

新しい属性と新しいドロワーを一度だけ作るのではなく、この属性を使って、カスタムプロパティドロワーとして機能するメソッドを作ることができます。これらのドロワーは、すぐに元に戻す/やり直す、複数選択をサポートします。

public float From = 2, To = 7;

[CustomValueDrawer("MyCustomDrawerStatic")]
public float CustomDrawerStatic;

[CustomValueDrawer("MyCustomDrawerInstance")]
public float CustomDrawerInstance;

[CustomValueDrawer("MyCustomDrawerAppendRange")]
public float AppendRange;

[CustomValueDrawer("MyCustomDrawerArrayNoLabel")]
public float[] CustomDrawerArrayNoLabel = new float[] { 3f, 5f, 6f };

private static float MyCustomDrawerStatic(float value, GUIContent label)
{
    return EditorGUILayout.Slider(label, value, 0f, 10f);
}

private float MyCustomDrawerInstance(float value, GUIContent label)
{
    return EditorGUILayout.Slider(label, value, this.From, this.To);
}

private float MyCustomDrawerAppendRange(float value, GUIContent label, Func<GUIContent, bool> callNextDrawer)
{
    SirenixEditorGUI.BeginBox();
    callNextDrawer(label);
    var result = EditorGUILayout.Slider(value, this.From, this.To);
    SirenixEditorGUI.EndBox();
    return result;
}

private float MyCustomDrawerArrayNoLabel(float value)
{
    return EditorGUILayout.Slider(value, this.From, this.To);
}

Delayed Property Attribute

インスペクタで編集中のプロパティへの変更適用を遅らせます。Unityに内蔵されているDelayed属性と似ていますが、この属性はプロパティにも適用できます。

// DelayedとDelayedPropertyの属性は実質的に同じです
[Delayed]...
[OnValueChanged("OnValueChanged")] 
public int DelayedField;

// しかし、DelayedPropertyは、その名の通り、プロパティにも適用することができます。
[ShowInInspector, DelayedProperty] 
[OnValueChanged("OnValueChanged")] 
public string DelayedProperty { get; set; }.

private void OnValueChanged()
{
    Debug.Log("Value changed!");
}

Detailed Info Box Attribute

DetailedInfoBox は、任意のプロパティに使用され、詳細を表示するために展開できるメッセージ ボックスを表示します。ユーザーにメッセージを伝えたり、詳細を見るためのオプションを与えたりする場合に使用します。

[DetailedInfoBox("Click the DetailedInfoBox...",
    "... to reveal more information!\n" +
    "This allows you to reduce unnecessary clutter in your editors, and still have all the relavant information available when required.")]
public int Field;

Enable GUIAttribute

通常は無効になっているプロパティのGUIを有効にします。

[ShowInInspector]
public int GUIDisabledProperty { get { return 10; } }

[ShowInInspector, EnableGUI]
public int GUIEnabledProperty { get { return 10; } }

GUIColor Attribute

GUIColorは、任意のプロパティで使用され、プロパティの描画に使用されるGUIカラーを変更します。

[GUIColor(0.3f, 0.8f, 0.8f, 1f)]
public int ColoredInt1;

[GUIColor(0.3f, 0.8f, 0.8f, 1f)]
public int ColoredInt2;

[ButtonGroup]
[GUIColor(0, 1, 0)]
private void Apply()
{
}

[ButtonGroup]
[GUIColor(1, 0.6f, 0.4f)]
private void Cancel()
{
}

[InfoBox("You can also reference a color member to dynamically change the color of a property.")]
[GUIColor("GetButtonColor")]
[Button("I Am Fabulous", ButtonSizes.Gigantic)]
private static void IAmFabulous()
{
}

[Button(ButtonSizes.Large)]
[GUIColor("@Color.Lerp(Color.red, Color.green, Mathf.Abs(Mathf.Sin((float)EditorApplication.timeSinceStartup)))")]
private static void Expressive()
{
}

private static Color GetButtonColor()
{
    Sirenix.Utilities.Editor.GUIHelper.RequestRepaint();
    return Color.HSVToRGB(Mathf.Cos((float)UnityEditor.EditorApplication.timeSinceStartup + 1f) * 0.225f + 0.325f, 1, 1);
}

Hide Label Attribute

HideLabel は、任意のプロパティに使用され、インスペクタでラベルを隠します。インスペクタでプロパティのラベルを隠すには、これを使います。

[Title("Wide Colors")]
[HideLabel]
[ColorPalette("Fall")]
public Color WideColor1;

[HideLabel]
[ColorPalette("Fall")]
public Color WideColor2;

[Title("Wide Vector")]
[HideLabel]
public Vector3 WideVector1;

[HideLabel]
public Vector4 WideVector2;

[Title("Wide String")]
[HideLabel]
public string WideString;

[Title("Wide Multiline Text Field")]
[HideLabel]
[MultiLineProperty]
public string WideMultilineTextField = "";

Property Order Attribute

PropertyOrder は、任意のプロパティで使用され、プロパティの順序付けを可能にします。これを使用して、プロパティの表示順を定義します。

[PropertyOrder(1)]
public int Second;

[InfoBox("PropertyOrder is used to change the order of properties in the inspector.")]
[PropertyOrder(-1)]
public int First;

Property Space Attribute

PropertySpaceアトリビュートは、Unityの既存のSpaceアトリビュートと同じ機能を持っていますが、フィールドだけでなく、どこにでも適用することができます。

// PropertySpaceとSpaceの属性はほぼ同じ
[Space] 
public int Space;

// しかし、PropertySpaceはその名の通り、プロパティにも適用することができます。
[ShowInInspector, PropertySpace] 
public string Property { get; set; }

// PropertySpace属性の前と後の両方の間隔を制御することもできます。
[PropertySpace(SpaceBefore = 0, SpaceAfter = 60), PropertyOrder(2)] 
public int BeforeAndAfter;

Read Only Attribute

ReadOnly は、任意のプロパティに使用され、インスペクタでプロパティが変更されないようにします。これは、インスペクタでプロパティの値を確認したいが、変更されたくない場合に使用します。

[ReadOnly]
public string MyString = "This is displayed as text";

[ReadOnly]
public int MyInt = 9001;

[ReadOnly]
public int[] MyIntList = new int[] { 1, 2, 3, 4, 5, 6, 7, };

Required Attribute

Required は、任意のオブジェクトプロパティに使用され、そのプロパティがない場合はインスペクタにメッセージが表示されます。オブジェクトに必要なフィールドを明確に示すために使用します。

[Required]
public GameObject MyGameObject;

[Required("Custom error message.")]
public Rigidbody MyRigidbody;

[InfoBox("Use $ to indicate a member string as message.")]
[Required("$DynamicMessage")]
public GameObject GameObject;

public string DynamicMessage = "Dynamic error message";

Searchable Attribute

適用されたフィールドまたはタイプの子を検索することができる検索フィルタを追加します。ただし、辞書の「上」にある検索フィールドが再帰的に検索する場合は、辞書のプロパティを検索します。

[Searchable]
public List<Perk> Perks = new List<Perk>()
{
    new Perk()
    {
        Name = "Old Sage",
        Effects = new List<Effect>()
        {
            new Effect() { Skill = Skill.Wisdom, Value = 2, },
            new Effect() { Skill = Skill.Intelligence, Value = 1, },
            new Effect() { Skill = Skill.Strength, Value = -2 },
        },
    },
    new Perk()
    {
        Name = "Hardened Criminal",
        Effects = new List<Effect>()
        {
            new Effect() { Skill = Skill.Dexterity, Value = 2, },
            new Effect() { Skill = Skill.Strength, Value = 1, },
            new Effect() { Skill = Skill.Charisma, Value = -2 },
        },
    },
    new Perk()
    {
        Name = "Born Leader",
        Effects = new List<Effect>()
        {
            new Effect() { Skill = Skill.Charisma, Value = 2, },
            new Effect() { Skill = Skill.Intelligence, Value = -3 },
        },
    },
    new Perk()
    {
        Name = "Village Idiot",
        Effects = new List<Effect>()
        {
            new Effect() { Skill = Skill.Charisma, Value = 4, },
            new Effect() { Skill = Skill.Constitution, Value = 2, },
            new Effect() { Skill = Skill.Intelligence, Value = -3 },
            new Effect() { Skill = Skill.Wisdom, Value = -3 },
        },
    },
};

[Serializable]
public class Perk
{
    public string Name;

    [TableList]
    public List<Effect> Effects;
}

[Serializable]
public class Effect
{
    public Skill Skill;
    public float Value;
}

public enum Skill
{
    Strength,
    Dexterity,
    Constitution,
    Intelligence,
    Wisdom,
    Charisma,
}

Show In Inspector Attribute

ShowInInspector は、任意のメンバーに使用され、その値をインスペクタに表示します。つまり、ShowInInspector属性だけでは、変更した内容が保存されないということです。大まかな目安としては つまり、ShowInInspector属性だけでは、変更が保存されません。クラス内で何がシリアル化されていて、何がシリアル化されていないかを把握するには、シリアル化デバッガーを使用してください。

[ShowInInspector]
private int myPrivateInt;

[ShowInInspector]
public int MyPropertyInt { get; set; }

[ShowInInspector]
public int ReadOnlyProperty
{
    get { return this.myPrivateInt; }
}

[ShowInInspector]
public static bool StaticProperty { get; set; }

Title Attribute

タイトルは、プロパティの上に太いヘッダーを作るために使われます。

[Title("Titles and Headers")]
public string MyTitle = "My Dynamic Title";
public string MySubtitle = "My Dynamic Subtitle";

[Title("Static title")]
public int C;
public int D;

[Title("Static title", "Static subtitle")]
public int E;
public int F;

[Title("$MyTitle", "$MySubtitle")]
public int G;
public int H;

[Title("Non bold title", "$MySubtitle", bold: false)]
public int I;
public int J;

[Title("Non bold title", "With no line seperator", horizontalLine: false, bold: false)]
public int K;
public int L;

[Title("$MyTitle", "$MySubtitle", TitleAlignments.Right)]
public int M;
public int N;

[Title("$MyTitle", "$MySubtitle", TitleAlignments.Centered)]
public int O;
public int P;

[Title("$Combined", titleAlignment: TitleAlignments.Centered)]
public int Q;
public int R;

[ShowInInspector]
[Title("Title on a Property")]
public int S { get; set; }

[Title("Title on a Method")]
[Button]
public void DoNothing()
{ }

[Title("@DateTime.Now.ToString(\"dd:MM:yyyy\")", "@DateTime.Now.ToString(\"HH:mm:ss\")")]
public int Expresion;

public string Combined { get { return this.MyTitle + " - " + this.MySubtitle; } }

Type Filter Attribute

[TypeFilter("GetFilteredTypeList")]
public BaseClass A, B;

[TypeFilter("GetFilteredTypeList")]
public BaseClass[] Array = new BaseClass[3];

public IEnumerable<Type> GetFilteredTypeList()
{
    var q = typeof(BaseClass).Assembly.GetTypes()
        .Where(x => !x.IsAbstract)                                          // Excludes BaseClass
        .Where(x => !x.IsGenericTypeDefinition)                             // Excludes C1<>
        .Where(x => typeof(BaseClass).IsAssignableFrom(x));                 // Excludes classes not inheriting from BaseClass

    // Adds various C1<T> type variants.
    q = q.AppendWith(typeof(C1<>).MakeGenericType(typeof(GameObject)));
    q = q.AppendWith(typeof(C1<>).MakeGenericType(typeof(AnimationCurve)));
    q = q.AppendWith(typeof(C1<>).MakeGenericType(typeof(List<float>)));

    return q;
}

public abstract class BaseClass
{
    public int BaseField;
}

public class A1 : BaseClass { public int _A1; }
public class A2 : A1 { public int _A2; }
public class A3 : A2 { public int _A3; }
public class B1 : BaseClass { public int _B1; }
public class B2 : B1 { public int _B2; }
public class B3 : B2 { public int _B3; }
public class C1<T> : BaseClass { public T C; }

Type Info Box Attribute

TypeInfoBox属性は、インスペクタの型の最上部に情報ボックスを追加します。これを使うと、PropertyOrderやOnInspectorGUI属性を使わなくても、インスペクタのクラスの一番上に情報ボックスを追加することができます。

public MyType MyObject = new MyType();

[InfoBox("Click the pen icon to open a new inspector for the Scripty object.")]
[InlineEditor]
public MyScriptyScriptableObject Scripty;

[Serializable]
[TypeInfoBox("The TypeInfoBox attribute can be put on type definitions and will result in an InfoBox being drawn at the top of a property.")]
public class MyType
{
    public int Value;
}

//[TypeInfoBox("The TypeInfoBox attribute can also be used to display a text at the top of, for example, MonoBehaviours or ScriptableObjects.")]
//public class MyScriptyScriptableObject : ScriptableObject
//{
//    public string MyText = ExampleHelper.GetString();
//    [TextArea(10, 15)]
//    public string Box;
//}


[OnInspectorInit]
private void CreateData()
{
    Scripty = ExampleHelper.GetScriptableObject<MyScriptyScriptableObject>("Scripty");
}

[OnInspectorDispose]
private void CleanupData()
{
    if (Scripty != null) UnityEngine.Object.DestroyImmediate(Scripty);
}

Validate Input Attribute

ValidateInputは任意のプロパティに使用され、インスペクタからの入力を検証することができます。正しい値を強制するために使用します。

[HideLabel]
[Title("Default message", "You can just provide a default message that is always used")]
[ValidateInput("MustBeNull", "This field should be null.")]
public MyScriptyScriptableObject DefaultMessage;

[Space(12), HideLabel]
[Title("Dynamic message", "Or the validation method can dynamically provide a custom message")]
[ValidateInput("HasMeshRendererDynamicMessage", "Prefab must have a MeshRenderer component")]
public GameObject DynamicMessage;

[Space(12), HideLabel]
[Title("Dynamic message type", "The validation method can also control the type of the message")]
[ValidateInput("HasMeshRendererDynamicMessageAndType", "Prefab must have a MeshRenderer component")]
public GameObject DynamicMessageAndType;

[Space(8), HideLabel]
[InfoBox("Change GameObject value to update message type", InfoMessageType.None)]
public InfoMessageType MessageType;

[Space(12), HideLabel]
[Title("Dynamic default message", "Use $ to indicate a member string as default message")]
[ValidateInput("AlwaysFalse", "$Message", InfoMessageType.Warning)]
public string Message = "Dynamic ValidateInput message";

private bool AlwaysFalse(string value)
{
    return false;
}

private bool MustBeNull(MyScriptyScriptableObject scripty)
{
    return scripty == null;
}

private bool HasMeshRendererDefaultMessage(GameObject gameObject)
{
    if (gameObject == null) return true;

    return gameObject.GetComponentInChildren<MeshRenderer>() != null;
}

private bool HasMeshRendererDynamicMessage(GameObject gameObject, ref string errorMessage)
{
    if (gameObject == null) return true;

    if (gameObject.GetComponentInChildren<MeshRenderer>() == null)
    {
        // If errorMessage is left as null, the default error message from the attribute will be used
        errorMessage = "\"" + gameObject.name + "\" must have a MeshRenderer component";

        return false;
    }

    return true;
}

private bool HasMeshRendererDynamicMessageAndType(GameObject gameObject, ref string errorMessage, ref InfoMessageType? messageType)
{
    if (gameObject == null) return true;

    if (gameObject.GetComponentInChildren<MeshRenderer>() == null)
    {
        // If errorMessage is left as null, the default error message from the attribute will be used
        errorMessage = "\"" + gameObject.name + "\" should have a MeshRenderer component";

        // If messageType is left as null, the default message type from the attribute will be used
        messageType = this.MessageType;

        return false;
    }

    return true;
}

Value Dropdown Attribute

ValueDropdownは、任意のプロパティで使用され、設定可能なオプションを持つドロップダウンを作成します。ユーザーが特定のオプションを選択できるようにするために使用します。

[ValueDropdown("TextureSizes")]
public int SomeSize1;

[ValueDropdown("FriendlyTextureSizes")]
public int SomeSize2;

[ValueDropdown("FriendlyTextureSizes", AppendNextDrawer = true, DisableGUIInAppendedDrawer = true)]
public int SomeSize3;

[ValueDropdown("GetListOfMonoBehaviours", AppendNextDrawer = true)]
public MonoBehaviour SomeMonoBehaviour;

[ValueDropdown("KeyCodes")]
public KeyCode FilteredEnum;

[ValueDropdown("TreeViewOfInts", ExpandAllMenuItems = true)]
public List<int> IntTreview = new List<int>() { 1, 2, 7 };

[ValueDropdown("GetAllSceneObjects", IsUniqueList = true)]
public List<GameObject> UniqueGameobjectList;

[ValueDropdown("GetAllSceneObjects", IsUniqueList = true, DropdownTitle = "Select Scene Object", DrawDropdownForListElements = false, ExcludeExistingValuesInList = true)]
public List<GameObject> UniqueGameobjectListMode2;

private IEnumerable TreeViewOfInts = new ValueDropdownList<int>()
{
    { "Node 1/Node 1.1", 1 },
    { "Node 1/Node 1.2", 2 },
    { "Node 2/Node 2.1", 3 },
    { "Node 3/Node 3.1", 4 },
    { "Node 3/Node 3.2", 5 },
    { "Node 1/Node 3.1/Node 3.1.1", 6 },
    { "Node 1/Node 3.1/Node 3.1.2", 7 },
};

private IEnumerable<MonoBehaviour> GetListOfMonoBehaviours()
{
    return GameObject.FindObjectsOfType<MonoBehaviour>();
}

private static IEnumerable<KeyCode> KeyCodes = Enumerable.Range((int)KeyCode.Alpha0, 10).Cast<KeyCode>();

private static IEnumerable GetAllSceneObjects()
{
    Func<Transform, string> getPath = null;
    getPath = x => (x ? getPath(x.parent) + "/" + x.gameObject.name : "");
    return GameObject.FindObjectsOfType<GameObject>().Select(x => new ValueDropdownItem(getPath(x.transform), x));
}

private static IEnumerable GetAllScriptableObjects()
{
    return UnityEditor.AssetDatabase.FindAssets("t:ScriptableObject")
        .Select(x => UnityEditor.AssetDatabase.GUIDToAssetPath(x))
        .Select(x => new ValueDropdownItem(x, UnityEditor.AssetDatabase.LoadAssetAtPath<ScriptableObject>(x)));
}

private static IEnumerable GetAllSirenixAssets()
{
    var root = "Assets/Plugins/Sirenix/";

    return UnityEditor.AssetDatabase.GetAllAssetPaths()
        .Where(x => x.StartsWith(root))
        .Select(x => x.Substring(root.Length))
        .Select(x => new ValueDropdownItem(x, UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(root + x)));
}

private static IEnumerable FriendlyTextureSizes = new ValueDropdownList<int>()
{
    { "Small", 256 },
    { "Medium", 512 },
    { "Large", 1024 },
};

private static int[] TextureSizes = new int[] { 256, 512, 1024 };

アイコンはまてるさんからお借りしました。

+3