ScriptableObject 将 PropertyDrawer 数组重置为零长度
ScriptableObject resetting Array of PropertyDrawer to Length of Zero
我有一个名为 Behavior 的简单 class,它保存一个字符串和一个字符串数组。它看起来像这样:
[System.Serializable]
public class Behaviour {
public string Methodname;
public string[] Parameters;
}
如您所料,这个class是为了保存一个方法,以便将方法插入到unity inspector中。 Methodname 是插入的方法的名称,而 Parameters 是以静态实用程序 class 可以读取的方式格式化的该方法的所有参数(这些字符串被转换为对象,然后用作参数。所以 a “i25”的字符串将转换为 25 的整数参数。数组是一个字符串,因为对象 [] 无法序列化,因此无法保存。
我会剔除所有不必要的逻辑并专注于问题所在。假设我们要将单个整数保存到参数数组的第一个索引中。 Behavior 的 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)。所有这些逻辑都有效。
再次说明:这是Behaviour的PropertyDrawer。 Behaviour 是 ScriptableObject.
中的私有 [SerializedProperty]
如果数组为null,则if == null 触发并将数组参数设置为1 的长度。后面的逻辑都有效,我们可以从 EditorGUILayout 为 int 字段赋值,并将那里的值正确保存到数组中。所有这些逻辑都有效。
但是:某些东西正在改变行为参数数组。我相信那是 ScriptableObject。下一帧,Parameters 不再为空(显然),我们尝试访问索引位置 0。这导致索引超出范围异常,因为某些东西将 Parameters 更改为新字符串 [0]。他们没有将它设置为 null,而是将它的长度设置为 0。
为什么?什么可能触发这种逻辑?如果我将 Parameters 设置为 属性 并在 set-method 中设置一个断点,除了我上面自己的代码之外没有其他人调用它,但数组的长度仍然为 0。有什么想法吗?
Unity 的序列化程序确实 auto-initializes 任何可序列化类型的字段,例如列表或数组或 string
等! -> 它们永远不会 null
,最坏的情况下它们将是空的。
如果你只是想为你的字段设置默认值,你可以简单地在你的 class 中分配它们,不需要抽屉:
[Serializable]
public class Behaviour
{
public string Methodname = "ExampleMethod";
public string[] Parameters = new string[1] { "i0" };
}
通常不要通过编辑器脚本中的目标直接更改值!
这会让你很头疼,因为将更改的对象标记为脏,serialize/save 值正确持久化,处理 undo/redo 等
总是宁愿通过 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";
但如前所述,这只是一个示例,您真的只想将默认值放在 class 中,而不是抽屉中。
同时放
EditorGUI.BeginProperty(position, label, property);
...
EditorGUI.EndProperty();
您的 属性 抽屉内容对于脏状态处理至关重要!参见 Property Drawers
我有一个名为 Behavior 的简单 class,它保存一个字符串和一个字符串数组。它看起来像这样:
[System.Serializable]
public class Behaviour {
public string Methodname;
public string[] Parameters;
}
如您所料,这个class是为了保存一个方法,以便将方法插入到unity inspector中。 Methodname 是插入的方法的名称,而 Parameters 是以静态实用程序 class 可以读取的方式格式化的该方法的所有参数(这些字符串被转换为对象,然后用作参数。所以 a “i25”的字符串将转换为 25 的整数参数。数组是一个字符串,因为对象 [] 无法序列化,因此无法保存。
我会剔除所有不必要的逻辑并专注于问题所在。假设我们要将单个整数保存到参数数组的第一个索引中。 Behavior 的 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)。所有这些逻辑都有效。
再次说明:这是Behaviour的PropertyDrawer。 Behaviour 是 ScriptableObject.
中的私有 [SerializedProperty]如果数组为null,则if == null 触发并将数组参数设置为1 的长度。后面的逻辑都有效,我们可以从 EditorGUILayout 为 int 字段赋值,并将那里的值正确保存到数组中。所有这些逻辑都有效。
但是:某些东西正在改变行为参数数组。我相信那是 ScriptableObject。下一帧,Parameters 不再为空(显然),我们尝试访问索引位置 0。这导致索引超出范围异常,因为某些东西将 Parameters 更改为新字符串 [0]。他们没有将它设置为 null,而是将它的长度设置为 0。
为什么?什么可能触发这种逻辑?如果我将 Parameters 设置为 属性 并在 set-method 中设置一个断点,除了我上面自己的代码之外没有其他人调用它,但数组的长度仍然为 0。有什么想法吗?
Unity 的序列化程序确实 auto-initializes 任何可序列化类型的字段,例如列表或数组或 string
等! -> 它们永远不会 null
,最坏的情况下它们将是空的。
如果你只是想为你的字段设置默认值,你可以简单地在你的 class 中分配它们,不需要抽屉:
[Serializable]
public class Behaviour
{
public string Methodname = "ExampleMethod";
public string[] Parameters = new string[1] { "i0" };
}
通常不要通过编辑器脚本中的目标直接更改值!
这会让你很头疼,因为将更改的对象标记为脏,serialize/save 值正确持久化,处理 undo/redo 等
总是宁愿通过 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";
但如前所述,这只是一个示例,您真的只想将默认值放在 class 中,而不是抽屉中。
同时放
EditorGUI.BeginProperty(position, label, property);
...
EditorGUI.EndProperty();
您的 属性 抽屉内容对于脏状态处理至关重要!参见 Property Drawers