如何在编辑器中编辑和持久化可序列化资产Window?
How to edit and persist serializable Assets in the Editor Window?
我的项目中保存了一个资产,它代表一个可序列化的脚本化对象。
对象的代码很简单:
using UnityEngine;
using System.Collections;
public class TestScriptable : ScriptableObject {
public float gravity = .3f;
public float plinkingDelay = .1f;
public float storedExecutionDelay = .3f;
}
在检查器中更改此对象的值没有问题,并且更改在退出 → 进入 Unity.
后仍然存在并继续存在
我正在尝试模仿 Editor Window
中的检查员行为。但是我在 Editor Window
中所做的任何更改虽然反映在 Inspector
中,但不会持续存在。
这是我在 Editor
文件夹中的两个脚本:
第一个(辅助)-这个脚本用按钮替换检查器字段(见上图),它调用我的自定义EditorWindow
。
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(TestScriptable))]
public class TestScriptableEditor : Editor {
public override void OnInspectorGUI() {
if (GUILayout.Button("Open TestScriptableEditor"))
TestScriptableEditorWindow.Init();
}
}
第二个(我的问题)- 脚本,我试图在其中更改我的资产值:
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class TestScriptableEditorWindow : EditorWindow {
public static TestScriptableEditorWindow testScriptableEditorWindow;
private TestScriptable testScriptable;
[MenuItem("Window/TestTaskIceCat/TestScriptableEditor")]
public static void Init() {
// initialize window, show it, set the properties
testScriptableEditorWindow = GetWindow<TestScriptableEditorWindow>(false, "TestScriptableEditorWindow", true);
testScriptableEditorWindow.Show();
testScriptableEditorWindow.Populate();
}
// initialization of my troubled asset
void Populate() {
Object[] selection = Selection.GetFiltered(typeof(TestScriptable), SelectionMode.Assets);
if (selection.Length > 0) {
if (selection[0] == null)
return;
testScriptable = (TestScriptable)selection[0];
}
}
public void OnGUI() {
if (testScriptable == null) {
/* certain actions if my asset is null */
return;
}
// Here is my tries to change values
testScriptable.gravity = EditorGUILayout.FloatField("Gravity:", testScriptable.gravity);
testScriptable.plinkingDelay = EditorGUILayout.FloatField("Plinking Delay:", testScriptable.plinkingDelay);
testScriptable.storedExecutionDelay = EditorGUILayout.FloatField("Stored Execution Delay:", testScriptable.storedExecutionDelay);
// End of the region of change values
}
void OnSelectionChange() { Populate(); Repaint(); }
void OnEnable() { Populate(); }
void OnFocus() { Populate(); }
}
我的问题是:我做错了什么?可能是什么问题呢?如何解决?我是否在编辑器 Window 中错误地加载了资产?或者是什么?
任何 help/ideas 将不胜感激。
好吧,一切都简单又复杂,同时又简单。
尽管检查器中的视觉变化 - 这并不意味着数据实际上已更改。看起来一切正常,但是......在我看来这是 Unity
的缺点
为了正确工作,你应该使用一些东西:
- GUI.changed - returns 如果任何控件更改了输入数据的值,则为真。我们将用它来检测变化。
- Undo.RecordObject - 记录在 RecordObject 函数之后对对象所做的任何更改。
Undo state
被记录下来,允许您使用 Undo
系统恢复更改。
- EditorUtility.SetDirty(!!!最重要的事情!!!)- 很快:将目标对象标记为 "dirty",因此需要保存。有关详细信息 - 单击 link.
现在,我们需要做的就是在OnGUI()
方法的底部写一些代码;
if (GUI.changed) {
// writing changes of the testScriptable into Undo
Undo.RecordObject(testScriptable, "Test Scriptable Editor Modify");
// mark the testScriptable object as "dirty" and save it
EditorUtility.SetDirty(testScriptable);
}
即你的代码将是这样的:
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class TestScriptableEditorWindow : EditorWindow {
public static TestScriptableEditorWindow testScriptableEditorWindow;
private TestScriptable testScriptable;
[MenuItem("Window/TestTaskIceCat/TestScriptableEditor")]
public static void Init() {
// initialize window, show it, set the properties
testScriptableEditorWindow = GetWindow<TestScriptableEditorWindow>(false, "TestScriptableEditorWindow", true);
testScriptableEditorWindow.Show();
testScriptableEditorWindow.Populate();
}
// initialization of troubled asset
void Populate() {
Object[] selection = Selection.GetFiltered(typeof(TestScriptable), SelectionMode.Assets);
if (selection.Length > 0) {
if (selection[0] == null)
return;
testScriptable = (TestScriptable)selection[0];
}
}
public void OnGUI() {
if (testScriptable == null) {
/* certain actions if my asset is null */
return;
}
testScriptable.gravity = EditorGUILayout.FloatField("Gravity:", testScriptable.gravity);
testScriptable.plinkingDelay = EditorGUILayout.FloatField("Plinking Delay:", testScriptable.plinkingDelay);
testScriptable.storedExecutionDelay = EditorGUILayout.FloatField("Stored Execution Delay:", testScriptable.storedExecutionDelay);
// Magic of the data saving
if (GUI.changed) {
// writing changes of the testScriptable into Undo
Undo.RecordObject(testScriptable, "Test Scriptable Editor Modify");
// mark the testScriptable object as "dirty" and save it
EditorUtility.SetDirty(testScriptable);
}
}
void OnSelectionChange() { Populate(); Repaint(); }
void OnEnable() { Populate(); }
void OnFocus() { Populate(); }
}
就是这样。简单易行。
现在是故事的复杂部分...
SetDirty
- 当然不错。但此功能将在 Unity > 5.3 的版本中弃用。而且在某些版本中它将被删除。什么时候?我不知道。
除了使用 SetDirty
,您还可以采用另一种方式:
您应该在两次调用之间执行的自定义编辑器或 EditorWindow 中的所有操作:
serializedObject.Update()
// Here is some of your code
serializedObject.ApplyModifiedProperties()
此代码包含:
- serializedObject.Update() - 更新序列化对象的表示
- serializedObject.ApplyModifiedProperties() - 应用 属性 修改。
serializedObject - 获取序列化对象的访问权限并获取其属性。 SerializedObject配合使用:
SerializedProperty - 从 serializedObject
获取属性。所有数据都将是 SerializedProperty 类型,例如
SerializedProperty myGravity = serializedObject.FindProperty("gravity");
SerializedProperty myPlinkingDelay = serializedObject.FindProperty("plinkingDelay");
...
etc.
SerializedObject.FindProperty - 按名称查找序列化 属性。
- EditorGUILayout.PropertyField - 为
SerializedProperty
. 创建一个字段
最后四个跟SetDirty
一样:他们会把修改后的对象(or/and场景)标记为"dirty"并为你创建Undo states
所以,知道这一点,我们可以得到这样的东西:
using UnityEngine;
using UnityEditor;
public class TestScriptableEditorWindow : EditorWindow {
public static TestScriptableEditorWindow testScriptableEditorWindow;
private TestScriptable testScriptable;
// declaring our serializable object, that we are working on
private SerializedObject serializedObj;
[MenuItem("Window/TestTaskIceCat/TestScriptableEditor")]
public static void Init() {
testScriptableEditorWindow = GetWindow<TestScriptableEditorWindow>(false, "TestScriptableEditorWindow", true);
testScriptableEditorWindow.Show();
testScriptableEditorWindow.Populate();
}
// initialization of troubled asset
void Populate() {
Object[] selection = Selection.GetFiltered(typeof(TestScriptable), SelectionMode.Assets);
if (selection.Length > 0) {
if (selection[0] == null)
return;
testScriptable = (TestScriptable)selection[0];
// initialization of the serializedObj, that we are working on
serializedObj = new SerializedObject(testScriptable);
}
}
// our manipulation
public void OnGUI() {
if (testScriptable == null) {
/* certain actions if my asset is null */
return;
}
// Starting our manipulation
// We're doing this before property rendering
serializedObj.Update();
// Gets the property of our asset and скуфеу a field with its value
EditorGUILayout.PropertyField(serializedObj.FindProperty("gravity"), new GUIContent("Gravity"), true);
EditorGUILayout.PropertyField(serializedObj.FindProperty("plinkingDelay"), new GUIContent("Plinking Delay"), true);
EditorGUILayout.PropertyField(serializedObj.FindProperty("storedExecutionDelay"), new GUIContent("Stored Execution Delay"), true);
// Apply changes
serializedObj.ApplyModifiedProperties();
}
void OnSelectionChange() { Populate(); Repaint(); }
void OnEnable() { Populate(); }
void OnFocus() { Populate(); }
}
所以,这很简单,因为你应该只使用
Update
→ 动作 → ApplyModifiedProperties
。
但是它很复杂,因为你应该用一堆 属性 类 做很多工作:FindProperty
、PropertyField
和 SerializedProperty
。
但是当您了解它的工作原理时 - 它变得如此简单...
我的项目中保存了一个资产,它代表一个可序列化的脚本化对象。 对象的代码很简单:
using UnityEngine;
using System.Collections;
public class TestScriptable : ScriptableObject {
public float gravity = .3f;
public float plinkingDelay = .1f;
public float storedExecutionDelay = .3f;
}
在检查器中更改此对象的值没有问题,并且更改在退出 → 进入 Unity.
后仍然存在并继续存在我正在尝试模仿 Editor Window
中的检查员行为。但是我在 Editor Window
中所做的任何更改虽然反映在 Inspector
中,但不会持续存在。
这是我在 Editor
文件夹中的两个脚本:
第一个(辅助)-这个脚本用按钮替换检查器字段(见上图),它调用我的自定义EditorWindow
。
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(TestScriptable))]
public class TestScriptableEditor : Editor {
public override void OnInspectorGUI() {
if (GUILayout.Button("Open TestScriptableEditor"))
TestScriptableEditorWindow.Init();
}
}
第二个(我的问题)- 脚本,我试图在其中更改我的资产值:
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class TestScriptableEditorWindow : EditorWindow {
public static TestScriptableEditorWindow testScriptableEditorWindow;
private TestScriptable testScriptable;
[MenuItem("Window/TestTaskIceCat/TestScriptableEditor")]
public static void Init() {
// initialize window, show it, set the properties
testScriptableEditorWindow = GetWindow<TestScriptableEditorWindow>(false, "TestScriptableEditorWindow", true);
testScriptableEditorWindow.Show();
testScriptableEditorWindow.Populate();
}
// initialization of my troubled asset
void Populate() {
Object[] selection = Selection.GetFiltered(typeof(TestScriptable), SelectionMode.Assets);
if (selection.Length > 0) {
if (selection[0] == null)
return;
testScriptable = (TestScriptable)selection[0];
}
}
public void OnGUI() {
if (testScriptable == null) {
/* certain actions if my asset is null */
return;
}
// Here is my tries to change values
testScriptable.gravity = EditorGUILayout.FloatField("Gravity:", testScriptable.gravity);
testScriptable.plinkingDelay = EditorGUILayout.FloatField("Plinking Delay:", testScriptable.plinkingDelay);
testScriptable.storedExecutionDelay = EditorGUILayout.FloatField("Stored Execution Delay:", testScriptable.storedExecutionDelay);
// End of the region of change values
}
void OnSelectionChange() { Populate(); Repaint(); }
void OnEnable() { Populate(); }
void OnFocus() { Populate(); }
}
我的问题是:我做错了什么?可能是什么问题呢?如何解决?我是否在编辑器 Window 中错误地加载了资产?或者是什么? 任何 help/ideas 将不胜感激。
好吧,一切都简单又复杂,同时又简单。
尽管检查器中的视觉变化 - 这并不意味着数据实际上已更改。看起来一切正常,但是......在我看来这是 Unity
的缺点为了正确工作,你应该使用一些东西:
- GUI.changed - returns 如果任何控件更改了输入数据的值,则为真。我们将用它来检测变化。
- Undo.RecordObject - 记录在 RecordObject 函数之后对对象所做的任何更改。
Undo state
被记录下来,允许您使用Undo
系统恢复更改。 - EditorUtility.SetDirty(!!!最重要的事情!!!)- 很快:将目标对象标记为 "dirty",因此需要保存。有关详细信息 - 单击 link.
现在,我们需要做的就是在OnGUI()
方法的底部写一些代码;
if (GUI.changed) {
// writing changes of the testScriptable into Undo
Undo.RecordObject(testScriptable, "Test Scriptable Editor Modify");
// mark the testScriptable object as "dirty" and save it
EditorUtility.SetDirty(testScriptable);
}
即你的代码将是这样的:
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class TestScriptableEditorWindow : EditorWindow {
public static TestScriptableEditorWindow testScriptableEditorWindow;
private TestScriptable testScriptable;
[MenuItem("Window/TestTaskIceCat/TestScriptableEditor")]
public static void Init() {
// initialize window, show it, set the properties
testScriptableEditorWindow = GetWindow<TestScriptableEditorWindow>(false, "TestScriptableEditorWindow", true);
testScriptableEditorWindow.Show();
testScriptableEditorWindow.Populate();
}
// initialization of troubled asset
void Populate() {
Object[] selection = Selection.GetFiltered(typeof(TestScriptable), SelectionMode.Assets);
if (selection.Length > 0) {
if (selection[0] == null)
return;
testScriptable = (TestScriptable)selection[0];
}
}
public void OnGUI() {
if (testScriptable == null) {
/* certain actions if my asset is null */
return;
}
testScriptable.gravity = EditorGUILayout.FloatField("Gravity:", testScriptable.gravity);
testScriptable.plinkingDelay = EditorGUILayout.FloatField("Plinking Delay:", testScriptable.plinkingDelay);
testScriptable.storedExecutionDelay = EditorGUILayout.FloatField("Stored Execution Delay:", testScriptable.storedExecutionDelay);
// Magic of the data saving
if (GUI.changed) {
// writing changes of the testScriptable into Undo
Undo.RecordObject(testScriptable, "Test Scriptable Editor Modify");
// mark the testScriptable object as "dirty" and save it
EditorUtility.SetDirty(testScriptable);
}
}
void OnSelectionChange() { Populate(); Repaint(); }
void OnEnable() { Populate(); }
void OnFocus() { Populate(); }
}
就是这样。简单易行。
现在是故事的复杂部分...
SetDirty
- 当然不错。但此功能将在 Unity > 5.3 的版本中弃用。而且在某些版本中它将被删除。什么时候?我不知道。
除了使用 SetDirty
,您还可以采用另一种方式:
您应该在两次调用之间执行的自定义编辑器或 EditorWindow 中的所有操作:
serializedObject.Update()
// Here is some of your code
serializedObject.ApplyModifiedProperties()
此代码包含:
- serializedObject.Update() - 更新序列化对象的表示
- serializedObject.ApplyModifiedProperties() - 应用 属性 修改。
serializedObject - 获取序列化对象的访问权限并获取其属性。 SerializedObject配合使用:
SerializedProperty - 从
serializedObject
获取属性。所有数据都将是 SerializedProperty 类型,例如SerializedProperty myGravity = serializedObject.FindProperty("gravity"); SerializedProperty myPlinkingDelay = serializedObject.FindProperty("plinkingDelay"); ... etc.
SerializedObject.FindProperty - 按名称查找序列化 属性。
- EditorGUILayout.PropertyField - 为
SerializedProperty
. 创建一个字段
最后四个跟SetDirty
一样:他们会把修改后的对象(or/and场景)标记为"dirty"并为你创建Undo states
所以,知道这一点,我们可以得到这样的东西:
using UnityEngine;
using UnityEditor;
public class TestScriptableEditorWindow : EditorWindow {
public static TestScriptableEditorWindow testScriptableEditorWindow;
private TestScriptable testScriptable;
// declaring our serializable object, that we are working on
private SerializedObject serializedObj;
[MenuItem("Window/TestTaskIceCat/TestScriptableEditor")]
public static void Init() {
testScriptableEditorWindow = GetWindow<TestScriptableEditorWindow>(false, "TestScriptableEditorWindow", true);
testScriptableEditorWindow.Show();
testScriptableEditorWindow.Populate();
}
// initialization of troubled asset
void Populate() {
Object[] selection = Selection.GetFiltered(typeof(TestScriptable), SelectionMode.Assets);
if (selection.Length > 0) {
if (selection[0] == null)
return;
testScriptable = (TestScriptable)selection[0];
// initialization of the serializedObj, that we are working on
serializedObj = new SerializedObject(testScriptable);
}
}
// our manipulation
public void OnGUI() {
if (testScriptable == null) {
/* certain actions if my asset is null */
return;
}
// Starting our manipulation
// We're doing this before property rendering
serializedObj.Update();
// Gets the property of our asset and скуфеу a field with its value
EditorGUILayout.PropertyField(serializedObj.FindProperty("gravity"), new GUIContent("Gravity"), true);
EditorGUILayout.PropertyField(serializedObj.FindProperty("plinkingDelay"), new GUIContent("Plinking Delay"), true);
EditorGUILayout.PropertyField(serializedObj.FindProperty("storedExecutionDelay"), new GUIContent("Stored Execution Delay"), true);
// Apply changes
serializedObj.ApplyModifiedProperties();
}
void OnSelectionChange() { Populate(); Repaint(); }
void OnEnable() { Populate(); }
void OnFocus() { Populate(); }
}
所以,这很简单,因为你应该只使用
Update
→ 动作 → ApplyModifiedProperties
。
但是它很复杂,因为你应该用一堆 属性 类 做很多工作:FindProperty
、PropertyField
和 SerializedProperty
。
但是当您了解它的工作原理时 - 它变得如此简单...