[ReadOnly] Attribute

There are certain cases when you want to see a variable in the inspector but don’t want to change it. Sadly, there is no default solution for that in Unity. But with a little work, you can do it for yourself. Enter the world of editor scripting!

The bad news is that it might seem somewhat complicated at first. The good news is that you can just copy and paste the code (or download it from Github). It will work and you don’t have to figure it out if you don’t want to.

So we want to write something like this in our code:

[ReadOnly]
public float testFloatValue = 123;

And see something like this in the inspector:

Read-Only value

First, let’s create the ReadOnlyAttribute class and inherit it from PropertyAttribute:

public class ReadOnlyAttribute : PropertyAttribute
{
}

The next script will be an “Editor Script” and they must live in the Editor folder. Just create an empty folder anywhere in the project and call it “Editor”. Inside this folder create a new script and call it “ReadOnlyDrawer”. This is where the magic is going to happen. State that you are gonna use UnityEditor at the beginning of the script and inherit from “PropertyDrawer”. Mark the class with CustomPropertyDrawer attribute, like this:

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyDrawer : PropertyDrawer
{
}

Here we declared that it will be a property drawer attribute and it will draw the properties of the “ReadOnlyAttribute” class. Next we need to override the OnGUI function.

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
}

(From the documentation of the PropertyDrawer class) The “position” is the rectangle on the screen to use for the property GUI. The “property” argument is the property to make the custom GUI for. The “label” argument is the label of this property.

To actually draw anything, we will make use of EditorGUI class. Add the following line to the OnGUI function:

EditorGUI.LabelField(position, label.text + ": " + "Value will be displayed here...");

To test this create a new class outside of the Editor folder and call something like ReadOnlyTest. Declare a public float value and mark it with the [ReadOnly] attribute we just created.

using UnityEngine;

public class ReadOnlyTest : MonoBehaviour
{
    [ReadOnly]
    private float testFloatValue = 123;
}

Drag the script on a game object in a scene and you should see this:

It’s Alive!

It works! Well, kind of… We got the name of the float with the “label” argument. But what about its value? Wasn’t it the whole point? Yeah, and this part is a little bit tricky. Later we might need to get a value from some other property as well. So let’s write an extension method for the “SerializedProperty”. Create a static class and paste the following function inside:

public static object GetValue(this SerializedProperty property)
{
    object obj = property.serializedObject.targetObject;
    Type type = obj.GetType();
    FieldInfo field = type.GetField(property.propertyPath, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
    return field.GetValue(obj);
}

Now, all we need to do is to change the LabelField arguments:

EditorGUI.LabelField(position, label.text + ": " + property.GetValue());

And we are done!

Yey!

Note, however, that it will not work with lists and arrays. You will have to modify the GetValue() method to make it work.

As a bonus, I will add a warning if our value is null. To do that, first open the ReadOnlyAttribute class, add a public read-only bool and a constructor:

using UnityEngine;

public class ReadOnlyAttribute : PropertyAttribute
{
    /// <summary>
    /// Writes a warning if the value of this field is null
    /// </summary>
    public readonly bool warningIfNull = false;

    public ReadOnlyAttribute(bool _warningIfNull = false)
    {
        warningIfNull = _warningIfNull;
    }
}

Since the “_warningIfNull” argument is optional we can continue to use the [ReadOnly] attribute as we did before. And if we want to set the bool to true, we can write [ReadOnly(true)].

To get the value of “warningIfNull” from the “ReadOnlyDrawer” class, we are gonna need to cast the “attribute” variable to “ReadOnlyAttribute” and cache it to a variable. The “attribute” variable is a member of “PropertyDrawer”.

Then store the property value, check if its value is null and if we need a warning.

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
    ReadOnlyAttribute att = (ReadOnlyAttribute)attribute;
    object val = property.GetValue();

    if (att.warningIfNull && (val == null || val.ToString().Equals("null")))
        val += " <-This value should NOT be NULL!";

    EditorGUI.LabelField(position, string.Format("{0}: {1}", label.text, val));
}

To test it, add a variable of type RigidBody to the “ReadOnlyTest” script and mark it with [ReadOnly(true)] attribute.

[ReadOnly(true)]
public Rigidbody rigid;

Since the value of rigid is null, you should see:

The Warning

So far we have used only public variables. You can use privet ones as well, just make sure to mark them with [SerializeField] attribute. Unity won’t even try to draw it otherwise.

[SerializeField, ReadOnly]
private float testFloatValue = 123;

To summarize here is the ReadOnlyAttribute class:

public class ReadOnlyAttribute : PropertyAttribute
{
    /// <summary>
    /// Writes a warning if the value of this field is null
    /// </summary>
    public readonly bool warningIfNull = false;

    public ReadOnlyAttribute(bool _warningIfNull = false)
    {
        warningIfNull = _warningIfNull;
    }
}

The ReadOnlyDrawer class (I’ve optimized the string a little):

[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        ReadOnlyAttribute att = (ReadOnlyAttribute)attribute;
        object val = property.GetValue();

        if (att.warningIfNull && (val == null || val.ToString().Equals("null")))
            val += " <-This value should NOT be NULL!";

        EditorGUI.LabelField(position, string.Format("{0}: {1}", label.text, val));
    }
}

GetValue() function in the static Utils class:

public static Utils
{    
    public static object GetValue(this SerializedProperty property)
    {
        object obj = property.serializedObject.targetObject;
        Type type = obj.GetType();
        FieldInfo field = type.GetField(property.propertyPath, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        return field.GetValue(obj);
    }
}

And the ReadOnlyTest class:

using UnityEngine;

public class ReadOnlyTest : MonoBehaviour
{
    [ReadOnly(true)]
    public Rigidbody rigid;

    [SerializeField, ReadOnly]
    private float testFloatValue = 123;
}

If you are too lazy to copy-paste it, download it from the Github. I’ve added it to the ExtraTools project, check the rest of the project as well, you might find a couple of useful shortcuts.

Let me know if you know a better way to achieve this or if you have a correction or a suggestion for a future blog post!

Leave a Reply

Your email address will not be published. Required fields are marked *