Skip to content

Attributes

Andrew Rumak edited this page Sep 27, 2023 · 60 revisions

AutoProperty — Assign fields automatically
ButtonMethod — Display button in inspector
CharactersRange — Filter string field by the set of characters
ConditionalField — Conditionally display property in inspector, based on some other property value
ConstantsSelection — Popup of const, readonly or static fields and properties
DefinedValues — Display Dropdown with predefined values
DisplayInspector — Display one inspector inside of another
Foldout — Group your fields in inspector
InitializationField — Field that is not editable in playmode
Tag, Layer, SpriteLayer — Dropdown with Tags, Layers or SpriteLayers
MinMaxRange, RangedFloat and RangedInt — Ranged sliders
RangeVector — Limit Vector values set in inspector to a specified range
MaxValue, MinValue and PositiveValueOnly — Validation for numbers and vectors
MustBeAssigned — Automatically checks if field is assigned
OverrideLabel — Change visible in Inspector field name
ReadOnly — Draw property with disabled GUI
RegexString — Filter string field by the Regular Expression
RequireTag and RequireLayer — Automatically set Tag and Layer
Scene — Friendly way to keep Scene name as a string. See also SceneReference type
SearchableEnum — Nice UI for enums with lots of elements
Separator — Draw separator with or without title

Note, that in most of the cases attribute stacking is not supported yet.
You may try to mix several attributes on the same field and check if it's working, but further work is required to fully support this feature.


AutoProperty

Automatically assign property on Scene Save event (works best with AutoSave Feature!)
When used in Prefabs will be checked on Prefab Stage Opened and Saved
It can be used with ScriptableObjects, but you need to enable this feature in Settings.

