unity3d Unity自定义属性抽屉设置'SerializeReference'为UnityEngine.Object

pkwftd7m  于 2023-01-26  发布在  其他
关注(0)|答案(1)|浏览(385)

我正在为一个定制属性创建一个定制抽屉,这个定制属性可以帮助我初始化一个标记为SerializeReference属性的字段的值。
基本上,定制抽屉将显示一个下拉菜单,允许我选择要创建的Type并将其分配给字段。
到目前为止,我已经编写了以下代码来测试不同的场景:

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();
    }
}

这些是测试类:

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(){}
}

上面的代码看起来工作正常,它将属性初始化为OperaSinger的一个新示例,并显示编辑器。
但是,当我尝试对MonoBehaviour实现执行相同的操作时,我得到了一个错误:

尝试#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();
    }
}

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

尝试#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();
    }
}

错误:type is not a supported pptr value

尝试#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();
    }
}

错误:type is not a supported pptr value

我做错了什么,我该怎么补救?
据我所知,第2次和第3次尝试的错误是因为该字段被定义为接口类型ISinger

wgx48brx

wgx48brx1#

好吧,在尝试了以下方法之后:

[SerializeReference] private ISinger singer;

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

得到了与尝试1相同的错误,我意识到这是不可能的。
因此,我提出的解决方案是为StreetPerformer类创建一个wrapper-class,它将把所有方法委托给StreetPerformer类,如下所示:

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

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

}

显然,对我需要的每个类都这样做是很乏味的,所以我只是编写了一个代码生成器来为我创建(和更新)这个类。
因此,现在我可以在脚本中创建一个接口字段,它引用一个常规类、ScriptableObject或MonoBehaviour,而这一切都是最简单的😁

相关问题