public class AssetListExamples : MonoBehaviour
{
[InfoBox("The AssetList attribute work on both lists of UnityEngine.Object types and UnityEngine.Object types, but have different behaviour.")]
[AssetList]
[InlineEditor(InlineEditorModes.LargePreview)]
public GameObject Prefab;
[AssetList]
public List<PlaceableObject> PlaceableObjects;
[AssetList(Path = "Plugins/Sirenix/")]
[InlineEditor(InlineEditorModes.LargePreview)]
public UnityEngine.Object Object;
[AssetList(AutoPopulate = true)]
public List<PlaceableObject> PlaceableObjectsAutoPopulated;
[AssetList(LayerNames = "MyLayerName")]
public GameObject[] AllPrefabsWithLayerName;
[AssetList(AssetNamePrefix = "Rock")]
public List<GameObject> PrefabsStartingWithRock;
[AssetList(Path = "/Plugins/Sirenix/")]
public List<GameObject> AllPrefabsLocatedInFolder;
[AssetList(Tags = "MyTagA, MyTabB", Path = "/Plugins/Sirenix/")]
public List<GameObject> GameObjectsWithTag;
[AssetList(Path = "/Plugins/Sirenix/")]
public List<Material> AllMaterialsInSirenix;
[AssetList(Path = "/Plugins/Sirenix/")]
public List<ScriptableObject> AllScriptableObjects;
[InfoBox("Use a method as a custom filter for the asset list.")]
[AssetList(CustomFilterMethod = "HasRigidbodyComponent")]
public List<GameObject> MyRigidbodyPrefabs;
private bool HasRigidbodyComponent(GameObject obj)
{
return obj.GetComponent<Rigidbody>() != null;
}
}
[AssetList(Paths = "Assets/Textures|Assets/Other/Textures")]
public MyComponent : MonoBehaviour
{
[AssetsOnly]
public GameObject MyPrefab;
}
public class BoxGroupExamples : MonoBehaviour
{
// Box with a centered title.
[BoxGroup("Centered Title", centerLabel: true)]
public int A;
[BoxGroup("Centered Title", centerLabel: true)]
public int B;
[BoxGroup("Centered Title", centerLabel: true)]
public int C;
// Box with a title.
[BoxGroup("Left Oriented Title")]
public int D;
[BoxGroup("Left Oriented Title")]
public int E;
// Box with a title recieved from a field.
[BoxGroup("$DynamicTitle1"), LabelText("Dynamic Title")]
public string DynamicTitle1 = "Dynamic box title";
[BoxGroup("$DynamicTitle1")]
public int F;
// Box with a title recieved from a property.
[BoxGroup("$DynamicTitle2")]
public int G;
[BoxGroup("$DynamicTitle2")]
public int H;
// Box without a title.
[InfoBox("You can also hide the label of a box group.")]
[BoxGroup("NoTitle", false)]
public int I;
[BoxGroup("NoTitle")]
public int J;
[BoxGroup("NoTitle")]
public int K;
#if UNITY_EDITOR
public string DynamicTitle2
{
get { return UnityEditor.PlayerSettings.productName; }
}
#endif
[BoxGroup("Boxed Struct"), HideLabel]
public SomeStruct BoxedStruct;
public SomeStruct DefaultStruct;
[Serializable]
public struct SomeStruct
{
public int One;
public int Two;
public int Three;
}
}
public class MyComponent : MonoBehaviour
{
[Button]
private void Init()
{
// ...
}
}
public class MyBot : MonoBehaviour
{
[Button]
private void Jump()
{
// ...
}
}
public class MyComponent : MonoBehaviour
{
[Button("Function")]
private void MyFunction()
{
// ...
}
}
public class MyComponent : MonoBehaviour
{
[ButtonGroup("MyGroup")]
private void A()
{
// ..
}
[ButtonGroup("MyGroup")]
private void B()
{
// ..
}
}
public class MyComponent : MonoBehaviour
{
[ButtonGroup("First")]
private void A()
{ }
[ButtonGroup("First")]
private void B()
{ }
[ButtonGroup("")]
private void One()
{ }
[ButtonGroup("")]
private void Two()
{ }
[ButtonGroup("")]
private void Three()
{ }
}
public class ColorPaletteExamples : MonoBehaviour
{
[ColorPalette]
public Color ColorOptions;
[ColorPalette("Underwater")]
public Color UnderwaterColor;
[ColorPalette("Fall"), HideLabel]
public Color WideColorPalette;
[ColorPalette("My Palette")]
public Color MyColor;
[ColorPalette("Clovers")]
public Color[] ColorArray;
}
public class MyComponent : MonoBehaviour
{
[CustomContextMenu("My custom option", "MyAction")]
public Vector3 MyVector;
private void MyAction()
{
MyVector = Random.onUnitSphere;
}
}
public class CustomDrawerExamples : MonoBehaviour
{
public float From = 2, To = 7;
[CustomValueDrawer("MyStaticCustomDrawerStatic")]
public float CustomDrawerStatic;
[CustomValueDrawer("MyStaticCustomDrawerInstance")]
public float CustomDrawerInstance;
[CustomValueDrawer("MyStaticCustomDrawerArray")]
public float[] CustomDrawerArray;
#if UNITY_EDITOR
private static float MyStaticCustomDrawerStatic(float value, GUIContent label)
{
return EditorGUILayout.Slider(value, 0f, 10f);
}
private float MyStaticCustomDrawerInstance(float value, GUIContent label)
{
return EditorGUILayout.Slider(value, this.From, this.To);
}
private float MyStaticCustomDrawerArray(float value, GUIContent label)
{
return EditorGUILayout.Slider(value, this.From, this.To);
}
#endif
}
public class MyComponent : MonoBehaviour
{
[DetailedInfoBox("This is a message", "Here is some more details about that message")]
public int MyInt;
}
public class MyComponent : MonoBehaviour
{
[DisableContextMenu]
public Vector3 MyVector;
}
public class MyComponent : MonoBehaviour
{
public bool DisableProperty;
[DisableIf("DisableProperty")]
public int MyInt;
public SomeEnum SomeEnumField;
[DisableIf("SomeEnumField", SomeEnum.SomeEnumMember)]
public string SomeString;
}
public class MyComponent : MonoBehaviour
{
[EnableIf("MyDisableFunction")]
public int MyInt;
private bool MyDisableFunction()
{
// ...
}
}
public class MyComponent : MonoBehaviour
{
[DisableInEditorMode]
public int MyInt;
}
public class MyComponent : MonoBehaviour
{
[DisableInPlayMode]
public int MyInt;
}
public class MyComponent : MonoBehaviour
{
[DisplayAsString]
public string MyInt = 5;
// You can combine with to display a message in the inspector.
[DisplayAsString, HideLabel]
public string MyMessage = "This string will be displayed as text in the inspector";
[DisplayAsString(false)]
public string InlineMessage = "This string is very long, but has been configured to not overflow.";
}
[DontApplyToListElements]
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
public sealed class VisibleIfAttribute : Attribute
{
public string MemberName { get; private set; }
public VisibleIfAttribute(string memberName)
{
this.MemberName = memberName;
}
}
public class InlineEditorExamples : MonoBehaviour
{
[EnableGUI]
public string SomeReadonlyProperty { get { return "My GUI is usually disabled." } }
}
public class MyComponent : MonoBehaviour
{
public bool EnableProperty;
[EnableIf("EnableProperty")]
public int MyInt;
public SomeEnum SomeEnumField;
[EnableIf("SomeEnumField", SomeEnum.SomeEnumMember)]
public string SomeString;
}
public class MyComponent : MonoBehaviour
{
[EnableIf("MyEnableFunction")]
public int MyInt;
private bool MyEnableFunction()
{
// ...
}
}
public enum MyEnum
{
One,
Two,
Three,
}
public class MyMonoBehaviour : MonoBehaviour
{
[EnumPaging]
public MyEnum Value;
}
public class MyComponent : MonoBehvaiour
{
[EnumToggleButtons]
public MyBitmaskEnum MyBitmaskEnum;
[EnumToggleButtons]
public MyEnum MyEnum;
}
[Flags]
public enum MyBitmaskEnum
{
A = 1 << 1, // 1
B = 1 << 2, // 2
C = 1 << 3, // 4
ALL = A | B | C
}
public enum MyEnum
{
A,
B,
C
}
public class FilePathExamples : MonoBehaviour
{
// By default, FilePath provides a path relative to the Unity project.
[FilePath]
public string UnityProjectPath;
// It is possible to provide custom parent path. Parent paths can be relative to the Unity project, or absolute.
[FilePath(ParentFolder = "Assets/Plugins/Sirenix")]
public string RelativeToParentPath;
// Using parent path, FilePath can also provide a path relative to a resources folder.
[FilePath(ParentFolder = "Assets/Resources")]
public string ResourcePath;
// Provide a comma seperated list of allowed extensions. Dots are optional.
[FilePath(Extensions = "cs")]
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(RequireValidPath = true)]
public string ValidPath;
// By default, FilePath will enforce the use of forward slashes. It can also be configured to use backslashes instead.
[FilePath(UseBackslashes = true)]
public string Backslashes;
// FilePath also supports member references with the $ symbol.
[FilePath(ParentFolder = "$DynamicParent", Extensions = "$DynamicExtensions")]
public string DynamicFilePath;
public string DynamicParent = "Assets/Plugin/Sirenix";
public string DynamicExtensions = "cs, unity, jpg";
}
public class FolderPathExamples : MonoBehaviour
{
// By default, FolderPath provides a path relative to the Unity project.
[FolderPath]
public string UnityProjectPath;
// It is possible to provide custom parent patn. ParentFolder paths can be relative to the Unity project, or absolute.
[FolderPath(ParentFolder = "Assets/Plugins/Sirenix")]
public string RelativeToParentPath;
// Using ParentFolder, FolderPath can also provide a path relative to a resources folder.
[FolderPath(ParentFolder = "Assets/Resources")]
public string ResourcePath;
// By setting AbsolutePath to true, the FolderPath will provide an absolute path instead.
[FolderPath(AbsolutePath = true)]
public string AbsolutePath;
// FolderPath can also be configured to show an error, if the provided path is invalid.
[FolderPath(RequireValidPath = true)]
public string ValidPath;
// By default, FolderPath will enforce the use of forward slashes. It can also be configured to use backslashes instead.
[FolderPath(UseBackslashes = true)]
public string Backslashes;
// FolderPath also supports member references with the $ symbol.
[FolderPath(ParentFolder = "$DynamicParent")]
public string DynamicFolderPath;
public string DynamicParent = "Assets/Plugins/Sirenix";
}
public class MyComponent : MonoBehaviour
{
[FoldoutGroup("MyGroup")]
public int A;
[FoldoutGroup("MyGroup")]
public int B;
[FoldoutGroup("MyGroup")]
public int C;
}
public class MyComponent : MonoBehaviour
{
[FoldoutGroup("First")]
public int A;
[FoldoutGroup("First")]
public int B;
[FoldoutGroup("Second")]
public int C;
}
public class MyComponent : MonoBehaviour
{
[GUIColor(1f, 0f, 0f)]
public int A;
[GUIColor(1f, 0.5f, 0f, 0.2f)]
public int B;
[GUIColor("GetColor")]
public int C;
private Color GetColor() { return this.A == 0 ? Color.red : Color.white; }
}
public class MyComponent : MonoBehaviour
{
public bool HideProperties;
[HideIf("HideProperties")]
public int MyInt;
[HideIf("HideProperties", false)]
public string MyString;
public SomeEnum SomeEnumField;
[HideIf("SomeEnumField", SomeEnum.SomeEnumMember)]
public string SomeString;
}
public class MyComponent : MonoBehaviour
{
[HideIf("MyVisibleFunction")]
public int MyHideableField;
private bool MyVisibleFunction()
{
return !this.gameObject.activeInHierarchy;
}
}
HideIfGroup allows for showing or hiding a group of properties based on a condition.
The attribute is a group attribute and can therefore be combined with other group attributes, and even be used to show or hide entire groups.
Note that in the vast majority of cases where you simply want to be able to control the visibility of a single group, it is better to use the VisibleIf parameter that *all* group attributes have.
public class MyComponent : MonoBehaviour
{
[HideInEditorMode]
public int MyInt;
}
public class MyComponent : MonoBehaviour
{
[HideInPlayMode]
public int MyInt;
}
public class MyComponent : MonoBehaviour
{
[HideLabel]
public GameObject MyGameObjectWithoutLabel;
}
[HideMonoScript]
public class MyComponent : MonoBehaviour
{
// The Script property will not be shown for this component in the inspector
}
[HideNetworkBehaviourFields]
public class MyComponent : NetworkBehaviour
{
// The "Network Channel" and "Network Send Interval" properties will not be shown for this component in the inspector
}
public class MyComponent : SerializedMonoBehaviour
{
[Header("Hidden Object Pickers")]
[Indent]
[HideReferenceObjectPicker]
public MyCustomReferenceType OdinSerializedProperty1;
[Indent]
[HideReferenceObjectPicker]
public MyCustomReferenceType OdinSerializedProperty2;
[Indent]
[Header("Shown Object Pickers")]
public MyCustomReferenceType OdinSerializedProperty3;
[Indent]
public MyCustomReferenceType OdinSerializedProperty4;
public class MyCustomReferenceType
{
public int A;
public int B;
public int C;
}
}
// The width can either be specified as percentage or pixels.
// All values between 0 and 1 will be treated as a percentage.
// If the width is 0 the column will be automatically sized.
// Margin-left and right can only be specified in pixels.
public class HorizontalGroupAttributeExamples : MonoBehaviour
{
[HorizontalGroup]
public int A;
[HideLabel, LabelWidth (150)]
[HorizontalGroup(150)]
public LayerMask B;
// LabelWidth can be helpfull when dealing with HorizontalGroups.
[HorizontalGroup("Group 1"), LabelWidth(15)]
public int C;
[HorizontalGroup("Group 1"), LabelWidth(15)]
public int D;
[HorizontalGroup("Group 1"), LabelWidth(15)]
public int E;
// Having multiple properties in a column can be achived using multiple groups. Checkout the "Combining Group Attributes" example.
[HorizontalGroup("Split", 0.5f, PaddingRight = 15)]
[BoxGroup("Split/Left"), LabelWidth(15)]
public int L;
[BoxGroup("Split/Right"), LabelWidth(15)]
public int M;
[BoxGroup("Split/Left"), LabelWidth(15)]
public int N;
[BoxGroup("Split/Right"), LabelWidth(15)]
public int O;
// Horizontal Group also has supprot for: Title, MarginLeft, MarginRight, PaddingLeft, PaddingRight, MinWidth and MaxWidth.
[HorizontalGroup("MyButton", MarginLeft = 0.25f, MarginRight = 0.25f)]
public void SomeButton()
{
}
}
public class MyComponent : MonoBehaviour
{
[Indent]
public int IndentedInt;
}
public class MyComponent : MonoBehaviour
{
[InfoBox("This is an int property")]
public int MyInt;
[InfoBox("This info box is a warning", InfoMessageType.Warning)]
public float MyFloat;
[InfoBox("This info box is an error", InfoMessageType.Error)]
public object MyObject;
[InfoBox("This info box is just a box", InfoMessageType.None)]
public Vector3 MyVector;
}
public class MyComponent : MonoBehaviour
{
[InfoBox("This info box is hidden by an instance field.", "InstanceShowInfoBoxField")]
public int MyInt;
public bool InstanceShowInfoBoxField;
[InfoBox("This info box is hideable by a static field.", "StaticShowInfoBoxField")]
public float MyFloat;
public static bool StaticShowInfoBoxField;
[InfoBox("This info box is hidden by an instance property.", "InstanceShowInfoBoxProperty")]
public int MyOtherInt;
public bool InstanceShowInfoBoxProperty { get; set; }
[InfoBox("This info box is hideable by a static property.", "StaticShowInfoBoxProperty")]
public float MyOtherFloat;
public static bool StaticShowInfoBoxProperty { get; set; }
}
public class MyComponent : MonoBehaviour
{
[InfoBox("This info box is hidden by an instance function.", "InstanceShowFunction")]
public int MyInt;
public bool InstanceShowFunction()
{
return this.MyInt == 0;
}
[InfoBox("This info box is hidden by a static function.", "StaticShowFunction")]
public short MyShort;
public bool StaticShowFunction()
{
return true;
}
// You can also specify a function with the same type of parameter.
// Use this to specify the same function, for multiple different properties.
[InfoBox("This info box is hidden by an instance function with a parameter.", "InstanceShowParameterFunction")]
public GameObject MyGameObject;
public bool InstanceShowParameterFunction(GameObject property)
{
return property != null;
}
[InfoBox("This info box is hidden by a static function with a parameter.", "StaticShowParameterFunction")]
public Vector3 MyVector;
public bool StaticShowParameterFunction(Vector3 property)
{
return property.magnitude == 0f;
}
}
public class MyComponent : MonoBehaviour
{
// Adds a button to the end of the A property.
[InlineButton("MyFunction")]
public int A;
// This is example demonstrates how you can change the label of the button.
// InlineButton also supports refering to string members with $.
[InlineButton("MyFunction", "Button")]
public int B;
private void MyFunction()
{
// ...
}
}
public class InlineEditorExamples : MonoBehaviour
{
[DisableInInlineEditors]
public Vector3 DisabledInInlineEditors;
[HideInInlineEditors]
public Vector3 HiddenInInlineEditors;
[InlineEditor]
public Transform InlineComponent;
[InlineEditor(InlineEditorModes.FullEditor)]
public Material FullInlineEditor;
[InlineEditor(InlineEditorModes.GUIAndHeader)]
public Material InlineMaterial;
[InlineEditor(InlineEditorModes.SmallPreview)]
public Material[] InlineMaterialList;
[InlineEditor(InlineEditorModes.LargePreview)]
public GameObject InlineObjectPreview;
[InlineEditor(InlineEditorModes.LargePreview)]
public Mesh InlineMeshPreview;
}
public class InlinePropertyExamples : MonoBehaviour
{
public Vector3 Vector3;
public Vector3Int Vector3Int;
[InlineProperty(LabelWidth = 12)] // It can be placed on classes as well as members
public Vector2Int Vector2Int;
}
[Serializable]
[InlineProperty(LabelWidth = 12)] // It can be placed on classes as well as members
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;
}
public MyComponent : MonoBehaviour
{
[LabelText("1")]
public int MyInt1;
[LabelText("2")]
public int MyInt2;
[LabelText("3")]
public int MyInt3;
}
public MyComponent : MonoBehaviour
{
[LabelWidth("3")]
public int MyInt3;
}
[ListDrawerSettings(HideAddButton = true, OnTitleBarGUI = "DrawTitleBarGUI")]
public List<MyType> SomeList;
#if UNITY_EDITOR
private void DrawTitleBarGUI()
{
if (SirenixEditorGUI.ToolbarButton(EditorIcons.Plus))
{
this.SomeList.Add(new MyType());
}
}
#endif
public class Car : MonoBehaviour
{
// The speed of the car must be less than or equal to 200.
[MaxValue(200)]
public float Speed;
}
public class Health : MonoBehaviour
{
// The speed value must be between 0 and 200.
[MinValue(0), MaxValue(200)]
public float Speed;
}
public class Player : MonoBehaviour
{
[MinMaxSlider(4, 5)]
public Vector2 SpawnRadius;
}
public class Player : MonoBehaviour
{
// The life value must be set to at least 1.
[MinValue(1)]
public int Life;
}
public class Health : MonoBehaviour
{
// The health value must be between 0 and 100.
[MinValue(0), MaxValue(100)]
public float Health;
}
public class MyComponent : MonoBehaviour
{
[MultiLineProperty]
public string MyString;
[ShowInInspector, MultiLineProperty(10)]
public string PropertyString;
}
[OnCollectionChanged("Before", "After")]
public List<string> list;
public void Before(CollectionChangeInfo info)
{
if (info.ChangeType == CollectionChangeType.Add || info.ChangeType == CollectionChangeType.Insert)
{
Debug.Log("Adding to the list!");
}
else if (info.ChangeType == CollectionChangeType.RemoveIndex || info.ChangeType == CollectionChangeType.RemoveValue)
{
Debug.Log("Removing from the list!");
}
}
public void After(CollectionChangeInfo info)
{
if (info.ChangeType == CollectionChangeType.Add || info.ChangeType == CollectionChangeType.Insert)
{
Debug.Log("Finished adding to the list!");
}
else if (info.ChangeType == CollectionChangeType.RemoveIndex || info.ChangeType == CollectionChangeType.RemoveValue)
{
Debug.Log("Finished removing from the list!");
}
}
public class MyComponent : 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 { }
}
public MyComponent : MonoBehaviour
{
[OnInspectorGUI]
private void MyInspectorGUI()
{
GUILayout.Label("Label drawn from callback");
}
}
public MyComponent : MonoBehaviour
{
[OnInspectorGUI("MyInspectorGUI", false)]
public int MyField;
private void MyInspectorGUI()
{
GUILayout.Label("Label before My Field property");
}
}
public MyComponent : MonoBehaviour
{
[OnInspectorGUI("GUIBefore", "GUIAfter")]
public int MyField;
private void GUIBefore()
{
GUILayout.Label("Label before My Field property");
}
private void GUIAfter()
{
GUILayout.Label("Label after My Field property");
}
}
public class MyComponent : MonoBehaviour
{
// Display current time for reference.
[ShowInInspector, DisplayAsString, PropertyOrder(-1)]
public string CurrentTime { get { GUIHelper.RequestRepaint(); 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;
}
public class MyComponent : MonoBehaviour
{
[OnStateUpdate("@$property.State.Visible = ToggleMyInt")]
public int MyInt;
public bool ToggleMyInt;
}
public class MyComponent : MonoBehaviour
{
[OnStateUpdate("@$property.State.Expanded = ExpandList")]
public List<string> list;
public bool ExpandList;
}
public class MyComponent : MonoBehaviour
{
public List>string< list;
[OnStateUpdate("@#(list).State.Expanded = $value")]
public bool ExpandList;
}
public class MyComponent : MonoBehaviour
{
[OnValueChanged("MyCallback")]
public int MyInt;
private void MyCallback()
{
// ..
}
}
public class MyComponent : MonoBehaviour
{
[OnValueChanged("OnPrefabChange")]
public GameObject MyPrefab;
// RigidBody component of MyPrefab.
[SerializeField, HideInInspector]
private RigidBody myPrefabRigidbody;
private void OnPrefabChange()
{
if(MyPrefab != null)
{
myPrefabRigidbody = MyPrefab.GetComponent<Rigidbody>();
}
else
{
myPrefabRigidbody = null;
}
}
}
public MyComponent : MonoBehaviour
{
[PreviewField]
public UnityEngine.Object SomeObject;
[PreviewField]
public Texture SomeTexture;
[HorizontalGroup, HideLabel, PreviewField(30)]
public Material A, B, C, D, F;
}
public class ProgressBarExample : MonoBehaviour
{
// Default progress bar.
[ProgressBar(0, 100)]
public int ProgressBar;
// Health bar.
[ProgressBar(0, 100, ColorMember = "GetHealthBarColor")]
public float HealthBar = 50;
private Color GetHealthBarColor(float value)
{
// Blends between red, and yellow color for when the health is below 30,
// and blends between yellow and green color for when the health is above 30.
return Color.Lerp(Color.Lerp(
Color.red, Color.yellow, MathUtilities.LinearStep(0f, 30f, value)),
Color.green, MathUtilities.LinearStep(0f, 100f, value));
}
// Stacked health bar.
// The ProgressBar attribute is placed on property, without a set method, so it can't be edited directly.
// So instead we have this Range attribute on a float to change the value.
[Range(0, 300)]
public float StackedHealth;
[ProgressBar(0, 100, ColorMember = "GetStackedHealthColor", BackgroundColorMember = "GetStackHealthBackgroundColor")]
private float StackedHealthProgressBar
{
// Loops the stacked health value between 0, and 100.
get { return this.StackedHealth - 100 * (int)((this.StackedHealth - 1) / 100); }
}
private Color GetStackedHealthColor()
{
return
this.StackedHealth > 200 ? Color.cyan :
this.StackedHealth > 100 ? Color.green :
Color.red;
}
private Color GetStackHealthBackgroundColor()
{
return
this.StackedHealth > 200 ? Color.green :
this.StackedHealth > 100 ? Color.red :
new Color(0.16f, 0.16f, 0.16f, 1f);
}
// Custom color and height.
[ProgressBar(-100, 100, r: 1, g: 1, b: 1, Height = 30)]
public short BigProgressBar = 50;
// You can also reference members by name to dynamically assign the min and max progress bar values.
[ProgressBar("DynamicMin", "DynamicMax")]
public float DynamicProgressBar;
public float DynamicMin, DynamicMax;
}
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
public class BoxGroupAttribute : PropertyGroupAttribute
{
public string Label { get; private set; }
public bool ShowLabel { get; private set; }
public bool CenterLabel { get; private set; }
public BoxGroupAttribute(string group, bool showLabel = true, bool centerLabel = false, float order = 0)
: base(group, order)
{
this.Label = group;
this.ShowLabel = showLabel;
this.CenterLabel = centerLabel;
}
protected override void CombineValuesWith(PropertyGroupAttribute other)
{
// The given attribute parameter is *guaranteed* to be of type BoxGroupAttribute.
var attr = other as BoxGroupAttribute;
// If this attribute has no label, we the other group's label, thus preserving the label across combines.
if (this.Label == null)
{
this.Label = attr.Label;
}
// Combine ShowLabel and CenterLabel parameters.
this.ShowLabel |= attr.ShowLabel;
this.CenterLabel |= attr.CenterLabel;
}
}
protected override void CombineValuesWith(PropertyGroupAttribute other) { this.Title = this.Title ?? (other as MyGroupAttribute).Title; }
protected override void CombineValuesWith(PropertyGroupAttribute other)
{
// The given attribute parameter is *guaranteed* to be of type BoxGroupAttribute.
var attr = other as BoxGroupAttribute;
// If this attribute has no label, we the other group's label, thus preserving the label across combines.
if (this.Label == null)
{
this.Label = attr.Label;
}
// Combine ShowLabel and CenterLabel parameters.
this.ShowLabel |= attr.ShowLabel;
this.CenterLabel |= attr.CenterLabel;
}
public class MyComponent : MonoBehaviour
{
[PropertyOrder(1)]
public int MySecondProperty;
[PropertyOrder(-1)]
public int MyFirstProperty;
}
public class MyComponent : MonoBehaviour
{
[PropertyRange(0, 100)]
public int MyInt;
[PropertyRange(-100, 100)]
public float MyFloat;
[PropertyRange(-100, -50)]
public decimal MyDouble;
// This attribute also supports dynamically referencing members by name to assign the min and max values for the range field.
[PropertyRange("DynamicMin", "DynamicMax"]
public float MyDynamicValue;
public float DynamicMin, DynamicMax;
}
[PropertySpace] // Defaults to a space of 8 pixels just like Unity's Space attribute.
public int MyField;
[ShowInInspector, PropertySpace(16)]
public int MyProperty { get; set; }
[ShowInInspector, PropertySpace(16, 16)]
public int MyProperty { get; set; }
[Button, PropertySpace(32)]
public void MyMethod()
{
...
}
[PropertySpace(-8)] // A negative space can also be remove existing space between properties.
public int MovedUp;
public class MyComponent : MonoBehaviour
{
[PropertyTooltip("This is an int property.")]
public int MyField;
[ShowInInspector, PropertyTooltip("This is another int property.")]
public int MyProperty { get; set; }
}
public class Health : MonoBehaviour
{
public int MaxHealth;
[ReadOnly]
public int CurrentHealth;
}
public class Health : MonoBehaviour
{
public int MaxHealth;
[ShowInInspector, ReadOnly]
private int currentHealth;
}
public class MyComponent : MonoBehaviour
{
[Required]
public GameObject MyPrefab;
[Required(InfoMessageType.Warning)]
public Texture2D MyTexture;
[Required("MyMesh is nessessary for this component.")]
public Mesh MyMesh;
[Required("MyTransform might be important.", InfoMessageType.Info)]
public Transform MyTransform;
}
[ResponsiveButtonGroup]
public void Foo() { }
[ResponsiveButtonGroup]
public void Bar() { }
[ResponsiveButtonGroup]
public void Baz() { }
[ResponsiveButtonGroup(UniformLayout = true)]
public void Foo() { }
[ResponsiveButtonGroup]
public void Bar() { }
[ResponsiveButtonGroup]
public void Baz() { }
[ResponsiveButtonGroupAttribute(UniformLayout = true, DefaultButtonSize = ButtonSizes.Large)]
public void Foo() { }
[GUIColor(0, 1, 0))]
[Button(ButtonSizes.Large)]
[ResponsiveButtonGroup]
public void Bar() { }
[ResponsiveButtonGroup]
public void Baz() { }
[TabGroup("SomeTabGroup", "SomeTab")]
[ResponsiveButtonGroup("SomeTabGroup/SomeTab/SomeBtnGroup")]
public void Foo() { }
[ResponsiveButtonGroup("SomeTabGroup/SomeTab/SomeBtnGroup")]
public void Bar() { }
[ResponsiveButtonGroup("SomeTabGroup/SomeTab/SomeBtnGroup")]
public void Baz() { }
public MyComponent : MonoBehaviour
{
[SceneObjectsOnly]
public GameObject MyPrefab;
}
public class MyComponent : MonoBehaviour
{
[ShowDrawerChain]
public int IndentedInt;
}
public class MyComponent : MonoBehaviour
{
public bool ShowProperties;
[ShowIf("showProperties")]
public int MyInt;
[ShowIf("showProperties", false)]
public string MyString;
public SomeEnum SomeEnumField;
[ShowIf("SomeEnumField", SomeEnum.SomeEnumMember)]
public string SomeString;
}
public class MyComponent : MonoBehaviour
{
[ShowIf("MyVisibleFunction")]
public int MyHideableField;
private bool MyVisibleFunction()
{
return this.gameObject.activeInHierarchy;
}
}
ShowIfGroup allows for showing or hiding a group of properties based on a condition.
The attribute is a group attribute and can therefore be combined with other group attributes, and even be used to show or hide entire groups.
Note that in the vast majority of cases where you simply want to be able to control the visibility of a single group, it is better to use the VisibleIf parameter that *all* group attributes have.
public class MyComponent : MonoBehaviour
{
[ShowInInspector]
private int myField;
[ShowInInspector]
public int MyProperty { get; set; }
}
public class MyComponent : MonoBehaviour
{
[ShowPropertyResolver]
public int IndentedInt;
}
public class MyComponent : MonoBehaviour
{
// The SuffixLabel attribute draws a label at the end of a property.
// It's useful for conveying intend about a property.
// Fx, this field is supposed to have a prefab assigned.
[SuffixLabel("Prefab")]
public GameObject GameObject;
// Using the Overlay property, the suffix label will be drawn on top of the property instead of behind it.
// Use this for a neat inline look.
[SuffixLabel("ms", Overlay = true)]
public float Speed;
[SuffixLabel("radians", Overlay = true)]
public float Angle;
// The SuffixLabel attribute also supports string member references by using $.
[SuffixLabel("$Suffix", Overlay = true)]
public string Suffix = "Dynamic suffix label";
}
public class NamedValue<T>
{
public string Name;
// The Range attribute will be applied if T is compatible with it, but if T is not compatible, an error will not be shown.
[SuppressInvalidAttributeError, Range(0, 10)]
public T Value;
}
public class MyComponent : MonoBehaviour
{
[TabGroup("First")]
public int MyFirstInt;
[TabGroup("First")]
public int AnotherInt;
[TabGroup("Second")]
public int MySecondInt;
}
public class MyComponent : MonoBehaviour
{
[TabGroup("A", "FirstGroup")]
public int FirstGroupA;
[TabGroup("B", "FirstGroup")]
public int FirstGroupB;
// The second tab group has been configured to have constant height across all tabs.
[TabGroup("A", "SecondGroup", true)]
public int SecondgroupA;
[TabGroup("B", "SecondGroup")]
public int SecondGroupB;
[TabGroup("B", "SecondGroup")]
public int AnotherInt;
}
public class MyComponent : MonoBehaviour
{
[TabGroup("ParentGroup", "First Tab")]
public int A;
[TabGroup("ParentGroup", "Second Tab")]
public int B;
// Specify 'First Tab' as a group, and another child group to the 'First Tab' group.
[TabGroup("ParentGroup/First Tab/InnerGroup", "Inside First Tab A")]
public int C;
[TabGroup("ParentGroup/First Tab/InnerGroup", "Inside First Tab B")]
public int D;
[TabGroup("ParentGroup/Second Tab/InnerGroup", "Inside Second Tab")]
public int E;
}
[TableList]
public List<SomeType> TableList = new List<SomeType>();
[Serializable]
public class SomeType
{
[LabelWidth(30)]
[TableColumnWidth(130, false)]
[VerticalGroup("Combined")]
public string A;
[LabelWidth(30)]
[VerticalGroup("Combined")]
public string B;
[Multiline(2), Space(3)]
public string fields;
}
// Inheriting from SerializedMonoBehaviour is only needed if you want Odin to serialize the multi-dimensional arrays for you.
// If you prefer doing that yourself, you can still make Odin show them in the inspector using the ShowInInspector attribute.
public class TableMatrixExamples : SerializedMonoBehaviour
{
[InfoBox("Right-click and drag column and row labels in order to modify the tables."), PropertyOrder(-10), OnInspectorGUI]
private void ShowMessageAtOP() { }
[BoxGroup("Two Dimensional array without the TableMatrix attribute.")]
public bool[,] BooleanTable = new bool[15, 6];
[BoxGroup("ReadOnly table")]
[TableMatrix(IsReadOnly = true)]
public int[,] ReadOnlyTable = new int[5, 5];
[BoxGroup("Labled table")]
[TableMatrix(HorizontalTitle = "X axis", VerticalTitle = "Y axis")]
public GameObject[,] LabledTable = new GameObject[15, 10];
[BoxGroup("Enum table")]
[TableMatrix(HorizontalTitle = "X axis")]
public InfoMessageType[,] EnumTable = new InfoMessageType[4,4];
[BoxGroup("Custom table")]
[TableMatrix(DrawElementMethod = "DrawColoredEnumElement", ResizableColumns = false)]
public bool[,] CustomCellDrawing = new bool[30,30];
#if UNITY_EDITOR
private static bool DrawColoredEnumElement(Rect rect, bool value)
{
if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition))
{
value = !value;
GUI.changed = true;
Event.current.Use();
}
UnityEditor.EditorGUI.DrawRect(rect.Padding(1), value ? new Color(0.1f, 0.8f, 0.2f) : new Color(0, 0, 0, 0.5f));
return value;
}
#endif
}
[TableMatrix(SquareCells = true, Labels = "GetLabel")]
public int[,] ChessBoard = new int[8, 8];
private (string, LabelDirection) GetLabel(int[,] array, TableAxis axis, int index)
{
var chessFileLetters = "ABCDEFGH";
switch (axis)
{
case TableAxis.Y:
return ((array.GetLength(1) - index).ToString(), LabelDirection.LeftToRight);
case TableAxis.X:
return (chessFileLetters[index].ToString(), LabelDirection.TopToBottom);
default:
return (index.ToString(), LabelDirection.LeftToRight);
}
}
public class TitleExamples : MonoBehaviour
{
[Title("Titles and Headers")]
[InfoBox(
"The Title attribute has the same purpose as Unity's Header attribute," +
"but it also supports properties, and methods." +
"\n\nTitle also offers more features such as subtitles, options for horizontal underline, bold text and text alignment." +
"\n\nBoth attributes, with Odin, supports either static strings, or refering to members strings by adding a $ in front.")]
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()
{ }
public string Combined { get { return this.MyTitle + " - " + this.MySubtitle; } }
}
public class MyComponent : MonoBehaviour
{
[Toggle("Enabled")]
public MyToggleable MyToggler = new MyToggleable();
}
public class MyToggleable
{
public bool Enabled;
public int MyValue;
}
public class MyComponent : MonoBehaviour
{
// This attribute has a title specified for the group. The title only needs to be applied to a single attribute for a group.
[ToggleGroup("FirstToggle", order: -1, groupTitle: "First")]
public bool FirstToggle;
[ToggleGroup("FirstToggle")]
public int MyInt;
// This group specifies a member string as the title of the group. A property or a function can also be used.
[ToggleGroup("SecondToggle", titleStringMemberName: "SecondGroupTitle")]
public bool SecondToggle { get; set; }
[ToggleGroup("SecondToggle")]
public float MyFloat;
[HideInInspector]
public string SecondGroupTitle = "Second";
}
public class MyComponent : MonoBehaviour
{
[ToggleLeft]
public bool MyBoolean;
}
[TypeInfoBox("This is my component and it is mine.")]
public class MyComponent : MonoBehaviour
{
// Class implementation.
}
public class MyComponent : MonoBehaviour
{
[ValidateInput("ValidateInput")]
public float Speed;
// Specify custom output message and message type.
[ValidateInput("ValidateInput", "Health must be more than 0!", InfoMessageType.Warning)]
public float Health;
private bool ValidateInput(float property)
{
return property > 0f;
}
}
public class MyComponent : MonoBehaviour
{
[ValidateInput("StaticValidateFunction")]
public int MyInt;
private static bool StaticValidateFunction(int property)
{
return property != 0;
}
}
public class MyComponent : MonoBehaviour
{
[ValueDropdown("myValues")]
public int MyInt;
// The selectable values for the dropdown.
private int[] myValues = { 1, 2, 3 };
}
public class MyComponent : MonoBehaviour
{
[ValueDropdown("myVectorValues")]
public Vector3 MyVector;
// The selectable values for the dropdown, with custom names.
private ValueDropdownList<Vector3> myVectorValues = new ValueDropdownList<Vector3>()
{
{"Forward", Vector3.forward },
{"Back", Vector3.back },
{"Up", Vector3.up },
{"Down", Vector3.down },
{"Right", Vector3.right },
{"Left", Vector3.left },
};
}
public class MyComponent : MonoBehaviour
{
// Member field of type float[].
private float[] valuesField;
[ValueDropdown("valuesField")]
public float MyFloat;
// Member property of type List<thing>.
private List<string> ValuesProperty { get; set; }
[ValueDropdown("ValuesProperty")]
public string MyString;
// Member function that returns an object of type IList.
private IList<ValueDropdownItem<int>> ValuesFunction()
{
return new ValueDropdownList<int>
{
{ "The first option", 1 },
{ "The second option", 2 },
{ "The third option", 3 },
};
}
[ValueDropdown("ValuesFunction")]
public int MyInt;
}
public class MyComponent : MonoBehaviour
{
// Make the field static.
private static MyEnum[] MyStaticEnumArray = MyEnum[] { ... };
// Force Unity to serialize the field, and hide the property from the inspector.
[SerializeField, HideInInspector]
private MyEnum MySerializedEnumArray = MyEnum[] { ... };
}
public class MyComponent : MonoBehaviour
{
[HorizontalGroup("Split")]
[VerticalGroup("Split/Left")]
public Vector3 Vector;
[VerticalGroup("Split/Left")]
public GameObject First;
[VerticalGroup("Split/Left")]
public GameObject Second;
[VerticalGroup("Split/Right", PaddingTop = 18f)]
public int A;
[VerticalGroup("Split/Right")]
public int B;
}
public class MyComponent : MonoBehaviour
{
[Wrap(-100, 100)]
public float MyFloat;
}
public class MyCustomClass : ISearchFilterable
{
public bool SearchEnabled;
public string MyStr;
public bool IsMatch(string searchString)
{
if (SearchEnabled)
{
return MyStr.Contains(searchString);
}
return false;
}
}