第三章 编辑器下的数据保存
我们在扩展编辑器的时候,经常需要把一些数据保存下来,比如跟编辑器本身相关的一些设置参数或者跟游戏有关的一些参数,以便下次使用。
在Unity中保存数据的主要方法有三种。
3.1 使用EditorPrefs保存数据 (以明文保存)
这是一种可以在项目之间共享的数据保存方式,适用于跨Unity编辑器共享数据而不受项目约束。
影响范围
保存的值可能会受到Unity大版本的影响。
比如在Unity 4.x中保存的值只能在Unity 4.x中使用,Unity 5.x也仅在Unity 5.x中可用。

注意:1、它不需要考虑运行时的效率问题,所以没有采用PlayerPrefs的优化方式,而是直接保存了。EditorPrefs中比PlayerPrefs多了一个Bool类型的键值对。
2、所有通过EditorPrefs保存的值均以明文形式保存,所以切勿保存重要信息,例如密码。
3、在Unity编辑器环境中,谨慎调用EditorPrefs.DeleteAll()。如果这样做下次打开Unity会发现以前保存的打开项目,全部消失了。
猜测Unity自身也是使用EditorPrefs保存了所有编辑器默认的数据,像以前打开的工程、退出时保存的场景、设置的编辑环境等等都保存在EditorPrefs中,估计还会有其他编辑环境的数据,只是没有发现。因此建议不要轻易调用
存储EditorPrefs的位置
平台位置 |
Windows(Unity4.x)HKEY_CURRENT_USER \软件\ Unity技术\ UnityEditor 4.x |
Windows(Unity5.x)HKEY_CURRENT_USER \软件\ Unity技术\ UnityEditor 5.x |
Mac OS X(Unity4.x)?/库/首选项/ com.unity3d.UnityEditor4.x.plist |
Mac OS X(Unity5.x)?/库/首选项/ com.unity3d.UnityEditor5.x.plist |
Unity每个主要版本的EditorPrefs都会分别保存。特别是Windows在注册表中存储的值。如果仅使用EditorPrefs是没有问题的,但是由于也可以直接操作注册表,因此可能会在此过程中进行错误的设置,需要足够小心。

上图: 在Xcode中打开的com.unity3d.UnityEditor5.x.plist
- 1.SetInt(); 保存整型数据;
- 2.GetInt(); 读取整形数据;
- 3.SetFloat(); 保存浮点型数据;
- 4.GetFloat(); 读取浮点型数据;
- 5.SetString(); 保存字符串型数据;
- 6.GetString(); 读取字符串型数据;
- EditorPrefs.DeleteKey (key : string) 删除指定数据;
- EditorPrefs.DeleteAll() 删除全部键 ;
- EditorPrefs.HasKey (key : string) 判断数据是否存在;
3.2 EditorUserSettings.Set / GetConfigValue (以二进制形式保存)
此方法存储的值是以二进制方式保存的,我们无法看到明文,相当于简单的加了密,所以它适用于存储个人信息,例如密码。
范围和存放位置
使用此API保存的数据一般只会在本项目中使用。由于数据是存储在Library/EditorUserSettings.asset
中的,因此除非与他人共享“Library”文件夹,否则不会与他人共享信息。
应用场景
有时候我们在使用工具的时候,可能需要电子邮件地址和密码才能登录,其中之一是Oauth访问令牌。
EditorUserSettings.asset以二进制格式保存,因此不容易看到其内容。但是,因为没有采用任何加密的算法,我们还是可以使用Unity提供的binary2text将二进制文件转换为文本格式并进行查看。
如何使用
- using UnityEditor;
- using UnityEngine;
- public class EditorTest: EditorWindow
- {
- [InitializeOnLoadMethod]
- static void SaveConfig()
- {
- EditorUserSettings.SetConfigValue("Password", "xxxxx");
- }
- }
3.3 脚本化对象 ScriptableObject
这是一种应用广泛的数据存储方法,Unity里面的很多资源都是采用这是方式存储的。如果我们需要在项目中共享设置或要存储大量数据,就可以使用此方法。
影响范围
ScriptableObject是用Unity项目中存储数据的主要格式。我们可以随时通过将数据另存为Unity项目中的资源来保存数据,并且可以从脚本中加载该数据。
- using UnityEngine;
- [CreateAssetMenu(fileName = "EditorTest", menuName = "TestSO", order = 1)]
- public class EditorTest: ScriptableObject
- {
- [Range(0, 10)]
- public int number = 3;
- public bool toggle = false;
- public string[] texts = new string[5];
- }

