Gadget is package containing a handful of useful and stackable Unity Inspector attributes.
The goal of Gadget is to make Unity game development more fun.
Install via Git URL by declaring the package in your manifest.json file.
"io.savolainen.gadget.core": "https://github.com/jonisavo/gadget.git#latest"
Based on work by Garett Bass and contributors, see UnityExtensions.InspectInline.
Allows viewing UnityEngine.Object
in an inline editor, and creating new ScriptableObject
and Material
assets.
[CreateAssetMenu(fileName = "EnemyData", menuName = "Enemy Data")]
public class EnemyData : ScriptableObject
{
public string name;
public int healthPoints;
}
[CreateAssetMenu(fileName = "BossData", menuName = "Boss Data")]
public class BossData : EnemyData
{
public AudioClip theme;
[Inline]
public EnemyData[] henchmen;
}
[Inline]
public EnemyData[] enemies;
[Inline]
public BossData boss;
Choose the type of a SerializeReference
field.
[Serializable]
public abstract class Character
{
public string name;
public abstract void Attack();
}
[Serializable]
public class Swordsman : Character
{
public float strength;
public override void Attack() { /* ... */ }
}
[Serializable]
public class Orc : Character
{
public float scariness;
public override void Attack() { /* ... */ }
}
public interface IEvent
{
public void Trigger();
}
[Serializable]
public struct ScreenFadeEvent : IEvent
{
public Gradient fadeGradient;
public AnimationCurve fadeCurve;
public void Trigger() {}
}
[Serializable]
public struct PlayerDamageEvent : IEvent
{
public float damageAmount;
public void Trigger() {}
}
[SerializeReference, ReferenceTypeSelector]
public Character character;
[SerializeReference, ReferenceTypeSelector]
public IEvent[] events;
Specify the type menu path used with [Inline]
and [ReferenceTypeSelector]
.
[TypeMenuPath("Heroes/Swordsman")]
public class Swordsman : Character { /* ... */ }
[TypeMenuPath("Heroes/Thief")]
public class Thief : Character { /* ... */ }
[TypeMenuPath("Enemies/Orc")]
public class Orc : Character { /* ... */ }
[TypeMenuPath("Enemies/Bosses/Evil King")]
public class EvilKing : Character { /* ... */ }
Enables the field if the given member resolves to true
.
The member can be a field or a method. Visibility is ignored.
A member name prefixed with !
has an inverse effect.
public bool followPlayer;
[EnableIf("followPlayer")]
public Transform playerPosition;
[EnableIf("!followPlayer")
public bool goShopping;
public bool IsActive => gameObject.activeInHierarchy;
private static bool IsHappyHour()
{
var hour = DateTime.Now.Hour;
return hour == 21 || hour == 22;
}
[EnableIf("IsHappyHour")]
public bool happyHourBooleanField;
Makes the field visible if the given member resolves to true
.
The member can be a field or a method. Visibility is ignored.
A member name prefixed with !
has an inverse effect.
public bool enableDebugMode;
[ShowIf("enableDebugMode")]
public bool invincibility;
[ShowIf("!enableDebugMode")
public bool enforceSecurity;
public bool IsActive => gameObject.activeInHierarchy;
[ShowIf("!IsActive")]
public string IAmHidden = "I am hidden most of the time :)";
private bool _angry;
[SerializeField, ShowIf("_angry")]
private int _angerLevel;
Disables the field. Useful for debugging values.
[SerializeField, Disabled]
private Vector3 _currentPosition;
Gadget comes with replacements for Unity's built-in attributes to enable stacking them. Some even have additional features.
A stackable version of Unity's [ContextMenuItem]
.
Additional feature: if a single string is passed to the constructor, it is used for the function and the context menu item.
[GadgetContextMenuItem("Reset")]
public Vector3 position;
private void Reset()
{
position = Vector3.zero;
}
Additional feature: supports functions with a parameter.
[GadgetContextMenuItem("Print")]
[GadgetContextMenuItem("PrintTypeOfObject", "Print Type")]
public string name;
private void Print(string str)
{
Debug.Log(str);
}
private void PrintTypeOfObject(object obj)
{
Debug.Log(obj.GetType());
}
A stackable version of Unity's [ColorUsage]
.
Additional feature: has ShowAlpha
and HDR
properties for configuring the
attribute without boolean arguments.
[GadgetColorUsage(ShowAlpha = false, HDR = true)]
public Color myColor;
Additional feature: allows hiding the eye dropper tool.
[GadgetColorUsage(ShowEyedropper = false)]
public Color myColor;
A stackable version of Unity's [GradientUsage]
.
Additional feature: has additional ColorSpace
and HDR
properties for
configuring the attribute without boolean arguments.
[GadgetGradientUsage(ColorSpace = ColorSpace.Linear, HDR = true)]
public Gradient myGradient;
A stackable version of Unity's [Multiline]
.
Additional feature: has a Lines
property for configuring the attribute
without an integer argument.
[GadgetMultiline(Lines = 5)]
public string messageWithFiveLines;
A stackable version of Unity's [Range]
.
A stackable version of Unity's [Min]
.
A stackable version of Unity's [Tooltip]
.
A stackable version of Unity's [Delayed]
.
All attributes defined by Gadget can be combined.
private static bool IsHappyHour()
{
int hour = DateTime.Now.Hour;
return hour >= 20 && hour <= 23;
}
public bool enableDebugMode;
[ShowIf("IsHappyHour"), EnableIf("enableDebugMode"), GadgetTooltip("Shh!")]
public bool happyHourSecretField;
To create your own extension, you need an attribute class that inherits
from GadgetPropertyAttribute
.
public class MyCoolModifierAttribute : GadgetPropertyAttribute
{
/* ... */
}
Create a new GadgetDrawerExtension
with a GadgetExtensionFor
attribute pointing
to your property attribute type.
[GadgetExtensionFor(typeof(MyCoolModifierAttribute))]
public class MyCoolModifierDrawerExtension : GadgetDrawerExtension
{
/* ... */
}
The class has three protected properties:
Attribute
: theGadgetPropertyAttribute
the extension is drawingLabel
: theGUIContent
label used for the fieldFieldInfo
: theFieldInfo
of the field
You can override the following virtual methods:
public override void OnPreGUI(Rect position, SerializedProperty property)
{
/* ... */
}
Called before drawing the actual property field.
OnPreGUI
is called for each extension in the order they are defined in.
[A] [B] [C]
public float field;
The extension for A
is first, then B
, then C
.
public override void OnPostGUI(Rect position, SerializedProperty property)
{
/* ... */
}
Called after drawing the actual property field.
OnPostGUI
is called for each extension in the reverse order.
[A] [B] [C]
public float field;
The method for C
is called first, then B
, then A
.
public override bool TryOverrideMainGUI(Rect position, SerializedProperty property)
{
/* ... */
}
By default, EditorGUI.PropertyField
is used to draw the property. Extensions have
the opportunity to override this behaviour with TryOverrideMainGUI
.
Only one extension is able to override the main GUI.
public override bool TryOverrideMainGUI(Rect position, SerializedProperty property)
{
if (!PropertyIsValid(property))
return false;
/* ... */
return true;
}
private static bool PropertyIsValid(SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.Integer;
}
public override bool TryOverrideHeight(SerializedProperty property, GUIContent label, out float newHeight)
{
/* ... */
}
Used in conjuction with TryOverrideMainGUI
. The method should output the new height if
the extension wishes to override the property height.
Only one extension is able to override the property height.
public override bool TryOverrideHeight(SerializedProperty property, GUIContent label, out float newHeight)
{
newHeight = 0f;
if (!PropertyIsValid(property))
return false;
newHeight = /* ... */
return true;
}
public override bool IsVisible(SerializedProperty property)
{
/* ... */
}
If the method returns false
, the property will not be drawn.
public override bool IsEnabled(SerializedProperty property)
{
}
If the method returns false
, the property will be disabled.
public override bool IsInvalid(SerializedProperty property, out string errorMessage)
{
/* ... */
}
If the method returns true
, the property is invalid and an error message will
be shown in the inspector with the errorMessage
set inside the method.
public override bool IsInvalid(SerializedProperty property, out string errorMessage)
{
errorMessage = "Field is not an integer";
return property.propertyType != SerializedPropertyType.Integer;
}
public override bool CanCacheInspectorGUI(SerializedProperty property)
{
/* ... */
}
If the method returns false
, the GUI will not be cached.