如何在编辑器窗口中编辑和保留可序列化资产?

时间:2016-07-18 16:31:51

标签: c# unity3d unity5

我的项目中保存了一个资产,表示可序列化的脚本化对象。 对象的代码非常简单:

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(); }

}

我的问题是:我做错了什么?可能是什么问题呢?怎么解决?我是否在编辑窗口中错误地加载了资产?或者是什么? 任何帮助/想法将不胜感激。

1 个答案:

答案 0 :(得分:1)

嗯,一切都简单而复杂,同时又简单。

尽管检查员发生了视觉上的变化,但这并不意味着数据实际发生了变化。看起来一切正常,但......我觉得它是 Unity 的缺点

对于正确的工作,你应该使用一些东西:

  • GUI.changed - 如果任何控件更改了输入数据的值,则返回true。我们将把它用于变化检测。
  • Undo.RecordObject - 记录RecordObject函数之后对对象所做的任何更改。记录Undo state,允许您使用Undo系统还原更改。
  • EditorUtility.SetDirty !!!最重要的事情!!! ) - 很快:将目标对象标记为“脏”,因此需要保存。有关更多信息,请单击链接。

现在,我们需要做的就是在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()

此代码包含:

最后四个与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

但它很复杂,因为你应该对很多属性类做很多工作:FindPropertyPropertyFieldSerializedProperty

但是当你明白它是如何运作的时候 - 它变得如此容易......