unity - 当序列化字段更改时如何更新对象?

Apo*_*olo 4 c# unity-game-engine

我确信这个问题已经存在,但我找不到它,抱歉。

我正在尝试将对象的序列化字段与其其他组件同步。

假设我有一个字段“大小”,它会影响对象变换比例:

[SerializeField]
int _size;
Run Code Online (Sandbox Code Playgroud)

我正在寻找一个事件处理程序或允许我做的事情:

void onSerializedPropertyChange() {
    transform.localScale = new Vector3(_size,_size,_size);
}
Run Code Online (Sandbox Code Playgroud)

有这样的方法吗?

最后,我们的想法是使用多个属性并在预览结果的同时调整对象属性。

Mat*_*wHD 10

根据序列化字段更新对象?

添加 OnChangedCall 元素时,您可以更新具有 SerializeField 的变量。

成员变量:

[SerializeField]
[OnChangedCall("onSerializedPropertyChange")]
private int _size;
Run Code Online (Sandbox Code Playgroud)

您现在应该能够将函数作为字符串添加到括号中,并且应该在更改时调用它。

调用的方法必须是公共的,否则会产生错误!

方法:

public void onSerializedPropertyChange() {
    transform.localScale = new Vector3(_size,_size,_size);
}
Run Code Online (Sandbox Code Playgroud)

OnChangedCall 是一个自定义的 PropertyAttribute,需要继承它。

OnChangedCall 类:

using System.Linq;
using UnityEngine;
using UnityEditor;
using System.Reflection;

public class OnChangedCallAttribute : PropertyAttribute
{
    public string methodName;
    public OnChangedCallAttribute(string methodNameNoArguments)
    {
        methodName = methodNameNoArguments;
    }
}

#if UNITY_EDITOR

[CustomPropertyDrawer(typeof(OnChangedCallAttribute))]
public class OnChangedCallAttributePropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginChangeCheck();
        EditorGUI.PropertyField(position, property, label);
        if(EditorGUI.EndChangeCheck())
        {
            OnChangedCallAttribute at = attribute as OnChangedCallAttribute;
            MethodInfo method = property.serializedObject.targetObject.GetType().GetMethods().Where(m => m.Name == at.methodName).First();

            if (method != null && method.GetParameters().Count() == 0)// Only instantiate methods with 0 parameters
                method.Invoke(property.serializedObject.targetObject, null);
        }
    }
}

#endif
Run Code Online (Sandbox Code Playgroud)


A. *_*ran 6

这是我从 IndieGameDev 的答案中得到的 OnChangedCallAttributePropertyDrawer 版本。

主要变化是添加了以下行

property.serializedObject.ApplyModifiedProperties();
Run Code Online (Sandbox Code Playgroud)

这将确保 SerializedField 在调用函数之前更新,从而修复了当序列化字段更改时场景无法正确更新的错误。

OnChangedCallAttributePropertyDrawer :

#if UNITY_EDITOR

[CustomPropertyDrawer(typeof(OnChangedCallAttribute))]
public class OnChangedCallAttributePropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginChangeCheck();
        EditorGUI.PropertyField(position, property, label);
        if (!EditorGUI.EndChangeCheck()) return;

        var targetObject = property.serializedObject.targetObject;
        
        var callAttribute = attribute as OnChangedCallAttribute;
        var methodName = callAttribute?.methodName;

        var classType = targetObject.GetType();
        var methodInfo = classType.GetMethods().FirstOrDefault(info => info.Name == methodName);

        // Update the serialized field
        property.serializedObject.ApplyModifiedProperties();
        
        // If we found a public function with the given name that takes no parameters, invoke it
        if (methodInfo != null && !methodInfo.GetParameters().Any())
        {
            methodInfo.Invoke(targetObject, null);
        }
        else
        {
            // TODO: Create proper exception
            Debug.LogError($"OnChangedCall error : No public function taking no " +
                           $"argument named {methodName} in class {classType.Name}");
        }
    }
}
#endif
Run Code Online (Sandbox Code Playgroud)

编辑 :

我只想为那些可能不知道的人分享一个小技巧,我不喜欢使用字符串引用函数,它通常很容易出错,而且您很可能不会从 IDE 获得帮助。

您可以改为使用 nameof(),这将允许您使用 IDE 自动完成功能,Rider 还将识别正在使用的函数。

[SerializeField]
[OnChangedCall(nameof(UpdateHeuristic))]
private AStar.Heuristic heuristic;

public void UpdateHeuristic()
{
    _pathfinding?.SetHeuristic(heuristic);
}
Run Code Online (Sandbox Code Playgroud)

完整代码:

using System.Linq;
using UnityEngine;
using UnityEditor;
using System.Reflection;

public class OnChangedCallAttribute : PropertyAttribute
{
    public string methodName;
    public OnChangedCallAttribute(string methodNameNoArguments)
    {
        methodName = methodNameNoArguments;
    }
}

#if UNITY_EDITOR

[CustomPropertyDrawer(typeof(OnChangedCallAttribute))]
public class OnChangedCallAttributePropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginChangeCheck();
        EditorGUI.PropertyField(position, property, label);
        if (!EditorGUI.EndChangeCheck()) return;

        var targetObject = property.serializedObject.targetObject;

        OnChangedCallAttribute callAttribute = attribute as OnChangedCallAttribute;
        var methodName = callAttribute?.methodName;

        var classType = targetObject.GetType();
        var methodInfo = classType.GetMethods().FirstOrDefault(info => info.Name == methodName);

        // Update the serialized field
        property.serializedObject.ApplyModifiedProperties();

        // If we found a public function with the given name that takes no parameters, invoke it
        if (methodInfo != null && !methodInfo.GetParameters().Any())
        {
            methodInfo.Invoke(targetObject, null);
        }
        else
        {
            // TODO: Create proper exception
            Debug.LogError($"OnChangedCall error : No public function taking no " +
                           $"argument named {methodName} in class {classType.Name}");
        }
    }
}
#endif
Run Code Online (Sandbox Code Playgroud)