文章目录
- 1 说明
- 2 其他特性
- 2.1 CustomContextMenu
- 2.2 DisableContextMenu
- 2.3 DrawWithUnity
- 2.4 HideDuplicateReferenceBox
- 2.5 Indent
- 2.6 InfoBox
- 2.7 InlineProperty
- 2.8 LabelText
- 2.9 LabelWidth
- 2.10 OnCollectionChanged
- 2.11 OnInspectorDispose
- 2.12 OnInspectorGUI
- 2.13 OnInspectorInit
- 2.14 OnStateUpdate
- 2.15 OnValueChanged
- 2.16 TypeSelectorSettings
- 2.17 TypeRegistryItem
- 2.18 PropertyTooltip
- 2.19 SuffixLabel
1 说明
本文介绍 Odin Inspector 插件中其他特性的使用方法。
2 其他特性
2.1 CustomContextMenu

// CustomContextMenuExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class CustomContextMenuExamplesComponent : MonoBehaviour
{
[InfoBox("A custom context menu is added on this property. Right click the property to view the custom context menu.")]
[CustomContextMenu("Say Hello/Twice", "SayHello")]
public int MyProperty;
private void SayHello() {
Debug.Log("Hello Twice");
}
}
2.2 DisableContextMenu

// DisableContextMenuExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class DisableContextMenuExamplesComponent : MonoBehaviour
{
[InfoBox("DisableContextMenu disables all right-click context menus provided by Odin. It does not disable Unity's context menu.", InfoMessageType.Warning)]
[DisableContextMenu]
public int[] NoRightClickList = new int[] { 2, 3, 5 };
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
public int[] NoRightClickListOnListElements = new int[] { 7, 11 };
[DisableContextMenu(disableForMember: true, disableCollectionElements: true)]
public int[] DisableRightClickCompletely = new int[] { 13, 17 };
[DisableContextMenu]
public int NoRightClickField = 19;
}
2.3 DrawWithUnity

// DrawWithUnityExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class DrawWithUnityExamplesComponent : MonoBehaviour
{
[InfoBox("If you ever experience trouble with one of Odin's attributes, there is a good chance that DrawWithUnity will come in handy; it will make Odin draw the value as Unity normally would.")]
public GameObject ObjectDrawnWithOdin;
[DrawWithUnity]
public GameObject ObjectDrawnWithUnity;
}
2.4 HideDuplicateReferenceBox

// HideDuplicateReferenceBoxExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
#if UNITY_EDITOR // Editor namespaces can only be used in the editor.
using Sirenix.Utilities.Editor;
#endif
public class HideDuplicateReferenceBoxExamplesComponent : SerializedMonoBehaviour
{
[PropertyOrder(1)]
public ReferenceTypeClass firstObject;
[PropertyOrder(3)]
public ReferenceTypeClass withReferenceBox;
[PropertyOrder(5)]
[HideDuplicateReferenceBox]
public ReferenceTypeClass withoutReferenceBox;
[OnInspectorInit]
public void CreateData() {
this.firstObject = new ReferenceTypeClass();
this.withReferenceBox = this.firstObject;
this.withoutReferenceBox = this.firstObject;
this.firstObject.recursiveReference = this.firstObject;
}
public class ReferenceTypeClass
{
[HideDuplicateReferenceBox]
public ReferenceTypeClass recursiveReference;
#if UNITY_EDITOR // Editor-related code must be excluded from builds
[OnInspectorGUI, PropertyOrder(-1)]
private void MessageBox() {
SirenixEditorGUI.WarningMessageBox("Recursively drawn references will always show the reference box regardless, to prevent infinite depth draw loops.");
}
#endif
}
#if UNITY_EDITOR // Editor-related code must be excluded from builds
[OnInspectorGUI, PropertyOrder(0)]
private void MessageBox1() {
SirenixEditorGUI.Title("The first reference will always be drawn normally", null, TextAlignment.Left, true);
}
[OnInspectorGUI, PropertyOrder(2)]
private void MessageBox2() {
GUILayout.Space(20);
SirenixEditorGUI.Title("All subsequent references will be wrapped in a reference box", null, TextAlignment.Left, true);
}
[OnInspectorGUI, PropertyOrder(4)]
private void MessageBox3() {
GUILayout.Space(20);
SirenixEditorGUI.Title("With the [HideDuplicateReferenceBox] attribute, this box is hidden", null, TextAlignment.Left, true);
}
#endif
}
2.5 Indent

// IndentExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class IndentExamplesComponent : MonoBehaviour
{
[Title("Nicely organize your properties.")]
[Indent]
public int A;
[Indent(2)]
public int B;
[Indent(3)]
public int C;
[Indent(4)]
public int D;
[Title("Using the Indent attribute")]
[Indent]
public int E;
[Indent(0)]
public int F;
[Indent(-1)]
public int G;
}
2.6 InfoBox

// InfoBoxExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class InfoBoxExamplesComponent : MonoBehaviour
{
[Title("InfoBox message types")]
[InfoBox("Default info box.")]
public int A;
[InfoBox("Warning info box.", InfoMessageType.Warning)]
public int B;
[InfoBox("Error info box.", InfoMessageType.Error)]
public int C;
[InfoBox("Info box without an icon.", InfoMessageType.None)]
public int D;
[Title("Conditional info boxes")]
public bool ToggleInfoBoxes;
[InfoBox("This info box is only shown while in editor mode.", InfoMessageType.Error, "IsInEditMode")]
public float G;
[InfoBox("This info box is hideable by a static field.", "ToggleInfoBoxes")]
public float E;
[InfoBox("This info box is hideable by a static field.", "ToggleInfoBoxes")]
public float F;
[Title("Info box member reference and attribute expressions")]
[InfoBox("$InfoBoxMessage")]
[InfoBox("@\"Time: \" + DateTime.Now.ToString(\"HH:mm:ss\")")]
public string InfoBoxMessage = "My dynamic info box message";
private static bool IsInEditMode() {
return !Application.isPlaying;
}
}
2.7 InlineProperty

// InlinePropertyExamplesComponent.cs
using Sirenix.OdinInspector;
using System;
using UnityEngine;
public class InlinePropertyExamplesComponent : MonoBehaviour
{
public Vector3 Vector3;
public Vector3Int MyVector3Int;
[InlineProperty(LabelWidth = 13)]
public Vector2Int MyVector2Int;
[Serializable]
[InlineProperty(LabelWidth = 13)]
public struct Vector3Int
{
[HorizontalGroup]
public int X;
[HorizontalGroup]
public int Y;
[HorizontalGroup]
public int Z;
}
[Serializable]
public struct Vector2Int
{
[HorizontalGroup]
public int X;
[HorizontalGroup]
public int Y;
}
}
2.8 LabelText

// LabelTextExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class LabelTextExamplesComponent : MonoBehaviour
{
[LabelText("1")]
public int MyInt1 = 1;
[LabelText("2")]
public int MyInt2 = 12;
[LabelText("3")]
public int MyInt3 = 123;
[InfoBox("Use $ to refer to a member string.")]
[LabelText("$MyInt3")]
public string LabelText = "The label is taken from the number 3 above";
[InfoBox("Use @ to execute an expression.")]
[LabelText("@DateTime.Now.ToString(\"HH:mm:ss\")")]
public string DateTimeLabel;
[LabelText("Test", SdfIconType.HeartFill)]
public int LabelIcon1 = 123;
[LabelText("", SdfIconType.HeartFill)]
public int LabelIcon2 = 123;
}
2.9 LabelWidth

// LabelWidthExampleComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class LabelWidthExampleComponent : MonoBehaviour
{
public int DefaultWidth;
[LabelWidth(50)]
public int Thin;
[LabelWidth(250)]
public int Wide;
}
2.10 OnCollectionChanged

// OnCollectionChangedExamplesComponent.cs
using Sirenix.OdinInspector;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR // Editor namespaces can only be used in the editor.
using Sirenix.OdinInspector.Editor;
#endif
public class OnCollectionChangedExamplesComponent : SerializedMonoBehaviour
{
[InfoBox("Change the collection to get callbacks detailing the changes that are being made.")]
[OnCollectionChanged("Before", "After")]
public List<string> list = new List<string>() { "str1", "str2", "str3" };
[OnCollectionChanged("Before", "After")]
public HashSet<string> hashset = new HashSet<string>() { "str1", "str2", "str3" };
[OnCollectionChanged("Before", "After")]
public Dictionary<string, string> dictionary = new Dictionary<string, string>() { { "key1", "str1" }, { "key2", "str2" }, { "key3", "str3" } };
#if UNITY_EDITOR // Editor-related code must be excluded from builds
public void Before(CollectionChangeInfo info, object value) {
Debug.Log("Received callback BEFORE CHANGE with the following info: " + info + ", and the following collection instance: " + value);
}
public void After(CollectionChangeInfo info, object value) {
Debug.Log("Received callback AFTER CHANGE with the following info: " + info + ", and the following collection instance: " + value);
}
#endif
}
2.11 OnInspectorDispose

// OnInspectorDisposeExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class OnInspectorDisposeExamplesComponent : MonoBehaviour
{
[OnInspectorDispose("@UnityEngine.Debug.Log(\"Dispose event invoked!\")")]
[ShowInInspector, InfoBox("When you change the type of this field, or set it to null, the former property setup is disposed. The property setup will also be disposed when you deselect this example."), DisplayAsString]
public BaseClass PolymorphicField;
public abstract class BaseClass { public override string ToString() { return this.GetType().Name; } }
public class A : BaseClass { }
public class B : BaseClass { }
public class C : BaseClass { }
}
2.12 OnInspectorGUI

// OnInspectorGUIExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class OnInspectorGUIExamplesComponent : MonoBehaviour
{
[OnInspectorInit("@Texture = Sirenix.Utilities.Editor.EditorIcons.OdinInspectorLogo")]
[OnInspectorGUI("DrawPreview", append: true)]
public Texture2D Texture;
private void DrawPreview() {
if (this.Texture == null) return;
GUILayout.BeginVertical(GUI.skin.box);
GUILayout.Label(this.Texture);
GUILayout.EndVertical();
}
#if UNITY_EDITOR // Editor-related code must be excluded from builds
[OnInspectorGUI]
private void OnInspectorGUI() {
UnityEditor.EditorGUILayout.HelpBox("OnInspectorGUI can also be used on both methods and properties", UnityEditor.MessageType.Info);
}
#endif
}
2.13 OnInspectorInit

// OnInspectorInitExamplesComponent.cs
using Sirenix.OdinInspector;
using System;
using UnityEngine;
#if UNITY_EDITOR // Editor namespaces can only be used in the editor.
using Sirenix.Utilities.Editor;
#endif
public class OnInspectorInitExamplesComponent : MonoBehaviour
{
// Display current time for reference.
[ShowInInspector, DisplayAsString, PropertyOrder(-1)]
public string CurrentTime {
get {
#if UNITY_EDITOR // Editor-related code must be excluded from builds
GUIHelper.RequestRepaint();
#endif
return DateTime.Now.ToString();
}
}
// OnInspectorInit executes the first time this string is about to be drawn in the inspector.
// It will execute again when the example is reselected.
[OnInspectorInit("@TimeWhenExampleWasOpened = DateTime.Now.ToString()")]
public string TimeWhenExampleWasOpened;
// OnInspectorInit will not execute before the property is actually "resolved" in the inspector.
// Remember, Odin's property system is lazily evaluated, and so a property does not actually exist
// and is not initialized before something is actually asking for it.
//
// Therefore, this OnInspectorInit attribute won't execute until the foldout is expanded.
[FoldoutGroup("Delayed Initialization", Expanded = false, HideWhenChildrenAreInvisible = false)]
[OnInspectorInit("@TimeFoldoutWasOpened = DateTime.Now.ToString()")]
public string TimeFoldoutWasOpened;
}
2.14 OnStateUpdate

// AnotherPropertysStateExampleComponent.cs
using Sirenix.OdinInspector;
using System.Collections.Generic;
using UnityEngine;
public class AnotherPropertysStateExampleComponent : MonoBehaviour
{
public List<string> list;
[OnStateUpdate("@#(list).State.Expanded = $value")]
public bool ExpandList;
}
2.15 OnValueChanged

// OnValueChangedExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class OnValueChangedExamplesComponent : MonoBehaviour
{
[OnValueChanged("CreateMaterial")]
public Shader Shader;
[ReadOnly, InlineEditor(InlineEditorModes.LargePreview)]
public Material Material;
private void CreateMaterial() {
if (this.Material != null) {
Material.DestroyImmediate(this.Material);
}
if (this.Shader != null) {
this.Material = new Material(this.Shader);
}
}
}
2.16 TypeSelectorSettings

// TypeSelectorSettingsExampleComponent.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEngine;
public class TypeSelectorSettingsExampleComponent : MonoBehaviour
{
[ShowInInspector]
public Type Default;
[Title("Show Categories"), ShowInInspector, LabelText("On")]
[TypeSelectorSettings(ShowCategories = true)]
public Type ShowCategories_On;
[ShowInInspector, LabelText("Off")]
[TypeSelectorSettings(ShowCategories = false)]
public Type ShowCategories_Off;
[Title("Prefer Namespaces"), ShowInInspector, LabelText("On")]
[TypeSelectorSettings(PreferNamespaces = true, ShowCategories = true)]
public Type PreferNamespaces_On;
[ShowInInspector, LabelText("Off")]
[TypeSelectorSettings(PreferNamespaces = false, ShowCategories = true)]
public Type PreferNamespaces_Off;
[Title("Show None Item"), ShowInInspector, LabelText("On")]
[TypeSelectorSettings(ShowNoneItem = true)]
public Type ShowNoneItem_On;
[ShowInInspector, LabelText("Off")]
[TypeSelectorSettings(ShowNoneItem = false)]
public Type ShowNoneItem_Off;
[Title("Custom Type Filter"), ShowInInspector]
[TypeSelectorSettings(FilterTypesFunction = nameof(TypeFilter), ShowCategories = false)]
public Type CustomTypeFilterExample;
private bool TypeFilter(Type type) {
return type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
}
}
2.17 TypeRegistryItem

