0

由于我完全没有想法,我想我会在这里发布,以防有​​人知道为什么会这样。

我有一个名为 Behavior 的简单类,它保存一个字符串和一个字符串数组。它看起来像这样:

[System.Serializable]
public class Behaviour {
  public string Methodname;
  public string[] Parameters;
}

正如您可能已经预料到的,这个类是为了保存一个方法,以便可以将方法插入统一检查器。Methodname 是插入的方法的名称,而 Parameters 是以静态实用程序类可以读取的方式格式化的该方法的所有参数(这些字符串被转换为对象,然后用作参数。所以一个字符串“i25 " 将转换为 25 的整数参数。该数组是一个字符串,因为 object[] 无法序列化,因此无法保存。

我将剔除所有不必要的逻辑并专注于出了什么问题。让我们假设我们想要将一个整数保存到参数数组的第一个索引中。行为的 PropertyDrawer 将如下所示:

[CustomPropertyDrawer(typeof(Behaviour))]
public class BehaviourEditor : PropertyDrawer {
  private Behaviour propertyReference;

  public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    propertyReference = fieldInfo.GetValue(property.serializedObject.targetObject) as Behaviour;
    // Check if the string array is null. This happens the very first time the scriptable object is created
    if(propertyReference.Parameters == null) propertyReference.Parameters = new string[1];
    // Check if the first index is null. In this simplified example also only happening the very first time.
    if(propertyReference.Parameters[0] == null) propertyReference.Parameters[0] = "i0";

    int value = (int)DataConverter.StringToObject(propertyReference.Parameters[index]);
    value = EditorGUILayout.IntField(value);
    propertyReference.Parameters[0] = DataConverter.ObjectToString(value);
  }
}

DataConverter 简单地将对象转换为字符串,反之亦然(因此 int n = 9 将变为“i9”或“i255”将变为对象 x = 255)。所有这些逻辑都有效。

再次澄清:这是PropertyDrawerBehavior行为ScriptableObject中的私有 [SerializedProperty] 。

如果数组为空,则 if == null 触发并将数组参数设置为长度为 1。后面的逻辑一切正常,我们可以从 EditorGUILayout 为 int 字段赋值,并且那里的值正确保存到数组中。所有这些逻辑都有效。

但是:有些东西正在改变行为的参数数组。我相信这个东西是ScriptableObject。下一帧,Parameters 不再为空(显然),我们尝试访问索引位置 0。这导致索引超出范围异常,因为某些内容将 Parameters 更改为新字符串 [0]。他们没有将其设置为 null,而是将其长度设置为 0。

为什么?什么可能触发这个逻辑?如果我将参数设置为属性并在设置方法中设置断点,除了上面我自己的代码之外没有其他人调用它,但数组的长度仍然为 0。有什么想法吗?


下面的答案是正确的:代码没有被正确应用,因此被序列化过程覆盖。必须添加 BeginProperty() 和 EndProperty()。

对于将来发现此问题的任何人,最终代码如下所示:

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
  SerializedProperty parameters = FindPropertyRelative("Parameters");

  EditorGUI.BeginProperty(position, label, property);
  // the enum popup for Methodname not shown here to keep it simple

  if(parameters.arraySize == 0) parameters.arraySize = 1;
  SerializedProperty element = parameters.GetArrayElementAtIndex(0);
  if(element.stringValue == string.Empty) element.stringValue = DataConverter.GetObjectDefaultString(typeof(int));
  int temp = (int)DataConverter.StringToObject(element.stringValue);
  temp = EditorGUILayout.IntField(temp);
  element.stringValue = DataConverter.ObjectToString(temp);

  EditorGUI.EndProperty();
4

1 回答 1

0

Unity 的序列化器确实会自动初始化任何可序列化类型的字段,例如列表或数组string等!-> 他们永远不会null,最坏的情况下他们会是空的。

如果您只是想为您的字段设置默认值,您可以简单地在您的类本身中分配它们,不需要抽屉:

[Serializable]
public class Behaviour 
{
    public string Methodname = "ExampleMethod";
    public string[] Parameters = new string[1] { "i0" };
}

通常不要通过编辑器脚本中的目标直接更改值!

这将使您在将更改的对象标记为脏、序列化/保存值正确持久、处理撤消/重做等方面感到头疼

总是宁愿通过SerializedProperty并使用例如

var methodName = property.FindPropertyRelative(nameof(Behavior.Methodname));
var parameters = property.FindPropertyRelative(nameof(Behavior.Parameters));

然后做例如

EditorGUILayout.PropertyField(methodName);

并访问和分配例如

parameters.arraySize = 1;
var parameter = parameters.GetElementAtIndex(0);
parameter.stringValue = "i0";

但正如所说的这只是一个例子,你真的想简单地将默认值放在类中,而不是抽屉中。

还放

EditorGUI.BeginProperty(position, label, property);

...

EditorGUI.EndProperty();

围绕您的财产抽屉内容对于脏状态处理至关重要!请参阅属性抽屉

于 2022-01-22T17:52:03.643 回答