首页 > 解决方案 > 如何强制 Unity 的 SerializedProperty 调用 OnValidate 回调?

问题描述

我正在编写一个扩展方法来简化SerializedProperty的使用

这个想法是该方法的用户应该能够设置 a 的值SerializedProperty而不必担心类型。如下例所示,正确的类型应该根据 和 的类型来serializedProperty处理myValue

SerializedPropertyUnity方式的常规使用示例:

serializedProperty.intValue = myIntValue;
serializedProperty.floatValue = myFloatValue;
serializedProperty.boolValue = myBoolValue;
...

SerializedProperty我的方法扩展的预期语法

serializedProperty.SetValue(myValue);

我目前对此SerializedProperty方法扩展的实现

public static void SetValue<TValue>(this SerializedProperty property, TValue value)
{
    if (property.hasMultipleDifferentValues)
        throw new ArgumentOutOfRangeException();

    Type parentType = property.serializedObject.targetObject.GetType();
    FieldInfo fieldInfo = parentType.GetField(property.propertyPath);
    fieldInfo.SetValue(property.serializedObject.targetObject, value);
}

问题:

此实现不调用OnValidateUnity 回调。常规用法 ( mySerializedProperty.intValue = myInt) 可以。

我的问题:有什么方法可以强制 Unity 调用该OnValidate方法SerializedProperty吗?

我考虑OnValidate过通过反射自己调用,但由于这已经在 Unity 中实现,我想知道是否有办法将其标记SerializedProperty为已更改或类似的东西。


为了测试这种行为,我编写了以下测试(NUnit):

private IntMonoBehaviourMock _mockInstance;

[SetUp]
public void CallBeforeEachTest()
{
    GameObject gameObject = new GameObject();
    this._mockInstance = gameObject.AddComponent<IntMonoBehaviourMock>();
}

[Test]
// This test fails for the current implementation
public void SetValue_ToDifferentValue_OnValidateCalledOnce()
{
    this.SetValueOfSUT(0x1CE1CE);

    int numCallsBefore = this._mockInstance.NumTimesValidateCalled;
    this.SetValueOfSUT(0xBABE);
    int numCallsAfter = this._mockInstance.NumTimesValidateCalled;

    int actualNumCalls = numCallsAfter - numCallsBefore;
    Assert.AreEqual(1, actualNumCalls); // Fails
}

private void SetValueOfSUT(int value)
{
    string fieldName = "publicSerializedField"
    SerializedObject serializedObject = new SerializedObject(this._mockInstance);
    SerializedProperty sut = this._serializedObject.FindProperty(fieldName);

    // This is the call to function being tested!
    // Swapping this for sut.intValue = value, makes the test pass.
    // (But the purpose is to write a function that handles any type correctly)
    sut.SetValue(value);

    this._serializedObject.ApplyModifiedProperties();
}

我在测试中使用的IntMonoBehaviourMock的实现是:

public class IntMonoBehaviourMock: MonoBehaviour
{
    [SerializeField]
    public int publicSerializedField = default;

    public int NumTimesValidateCalled { get; private set; }

    protected void OnValidate()
    {
        this.NumTimesValidateCalled++;
    }
}

测试结果是:

Expected: 1
  But was:  0

标签: c#unity3dserialization

解决方案


OnValidate你当然可以通过制作它来调用没有反射

public void OnValidate() ...

然后在编辑器调用中

theProperty.SetValue(5);
((IntMonoBehaviourMock)target).OnValidate();

或者我有时会做的是让 Editor 成为相应类型的子类,这样你也可以访问privateprotected方法。我通常男性仍然使用不同的文件

public partial class IntMonoBehaviourMock
{
    ...

#if UnityEditor
    private partial class IntMonoBehaviourMockEditor { }
#endif
}

然后在一个单独的文件中

#if UnityEditor
public partial class IntMonoBehaviourMock
{
    [CustomEditor(typeof(IntMonoBehaviourMock)]
    private partial class IntMonoBehaviourMockEditor : Editor
    {
        ...
    }
}
#endif

可能的类型非常有限

AnimationCurve, BoundsInt, bool, Bounds, Color, double, float, int, long, 
Quaternion, RectInt, Rect, string, Vector2, Vector2Int, Vector3, Vector3Int, Vector4

和一个特别的

UnityEngine.Object

这是(几乎)所有 Unity 类的父类。

我可以考虑使用类似的东西

var type = typeof(TValue);

if(type == typeof(int))
{
    serializedProperty.intValue = value;
}
else if(type == typeof(string)
{
    serializedProperty.stringValue = value;
}

...

else if(type.IsAssignableFrom(typeof(UnityEngine.Object)))
{
    serializedProperty.objectReferenceValue = value;
}
else
{
    Debug.LogError("Unassignable type: " + type.FullName);
}

实际上你甚至不需要泛型,但也可以简单地使用

var type = value.GetType();

推荐阅读