0

I am creating a custom drawer for a custom attribute that helps me initialize the value of a field marked with the attribute SerializeReference.

Basically, the custom drawer will show a dropdown menu that allows me to select the Type to create and assign to the field.

I have the following code so far, to test the different scenarios:

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
    var targetObject = property.serializedObject.targetObject;
    var field = targetObject.GetType().GetField(property.name, FieldBindingFlags)!;

    if (field.GetValue(targetObject) == null)
        field.SetValue(targetObject, new OperaSinger());
    else
    {
        EditorGUI.BeginProperty(position, label, property);
        EditorGUI.PropertyField(position, property, label, true);
        EditorGUI.EndProperty();
    }
}

And those are the test classes:

interface ISinger
{
    void Sing();
}

[System.Serializable]
class OperaSinger : ISinger
{
    [SerializeField] private string name;
    void Sing(){}
}

class StreetPerformer : MonoBehaviour, ISinger
{
    [SerializeField] private string streetName;
    void Sing(){}
}

The above code seems to work fine, it initializes the property to a new instance of the OperaSinger and shows the editor.

But, when I try to do the same with the MonoBehaviour implementation, I get an error:

Attempt #1:

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
    var targetObject = property.serializedObject.targetObject;
    var field = targetObject.GetType().GetField(property.name, FieldBindingFlags)!;

    if (field.GetValue(targetObject) == null)
    {
        var x = ((Component)targetObject).gameObject.AddComponent<StreetPerformer>();
        field.SetValue(targetObject, x);
    }
    else
    {
        EditorGUI.BeginProperty(position, label, property);
        EditorGUI.PropertyField(position, property, label, true);
        EditorGUI.EndProperty();
    }
}

Error: [SerializeReference] cannot serialize objects that derive from Unity.Object.


Attempt #2:

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
    var targetObject = property.serializedObject.targetObject;
    var field = targetObject.GetType().GetField(property.name, FieldBindingFlags)!;

    if (field.GetValue(targetObject) == null)
    {
        var x = ((Component)targetObject).gameObject.AddComponent<StreetPerformer>();
        property.objectReferenceValue = x;
    }
    else
    {
        EditorGUI.BeginProperty(position, label, property);
        EditorGUI.PropertyField(position, property, label, true);
        EditorGUI.EndProperty();
    }
}

Error: type is not a supported pptr value


Attempt #3:

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
    var targetObject = property.serializedObject.targetObject;
    var field = targetObject.GetType().GetField(property.name, FieldBindingFlags)!;

    if (field.GetValue(targetObject) == null)
    {
        var x = ((Component)targetObject).gameObject.AddComponent<StreetPerformer>();
        property.objectReferenceInstanceIDValue = x.GetInstanceID();
    }
    else
    {
        EditorGUI.BeginProperty(position, label, property);
        EditorGUI.PropertyField(position, property, label, true);
        EditorGUI.EndProperty();
    }
}

Error: type is not a supported pptr value


What am I doing wrong, and how can I fix it?

As far as I understand, the error from attempts 2 and 3 is because the field is defined as the interface type ISinger.

1 Answer 1

0

Well, after trying the following:

[SerializeReference] private ISinger singer;

private void Reset()
{
    singer = gameObject.AddComponent<StreetPerformer>()
}

And getting the same error as attempt 1, I realized that it is not possible to do it.

So, the solution I came up with is creating a wrapper-class to the StreetPerformer class that will delegate all methods to it like so:

internal sealed class StreetPerformer_ISing_Wrapper : ISing
{
    [SerializeField] private StreetPerformer _instance;

    public void Sing()
    {
        _instance.Sing();
    }

}

Obviously, doing so to every class that I need will be tedious so I simply wrote a code generator that creates (and updates) this class for me.

So now I can have an interface field in my script that references a regular class, a ScriptableObject or a MonoBehaviour with minimum effort 😁

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.