[英]How to force Unity's SerializedProperty to call OnValidate callback?
I am writing a extension method to simplify the use of SerializedProperty我正在编写一个扩展方法来简化SerializedProperty的使用
The idea is that the user of the method should be able to set the value of a SerializedProperty
without having to worry about the type.这个想法是,该方法的用户应该能够设置
SerializedProperty
的值,而不必担心类型。 As illustrated in the example below, the correct type should be handled according the the type of serializedProperty
and myValue
.如下例所示,正确的类型应该根据
serializedProperty
和myValue
的类型来处理。
Example of the regular use of SerializedProperty
, the Unity way:常规使用
SerializedProperty
的示例,Unity 方式:
serializedProperty.intValue = myIntValue;
serializedProperty.floatValue = myFloatValue;
serializedProperty.boolValue = myBoolValue;
...
Intended syntax for my SerializedProperty
method extension我的
SerializedProperty
方法扩展的预期语法
serializedProperty.SetValue(myValue);
My current implementation of this SerializedProperty
method extension我当前对此
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);
}
The Problem:问题:
This implementation does not call the OnValidate
Unity callback.此实现不调用
OnValidate
Unity 回调。 The regular usage ( mySerializedProperty.intValue = myInt
) does.常规用法(
mySerializedProperty.intValue = myInt
)确实如此。
MY QUESTION : Is there any way to force Unity to call the OnValidate
method on the SerializedProperty
?我的问题:有什么方法可以强制 Unity 在
SerializedProperty
上调用OnValidate
方法?
I have considered calling OnValidate
by myself via reflection but since this is already implemented in Unity I was wondering if there was a way to mark the SerializedProperty
as changed or something similar.我曾考虑通过反射自己调用
OnValidate
,但由于这已经在 Unity 中实现,我想知道是否有办法将SerializedProperty
标记为已更改或类似的东西。
For testing this behavior, I have written the following test (NUnit):为了测试这种行为,我编写了以下测试(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();
}
The implementation of IntMonoBehaviourMock that I am using in the test is:我在测试中使用的IntMonoBehaviourMock的实现是:
public class IntMonoBehaviourMock: MonoBehaviour
{
[SerializeField]
public int publicSerializedField = default;
public int NumTimesValidateCalled { get; private set; }
protected void OnValidate()
{
this.NumTimesValidateCalled++;
}
}
The result of the test is:测试结果是:
Expected: 1
But was: 0
You could ofcourse call OnValidate
also without Reflection by making it你当然可以调用
OnValidate
也没有反射
public void OnValidate() ...
and then in the Editor call然后在编辑器调用中
theProperty.SetValue(5);
((IntMonoBehaviourMock)target).OnValidate();
or what I sometimes do is make the Editor a subclass of the according type so you have access to private
and protected
methods as well.或者我有时会做的是让 Editor 成为相应类型的子类,这样你也可以访问
private
和protected
的方法。 I usually male it still in different files using我通常男性仍然使用不同的文件
public partial class IntMonoBehaviourMock
{
...
#if UnityEditor
private partial class IntMonoBehaviourMockEditor { }
#endif
}
and then in a separate file然后在一个单独的文件中
#if UnityEditor
public partial class IntMonoBehaviourMock
{
[CustomEditor(typeof(IntMonoBehaviourMock)]
private partial class IntMonoBehaviourMockEditor : Editor
{
...
}
}
#endif
The possible types are quite limited可能的类型非常有限
AnimationCurve, BoundsInt, bool, Bounds, Color, double, float, int, long,
Quaternion, RectInt, Rect, string, Vector2, Vector2Int, Vector3, Vector3Int, Vector4
and a special one和一个特别的
UnityEngine.Object
which is the parent class for (almost) all Unity classes.这是(几乎)所有 Unity 类的父 class。
I can think of alternatively using something like我可以考虑使用类似的东西
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);
}
actually you would then not even require the generic but could also simply use实际上你甚至不需要泛型,但也可以简单地使用
var type = value.GetType();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.