// TypeRegistryItemSettingsExampleComponent.cs
using System;
using Sirenix.OdinInspector;
using UnityEngine;
public class TypeRegistryItemSettingsExampleComponent : MonoBehaviour
{
private const string CATEGORY_PATH = "Sirenix.TypeSelector.Demo";
private const string BASE_ITEM_NAME = "Painting Tools";
private const string PATH = CATEGORY_PATH + "/" + BASE_ITEM_NAME;
[TypeRegistryItem(Name = BASE_ITEM_NAME, Icon = SdfIconType.Tools, CategoryPath = CATEGORY_PATH, Priority = Int32.MinValue)]
public abstract class Base
{ }
[TypeRegistryItem(darkIconColorR: 0.8f, darkIconColorG: 0.3f,
lightIconColorR: 0.3f, lightIconColorG: 0.1f,
Name = "Brush", CategoryPath = PATH, Icon = SdfIconType.BrushFill, Priority = Int32.MinValue)]
public class InheritorA : Base
{
public Color Color = Color.red;
public float PaintRemaining = 0.4f;
}
[TypeRegistryItem(darkIconColorG: 0.8f, darkIconColorB: 0.3f,
lightIconColorG: 0.3f, lightIconColorB: 0.1f,
Name = "Paint Bucket", CategoryPath = PATH, Icon = SdfIconType.PaintBucket, Priority = Int32.MinValue)]
public class InheritorB : Base
{
public Color Color = Color.green;
public float PaintRemaining = 0.8f;
}
[TypeRegistryItem(darkIconColorB: 0.8f, darkIconColorG: 0.3f,
lightIconColorB: 0.3f, lightIconColorG: 0.1f,
Name = "Palette", CategoryPath = PATH, Icon = SdfIconType.PaletteFill, Priority = Int32.MinValue)]
public class InheritorC : Base
{
public ColorPaletteItem[] Colors = {
new ColorPaletteItem(Color.blue, 0.8f),
new ColorPaletteItem(Color.red, 0.5f),
new ColorPaletteItem(Color.green, 1.0f),
new ColorPaletteItem(Color.white, 0.6f),
};
}
[ShowInInspector]
[PolymorphicDrawerSettings(ShowBaseType = false)]
[InlineProperty]
public Base PaintingItem;
public struct ColorPaletteItem
{
public Color Color;
public float Remaining;
public ColorPaletteItem(Color color, float remaining) {
this.Color = color;
this.Remaining = remaining;
}
}
}
2.18 PropertyTooltip

// PropertyTooltipExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class PropertyTooltipExamplesComponent : MonoBehaviour
{
[PropertyTooltip("This is tooltip on an int property.")]
public int MyInt;
[InfoBox("Use $ to refer to a member string.")]
[PropertyTooltip("$Tooltip")]
public string Tooltip = "Dynamic tooltip.";
[Button, PropertyTooltip("Button Tooltip")]
private void ButtonWithTooltip() {
// ...
}
}
2.19 SuffixLabel

// SuffixLabelExamplesComponent.cs
using Sirenix.OdinInspector;
using UnityEngine;
public class SuffixLabelExamplesComponent : MonoBehaviour
{
[SuffixLabel("Prefab")]
public GameObject GameObject;
[Space(15)]
[InfoBox("Using the Overlay property, the suffix label will be drawn on top of the property instead of behind it.\n" +
"Use this for a neat inline look.")]
[SuffixLabel("ms", Overlay = true)]
public float Speed;
[SuffixLabel("radians", Overlay = true)]
public float Angle;
[Space(15)]
[InfoBox("The Suffix attribute also supports referencing a member string field, property, or method by using $.")]
[SuffixLabel("$Suffix", Overlay = true)]
public string Suffix = "Dynamic suffix label";
[InfoBox("The Suffix attribute also supports expressions by using @.")]
[SuffixLabel("@DateTime.Now.ToString(\"HH:mm:ss\")", true)]
public string Expression;
[SuffixLabel("Suffix with icon", SdfIconType.HeartFill)]
public string IconAndText1;
[SuffixLabel(SdfIconType.HeartFill)]
public string OnlyIcon1;
[SuffixLabel("Suffix with icon", SdfIconType.HeartFill, Overlay = true)]
public string IconAndText2;
[SuffixLabel(SdfIconType.HeartFill, Overlay = true)]
public string OnlyIcon2;
}