25
loading...
This website collects cookies to deliver better user experience
this
is used religiously.Health
, that can be raised and lowered using the HealHealth
and TakeDamage
methods, both of which cause side effects based on the new Health
value. The Health
value can be changed directly by the Inspector, but it can also be changed directly by any code in the project, even though that would bypass the side effects of the HealHealth
and TakeDamage
methods.Health
should be a public property with a private setter that is displayed by and can be changed in the Inspector.public class HealthScript : MonoBehaviour
{
public float Health;
public void HealHealth(float healAmount)
{
this.Health += healAmount;
if (this.Health > 100)
{
// Do something based on excessive health.
}
}
public void TakeDamage(float damageAmount)
{
this.Health -= damageAmount;
if (this.Health <= 0)
{
// Do something like destroy the owning object.
}
}
}
Health
to a property, the code will behave the same, but the property won't be shown in the Inspector.//public float Health;
public float Health { get; set; }
SerializeField
attribute. Since the attribute has to go on a member variable, not a property, we'll rewrite the auto-implemented property to use a manually declared backing field.//public float Health { get; set; }
public float Health { get => this._health; set => this._health = value; }
private float _health = 0;
_health
backing field with the SerializeField
attribute so Unity will detect it.public float Health { get => this._health; set => this._health = value; }
[SerializeField]
private float _health = 0;
Health
value can be read and changed in the Inspector. While properties can now be used instead of public member variables, it is a shame to give up auto-implemented properties and return to the stone age of manually declaring backing fields. Thankfully, there is a solution.field:
to the annotation.Health
property back to an auto-implemented form, annotate it with the SerializeField
attribute, and add the field
prefix to the annotation.//public float Health { get => this._health; set => this._health = value; }
//[SerializeField]
//private float _health = 0;
[field: SerializeField]
public float Health { get; set; }
Health
value can be read and changed in the Inspector, but, unlike the manually declared backing field, the InspectorName
attribute is no longer needed to display the correct label in the Inspector.<Health>k__BackingField
, instead of a more readable name of Health
.SerializeField
attribute.// This will not be serialized by Unity.
[field: SerializeField]
public float Health { get; }
[field: SerializeField]
public float Health { get; private set; }
float
values as a number that can be dragged left and right, and a property drawer that displays Vector2
values as a combination of X and Y values.PropertyAttribute
, the Unity base class for all attributes. The attribute should be restricted to fields only, as Unity only deals with fields. Lastly, the attribute should be inherited and should not allow multiple declarations on the same field. The class itself has no functionality and will only serve as a marker to invoke our custom drawer.[System.AttributeUsage(System.AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public class ReadOnlyFieldAttribute : PropertyAttribute { }
PropertyDrawer
, the Unity base class for all property drawers. The class should be annotated with two attributes; JetBrains.Annotations.UsedImplicitly
, which will keep the compiler from complaining about the class not being directly used in the project; and CustomPropertyDrawer
with the type of our attribute, which tells Unity to use the class to draw any fields annotated with our attribute.[UsedImplicitly, CustomPropertyDrawer(typeof(ReadOnlyFieldAttribute))]
public class ReadOnlyFieldAttributeDrawer : PropertyDrawer
{
}
GetPropertyHeight
and it simply wraps a call to EditorGUI.GetPropertyHeight
so that we preserve the desired height of the property as displayed by the normal drawer.public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
=> EditorGUI.GetPropertyHeight(property, label, true);
OnGUI
to do the actual drawing. Since we don't want to change the display of the value, delegate the drawing down to the built-in EditorGUI.PropertyField
method. To accomplish our goal, disabling the drawn fields, we can use the GUI.enabled
field, setting it to false
(disabled) before the drawing and back to true
(enabled) after the drawing.public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GUI.enabled = false;
EditorGUI.PropertyField(position, property, label, true);
GUI.enabled = true;
}
SerializeField
attribute. Note that the field:
expression doesn't have to be repeated before each attribute as it applies to all attributes that follow it. That done, the Health
value will now be displayed in the Inspector, but it will be displayed as disabled and won't be editable.public class HealthScript : MonoBehaviour
{
[field: SerializeField, ReadOnlyField]
public float Health { get; private set; }
}
public class HealthScript : MonoBehaviour
{
[field: SerializeField]
public float Health { get; set; }
}
protected
if the value can be modified by derived classes.public class HealthScript : MonoBehaviour
{
[field: SerializeField]
public float Health { get; private set; }
}
public class HealthScript : MonoBehaviour
{
[field: SerializeField, ReadOnlyField]
public float Health { get; set; }
}
protected
if the value can be modified by derived classes.public class HealthScript : MonoBehaviour
{
[field: SerializeField, ReadOnlyField]
public float Health { get; private set; }
}
SerializeField
attribute on backing fields, it is trivial to separate code access from Inspector access and return some sanity to the code.ReadOnlyField
attribute, it is also possible to display values in the Inspector and restrict them from being changed. More importantly, that restriction can be imposed regardless of property's declared access scope.