Bind and cache components and arrays of components.
Allows to save some code time and gain bits of performance with absence of GetComponent calls
You'll get error message if there is no matching components.
Several modes are supported:

  • AutoPropertyMode.Children is default, similar to GetComponentsInChildren()
  • AutoPropertyMode.Parent — similar to GetComponentsInParent()
  • AutoPropertyMode.Scene — similar to Object.FindObjectsOfType()
  • AutoPropertyMode.Asset will search for this asset type in Assets folder (won't work if disabled in settings)
  • AutoPropertyMode.Any works as "Scene + Asset" modes
[AutoProperty(AutoPropertyMode.Parent)] public Rigidbody AbsentRB;
// Assuming AudioManager is "ScriptableObject Singleton" in your project
[AutoProperty(AutoPropertyMode.Asset)] public AudioManager AudioManager;
[AutoProperty] public Collider[] ChildColliders;
[AutoProperty] public GuidComponent[] ChildGuids;


You can disable the error message by specifying allowEmpty parameter


Another use-case for this attribute:
// Automatically updated collection of types on the scene, how cool is that 😁?
[AutoProperty(AutoPropertyMode.Scene)] public Camera[] AllCamerasOnScene;

You can use the predicate method to filter out the lookup.
Pass the name of the method as the second parameter and the type of the class with the method as a third:
[AutoProperty(AutoPropertyMode.Asset, nameof(EventPredicate), typeof(TestScript))]
public EventSO[] Events; // There are EventA, EventB, EventC in the project, only EventA and EventC are assigned.
public static bool EventPredicate(UnityEngine.Object obj) => obj.name != "EventB";

ButtonMethod

You may apply this attribute to any method and it will appear in inspector as a button.
If this method returns anything, the result will be logged to the console.
By default the button drawn in the bottom of the inspector, but you can change this with the first parameter.

public Collider[] Colliders;

[ButtonMethod]
private string CollectColliders()
{
    Colliders = FindObjectsOfType<Collider>();
    return Colliders.Length + " Colliders found on scene, cached";
}

ButtonAttribute Example

ButtonMethod with conditions

It is possible to draw the button conditionally. Check ConditionalField examples. The only difference is the additional first "DrawOrder" parameter:

[MustBeAssigned] public Transform ChildTransform;

// Button shown when ChildTransform is null
[ButtonMethod(ButtonMethodDrawOrder.BeforeInspector, nameof(TargetStack), true), UsedImplicitly]
private void CreateChild()
{
    var choldGO = new GameObject(name + " Child Object");
    choldGO.transform.SetParent(transform);
    choldGO.transform.localPosition = Vector3.zero;
    ChildTransform = choldGO.transform;
}

CharactersRange

See also RegexString Attribute

Allows to restrict the string field by the set of characters
Optionally, you can use several Modes:

// By default, only characters in range will be allowed
CharactersRangeMode.Allow 
// Characters in range will be removed from the string
CharactersRangeMode.Disallow 
// Highlight the field if any of the specified characters were fould
CharactersRangeMode.WarningIfAny 
// Highlight the field if some characters in string are not the characters from specified range
CharactersRangeMode.WarningIfNotMatch 

Also with the third parameter you can disable "Ignore Case" option

[CharactersRange("0123456789.-")]
public string DigitsOnly;
[CharactersRange("1234567890ABCDEF", CharacterRangeMode.WarningIfNotMatch)]
public string HexValue;

Thanks to tonygiang for this Attribute 🤓!


ConditionalField

This attribute allows you to conditionally hide/show fields in inspector, based on some other field.
You may also use ReadOnlyAttribute to make field disabled instead of hiding it

By default ConditionalField checks if "fieldToCheck" value is not null, not 0 and not empty.
Basically it means "if something assigned to field A, show field B".

Second parameter is "inverse", well.. to inverse the result 🙂 This one is optional

You may also specify "compareValues". Very handy to check for specific enum value for example!

[Separator("GameObject / Component")]
public Transform Target;
[ConditionalField(nameof(Target))]  public float TargetSpeed;
[ConditionalField(nameof(Target), inverse:true)]  public float Speed;

[Separator("Bool")]
public bool Teleport;
[ConditionalField(nameof(Teleport))] public float TeleportationDelay;

[Separator("String")]
public string Hello;
[ConditionalField(nameof(Hello), false, "Hello")] public string HelloWorld = "World! :)";
[ConditionalField(nameof(Hello))] public string HelloHint = "Print \"Hello\"!";

[Separator("Enum")] 
public AIState State = AIState.None;
[ConditionalField(nameof(State), false, AIState.Walk)] public float WalkSpeed;
[ConditionalField(nameof(State), false, AIState.Idle)] public float IdleTime;


Thanks to Kaynn-Cahya you may also specify several compare values!🤓:

public AIState State = AIState.None;
[ConditionalField(nameof(State), false, AIState.Walk, AIState.Run)] public float Speed;

ConditionalField with predicate method

Alternatively, instead of using the values of other fields as conditions, you may use the predicate method for more complex scenarios:

public int Value;
[ConditionalField(true, nameof(Predicate))] public string ValueIsEven;
private bool Predicate() => Value % 2 == 0;

// Example with inverse
public int Age;
[ConditionalField(true, nameof(IsChild))]
public bool HomeworkIsDone;
[ConditionalField(true, nameof(IsChild), true)]
public bool JobTaskIsDone;

ConditionalField with Arrays

There is a natural limitation in Unity preventing usage of attribute drawers (such as ConditionalField) on arrays.
To handle such cases CollectionWrapper was made.

public bool ShowWrappedCollection;
[ConditionalField(nameof(ShowWrappedCollection))]
public GOCollection WrappedCollection;


[Serializable]
public class GOCollection : CollectionWrapper<GameObject> {}

Since Unity 2020.1 you may use generics directly but field name won't be shown in inspector 🙄

[ConditionalField(nameof(ShowWrappedCollection))]
public CollectionWrapper<GameObject> WrappedCollection;

ConditionalField with Multiple Conditions

As it is not supported at the moment to use several ConditionalField attributes on the same field, you can utilize this overloads, to specify several conditions:

public enum Test {C, D, E}
public bool A;
public bool B;
public Test T;

[ConditionalField(nameof(A))]  public string IsA;
[ConditionalField(nameof(B), true)]  public string NotB;
[ConditionalField(nameof(A), nameof(B))]  public string AandB;
[ConditionalField(new []{nameof(A), nameof(B)}, new []{true})]   public string NotAandB;
[ConditionalField(new []{nameof(A), nameof(B)}, new []{false, true})]  public string AnotB;
[ConditionalField(new []{nameof(A), nameof(B)}, new []{true, true})]  public string NotAandNotB;
[ConditionalField(new[] { nameof(T), nameof(A), nameof(B) }, new[] { false, true, true }, Test.C )]  public string NotAandNotBButC;

ConstantsSelection

Allows to display a popup of const, readonly, static fields and properties defined in specified script:

[ConstantsSelection(typeof(PresetLayers))] public string DefaultLayer = "Default";
[ConstantsSelection(typeof(Vector3))] public Vector3 UnityDefaultVectors;
[ConstantsSelection(typeof(Color))] public Color UnityDefaultColors;

public class PresetLayers
{
	public const string Interactive = "Interactive";
	public const string Obstacle = "Obstacle";
}


DefinedValues

This attribute allows to display the dropdown of predefined values.

You can just pass a bunch of predefined values:

[DefinedValues("Hello", "World")] 
public string IsItHelloOrWorld;

[DefinedValues(.5f, 1f, 1.5f, 2f)] 
public float SimpleFloatDropdown;

You can show custom text instead of actual values in the dropdown.
To do so, as the first parameter ("withLabels") you should specify "true" and pass a string after every value:

[DefinedValues(true, 
    -1, "Left", 0, "Undefined", 1, "Right")]
public int DefaultLookDirection;

But the most interesting part is the ability to use the method that will return an array of predefined values!

[DefinedValues(nameof(StringTest))] public string StringSelection;

[DefinedValues(nameof(TransformTest))] public Transform ChildReference;

private string[] StringTest() => new[] { "A", "B", "C" };

private Transform[] TransformTest()
{
	var childs = transform.GetChilds();
	childs.Insert(0, null);
	return childs.ToArray();
}

image


DisplayInspector

Displays one inspector inside of another. It's handy if you'd like to store some settings in scriptable objects.

[DisplayInspector(displayScriptField:false)] to hide object field once assigned (useful for "single instance" settings)

[CreateAssetMenu]
public class AgentAIContextSettings : ScriptableObject
{
	public bool WanderAround;
	public float WanderDistance = 5;
}
[DisplayInspector]
public AgentAIContextSettings Settings;

Set displayScriptField to false (by default it's true) to hide property field once

[DisplayInspector(displayScriptField:false)]

DisplayInspector Example DisplayInspector Example2


Foldout

Group the fields in the inspector.
Optionally, add "true" as a second parameter to group further fields in the same foldout:

public class Player : MonoBehaviour
{
    [Foldout("Health Settings", true)] 
    public int HP;
    public int Defence;

    [Foldout("Attack Settings")] 
    public int Damage;
    [Foldout("Attack Settings")] 
    public int Piercing;
    [Foldout("Attack Settings")] 
    public float CriticalMultiplier;

    public string DisplayName;
}

image

Note, that Foldout need to be part of MonoBehaviour or ScriptableObject, it is not intended to work in Serializable classes

Thanks to PixeyeHQ for this attribute! 🤓 Official repo


InitializationField

Field will be not editable at playmode:

[InitializationField] public int MaxHP;
[ReadOnly] public int CurrentHP;

69 in this example is pure random, I swear!


Tag, Layer, SpriteLayer

[Tag]
public string Tag;
[Layer]
public int Layer;
[SpriteLayerAttribute]
public int SortingLayer;

Layer Example


MinMaxRange, RangedFloat and RangedInt

by Richard Fine

public class RandomisedHealth : MonoBehaviour
{
	[MinMaxRange(80, 120)] 
	// Will be set to 90-100 by default in inspector
	public RangedInt Health = new RangedInt(90, 100); 
	[MinMaxRange(0, 1)] 
	public RangedFloat Raito;
}

MinMaxRange Example


RangeVector

Limit Vector values set in inspector to a specified range.
Works with Vector2/3 and with VectorInt versions.

[RangeVector(new float[] { -1, -2 }, new float[] { 2, 1 })]
public Vector2 RangedFloat;

MaxValue, MinValue and PositiveValueOnly

These attributes works with numbers and vectors

[PositiveValueOnly]
public Vector2 ScanRange;
[PositiveValueOnly]
public float ChaseSpeed;

PositiveValueOnly Example


MustBeAssigned

Previously I used a lot of Debug.Assert() in Awake to ensure that all desired values are assigned through inspector. Now I just use MustBeAssigned.

It triggers on value types with default values, null refs, empty arrays and strings

[MustBeAssigned]
public MonoBehaviour MyScript;
[MustBeAssigned]
public float MyFloat;

MustBeAssigned Example


OverrideLabel

Change visible in Inspector field name:

OverrideLabel("GUID")]
public string InternalGUID;

originally made to fix serialized properties naming issue


ReadOnly

Disallow field edit in Inspector.
Might also work just like ConditionalAttribute to make field disabled, based on some other field value.

public float InitialHealth = 100;
[ReadOnly]
public float CurrentHealth;

ReadOnly Example


RegexString

See also CharactersRange Attribute

Allows to apply Regular Expression rule to the string field
Optionally, you can use several Modes:

// By default, keep only parts of the string that Match the Expression
RegexStringMode.Match
// Remove from the string parts that not match the Expression
RegexStringMode.Replace
// Highlight the field if any of the parts of the string matching the Expression
RegexStringMode.WarningIfMatch
// Highlight the field if some parts of the string not matching the Expression
RegexStringMode.WarningIfNotMatch

Also with the third parameter you can specify RegexOprions for validator

[RegexString(MyRegex.FloatingNumber)] 
public string MatchDigit;
[RegexString(@"\d+", RegexStringMode.Replace)]
public string WithoutNumbers;
[RegexString("[a-zA-Z]+", RegexStringMode.WarningIfNotMatch)] 
public string Name;
[RegexString(MyRegex.Email, RegexStringMode.WarningIfNotMatch)]
public string Email;


RequireTag and RequireLayer

[RequireLayer("Interactive")]
[RequireTag("Acquirable")]
public class PickupItem : MonoBehaviour { ... }

This attributes will set layer and tag of GameObject with such script on playmode.
Changes will persist when you will exit playmode

You may use layer index instead of a name

only works on objects present on scene initially, won't work on objects instantiated during playmode


Scene

Use with a string to display a popup of scenes, that listed in BuildSettings

[Scene]
public string SceneA;

image

Consider to use SceneReference type instead as it keeps scene reference if Scene is renamed


SearchableEnum

by incredible Ryan Hipple

[SearchableEnum]
public KeyCode SearchableKeyCode;

Also, if you want to make your enum to be searchable by default you can utilize SearchableEnumDrawer by making PropertyDrawer:

public enum TestEnum {A, B, C, D}

// Put CustomDrawer in Editor folder or wrap with ifdef
#if UNITY_EDITOR
[UnityEditor.CustomPropertyDrawer(typeof(TestEnum))] 
public class TestEnumDrawer : MyBox.EditorTools.SearchableEnumDrawer {}
#endif

// And use it as usual, without [SearchableEnum]
public TestEnum SearchableField;

SearchableEnum Example


Separator

Decorative separator. May be with or without the title

Separator Example