第三章 編輯器下的資料保存
我們在擴展編輯器的時候,經常需要把一些資料保存下來,比如跟編輯器本身相關的一些設定引數或者跟游戲有關的一些引數,以便下次使用,
在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
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/285777.html
標籤:其他
上一篇:Unity3D OpenVR 虛擬現實 保齡球打磚塊游戲開發
下一篇:fbx查看軟體
