【问题标题】:How to force Unity's SerializedProperty to call OnValidate callback?如何强制 Unity 的 SerializedProperty 调用 OnValidate 回调?
【发布时间】:2019-11-07 18:59:42
【问题描述】:

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

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

SerializedProperty的常规使用示例,Unity方式:

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

问题:

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

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

我考虑过自己通过反射调用 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# unity3d serialization


    【解决方案1】:

    你当然可以打电话给OnValidate,也可以不使用反射来实现

    public void OnValidate() ...
    

    然后在编辑器调用中

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

    或者我有时将编辑器设为相应类型的子类,这样您也可以访问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();
    

    【讨论】:

    • 手动枚举所有可能的类型确实有效并呈现正确的行为。非常感谢您的回答 - 可能会使用它。附带说明一下,我现在不给你“接受的答案”的唯一原因是因为“如何强制 Unity 的 SerializedProperty 调用 OnValidate 回调?”的实际问题。即使您的解决方案解决了宏观问题,仍然没有答案。因此,我会稍等片刻,看看是否有人提出了更好的答案。
    • 另外,XY Problem 可能是这种情况
    猜你喜欢
    • 1970-01-01
    • 2016-11-04
    • 1970-01-01
    • 2019-10-02
    • 1970-01-01
    • 1970-01-01
    • 2021-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多