我的项目中保存了一个资产,表示可序列化的脚本化对象。 对象的代码非常简单:
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(); }
}
我的问题是:我做错了什么?可能是什么问题呢?怎么解决?我是否在编辑窗口中错误地加载了资产?或者是什么? 任何帮助/想法将不胜感激。
答案 0 :(得分:1)
嗯,一切都简单而复杂,同时又简单。
尽管检查员发生了视觉上的变化,但这并不意味着数据实际发生了变化。看起来一切正常,但......我觉得它是 Unity 的缺点
对于正确的工作,你应该使用一些东西:
Undo state
,允许您使用Undo
系统还原更改。 现在,我们需要做的就是在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&gt;的版本中弃用。 5.3。而且在某些版本中它将被删除。什么时候?我不知道。
而不是使用SetDirty
,你可以采取另一种方式:
您应该在两次调用之间执行自定义编辑器或EditorWindow中的所有操作:
serializedObject.Update()
// Here is some of your code
serializedObject.ApplyModifiedProperties()
此代码包含:
serializedObject - 获取对序列化对象的访问权并获取其属性。 SerializedObject与以下内容结合使用:
SerializedProperty - 从serializedObject
获取属性。所有数据都是SerializedProperty类型,例如
SerializedProperty myGravity = serializedObject.FindProperty("gravity");
SerializedProperty myPlinkingDelay = serializedObject.FindProperty("plinkingDelay");
...
etc.
SerializedObject.FindProperty - 按名称查找序列化的属性。
SerializedProperty
创建一个字段。最后四个与SetDirty
类似:他们会将修改后的对象(或/和场景)标记为“脏”并为您创建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
。
但是当你明白它是如何运作的时候 - 它变得如此容易......