Как заставить SerializedProperty Unity вызывать обратный вызов OnValidate? - PullRequest
0 голосов
/ 07 ноября 2019

Я пишу метод расширения для упрощения использования SerializedProperty

Идея состоит в том, что пользователь метода должен иметь возможность установить значение SerializedProperty, не имеябеспокоиться о типе. Как показано в примере ниже, правильный тип должен обрабатываться в соответствии с типом serializedProperty и myValue.

Пример регулярного использования 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 вызывать метод 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

1 Ответ

1 голос
/ 08 ноября 2019

Вы можете, конечно, вызвать OnValidate также без Reflection, сделав его

public void OnValidate() ...

, а затем в вызове редактора

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

, или что я иногда делаю, это сделать Редакторподкласс соответствующего типа, поэтому у вас есть доступ к методам private и protected. Обычно я записываю его в разные файлы, используя

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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...