可以在监视器中编辑值
应用场景
它可以用作通过编辑器扩展创建的资源数据或者配置文件的数据库,以及在创建后用作游戏数据。
保存位置
可以将其保存在Assets文件夹下的任何地方。如果只是编辑器扩展的ScriptableObject,我们最好把它放到“Editor”文件夹下,跟我们项目中的正式资源区分开来。
3.4 JSON - JsonUtility
Json(JavaScript Object Notation, JS 对象表示法)是一种轻量级的数据交换格式。通常情况下,它被用来从Web或服务器检索数据的数据格式,被广泛使用。
从Unity5.3开始,已正式添加JsonUtility类,并已正式支持JSON。
但是,尽管它比我们通常使用的JSON库要快,但它的性能并不高,并且使用受到限制。
将对象转换为JSON的条件与Unity序列化中的条件相同:
1:[Serializable] Serializable是.Net自带的序列化,可以对class、struct、enum、delegate进行序列化,但是无法对属性进行序列化。
有时候我们会自定义一些单独的class/struct, 由于这些类并没有从 MonoBehavior 派生所以默认并不被Unity3D识别为可以序列化的结构,自然也就不会在Inspector中显示。
我们可以通过添加 [System.Serializable]这个属性使Unity3D检测并注册这些类为可序列化的类型。
2:[SerializeField] public变量是默认被视为可以被序列化的,所以public声明的变量在Inspector面板中是可见的。
SerializeField允许我们强制unity去序列化一个私有域,这是一个unity内部的序列化功能,有时候我们需要序列化一个private或者protected的属性,这个时候可以使用[SerializeField]这个属性。
3:SerializedObject ScriptableObject类型经常用于存储一些unity3d本身不可以打包的一些object,比如字符串,类对象等。
用这个类型的子类型,则可以用BuildPipeline打包成assetbundle包供后续使用,非常方便,具体请参考后续专门的章节 脚本化对象ScriptableObject。
4:[System.NonSerialized] 有时候我们需要定义一些public变量方便操作,但是又不希望这些变量保留,这个时候就可以使用[System.NonSerialized]来完成这个操作。
注意:默认情况下,protected, private, internal变量将不会被serialize,如果变量加入了readonly, const, static等修饰符,无论他的serialize设置如何,都将不会进行serialize。
使用Unity的序列化程序意味着该序列化程序无法处理的任何内容都不能序列化成JSON格式。
1、字典Dictionary无法序列化
2、无法序列化对象数组,如object[]、List<object>
3、即使按原样传递数组对象,也无法序列化(无法完成JsonUtility.ToJson(List<T>))
使用方式
JsonUtility使用起来很简单,通过JsonUtility.ToJson和JsonUtility.FromJson来分别进行序列化和反序列化。
- using System;
- using UnityEditor;
- using UnityEngine;
- [Serializable]
- public class EditorTest
- {
- public int m_ID = 1;
- [SerializeField]
- private string m_Name = "HeiHei";
- [SerializeField]
- internal int m_Number = 10;
- }
- Debug.Log(JsonUtility.ToJson(new EditorTest(), true));
EditorJsonUtility
我们无法在JsonUtility中将UnityEngine.Object转换为Json格式(虽然绝大多数对象确实无法进行序列化,但包括ScriptableObject在内的某些对象还是可以的)。
如果我们一定要序列化这种数据,可以使用特用于编辑器的EditorJsonUtility将UnityEngine.Object转换为Json。但是,EditorJsonUtility并不支持数组,所以最终的Json格式是通过串联字符串创建的。
- public static string ToJson(string key, UnityEngine.Object[] objs)
- {
- var json = objs.Select(obj => EditorJsonUtility.ToJson(obj)).ToArray();
- var values = string.Join(",", json);
- return string.Format("{\"{0}\":{1}]}", key, values);
- }
数组处理
许多Json库也允许序列化数组,比如Newtonsoft.Json。但是,以相同方式使用Unity的JsonUtility并不会序列化。如果真的想序列化数组,则需要进行一番设计。
可序列化类的任何字段变量都可以被序列化
- using System;
- using System.Linq;
- using UnityEditor;
- using UnityEngine;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- [Serializable]
- public class SerializableList<T> : Collection<T>, ISerializationCallbackReceiver
- {
- [SerializeField]
- List<T> items;
- public void OnBeforeSerialize()
- {
- items = (List<T>)Items;
- }
- public void OnAfterDeserialize()
- {
- Clear();
- foreach (var item in items)
- {
- Add(item);
- }
- }
- }
使用JsonUtility对其进行序列化
- var serializedList = new SerializableList<EditorTest>
- {
- new EditorTest(),
- new EditorTest()
- };
- Debug.Log(JsonUtility.ToJson(serializedList));
这里要着重提到的是ISerializationCallbackReceiver。在使用JsonUtility转换为Json时,将调用ISerializationCallbackReceiver.OnBeforeSerialize/OnAfterDeserialize。
有时候我们希望在序列化后显示成数组样式而不是Json格式(这意味着我们不需要"items"键),那么我们就可以在上面的回调里面处理一下,
在SerializableList类中创建一个ToJson方法,以便可以自定义字符串。
- public string ToJson()
- {
- var result = "[]";
- var json = JsonUtility.ToJson(this);
- var regex = new Regex("^{\"items\":(?<array>.*)}$");
- var match = regex.Match(json);
- if (match.Success)
- result = match.Groups["array"].Value;
- return result;
- }
但是,如果使用此方法,则无法进行反序列化,因此我将自己再处理下FromJson。
- public static SerializableList<T> FromJson(string arrayString)
- {
- var json = "{\"items\":" + arrayString + "}";
- return JsonUtility.FromJson<SerializableList<T>>(json);
- }
现在就可以进行反序列化了
- var serializedList = new SerializableList<EditorTest>
- {
- new EditorTest(),
- new EditorTest()
- };
- var json = serializedList.ToJson();
- var serializableList = SerializableList<EditorTest>.FromJson(json);
- Debug.Log(serializableList.Count == 2);
-
SerializableList.cs
- using System;
- using System.Linq;
- using UnityEditor;
- using UnityEngine;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Text.RegularExpressions;
- [Serializable]
- public class SerializableList<T> : Collection<T>, ISerializationCallbackReceiver
- {
- [SerializeField]
- List<T> items;
- public void OnBeforeSerialize()
- {
- items = (List<T>)Items;
- }
- public void OnAfterDeserialize()
- {
- Clear();
- foreach (var item in items)
- Add(item);
- }
- public string ToJson(bool prettyPrint = false)
- {
- var result = "[]";
- var json = JsonUtility.ToJson(this, prettyPrint);
- var pattern = prettyPrint ? "^\\{\n\\s+\"items\":\\s(?<array>.*)\n\\s+\\]\n}$" : "^{\"items\":(?<array>.*)}$";
- var regex = new Regex(pattern, RegexOptions.Singleline);
- var match = regex.Match(json);
- if (match.Success)
- {
- result = match.Groups["array"].Value;
- if (prettyPrint)
- result += "\n]";
- }
- return result;
- }
- public static SerializableList<T> FromJson(string arrayString)
- {
- var json = "{\"items\":" + arrayString + "}";
- return JsonUtility.FromJson<SerializableList<T>>(json);
- }
- }
字典处理
在JsonUtility中序列化Dictionary是一件几乎不可能的事情,我们无法像其他Json库那样进行序列化,因此必须自己编写几乎所有功能。所以这时候完全没有必要使用JsonUtility,使用MiniJSON 、Newtonsoft.Json会更加方便快捷。
参考文章:Unity编辑器拓展手册日文版 http://49.233.81.186/guicreation.html