您好,登錄后才能下訂單哦!
最近重構了一下我的存檔框架。我在這里對實現方法進行簡單的解析。注意這里主要演示算法,所以,效率上并不是最佳。一個游戲中,可能有成百上千個物體需要存儲,而且有幾十種類型,接下來就用一個簡單的例子來解釋。一個很簡單的例子,有一個Unit(單位)類型,有一個Inventory(背包)類型,有一個Item(道具)類型。
接下來先介紹框架中最重要的接口,ISavable,表示這個類型可以存檔
public interface ISavable{ uint Id {get; set;} Type DataType {get;} // 存檔數據類型 Type DataContainerType {get;} // 存檔數據容器類型 void Read(object data); void Write(object data); }
ISavableContainer,用來返回一組ISavable的容器:
public interface ISavableContainer{ IEnumerable<ISavable> Savables; }
IId, 具有Id的接口:
public interface IId { uint Id {get; set;} }
SaveEntity, 這是一個MonoBehaviour,將這個組件放到需要存檔的GameObject上就可以實現該GameObject的存檔了,這是最核心的類之一:
public class SaveEntity : MonoBehaviour{ public void Save(SaveDataContainer container){ foreach(ISavable savable in GetSavables()){ if(savable.DataContainerType = container.GetType()){ IId newData = Activator.CreateInstance(savable.DataType) as IId; newData.Id = savable.Id; savable.Write(newData); container.SetData(newData); } } } public void Load(SaveDataContainer container){ foreach(ISavable savable in GetSavables()){ if(savable.DataContainerType = container.GetType()){ IId data = container.GetData(savable.Id); savable.Read(data); } } } public IEnumerable<ISavable> GetSavables(){ foreach(ISavable savable in GetComponents<ISavable>()){ yield return savable; } foreach(ISavable savableContainer in GetComponents<ISavableContainer>()){ foreach(ISavable savable in savableContainer.Savables){ yield return savable; } } } }
SaveFile代表一個文件
[Serializable] public class SaveFileData{ public uint CurId; public string DataContainer; } // 代表一個存檔文件 public class SaveFile: MonoBehaviour{ // 包含實際數據的數據類 private SaveDataContainer _saveDataContainer; private uint _curId; public string Path{get;set;} public SaveDataContainer SaveDataContainer{get{return _saveDataContainer;}} private uint NextId{get{return ++_curId;}} // 得到場景里所有的SaveEntity private IEnumerable<SaveEntity> GetEntities(){ // 實現略過 } // 將場景物體中的數據存入到_saveDataContainer中 public void Save<T>() where T:SaveDataContainer, new() { // 一輪Id賦值,保證Id為0的所有ISavable都賦值一個新Id foreach(SaveEntity entity in Entities){ foreach (Savable savable in entity.GetSavables()){ if(savable.DataContainerType == typeof(T)){ if(savable.Id == 0){ savable.Id = NextId; } } } } T dataContainer = new T(); foreach(SaveEntity entity in Entities){ entity.Save(this, dataContainer); } _saveDataContainer = dataContainer; } // 將_saveDataContainer中的數據載入到場景物體中 public void Load(){ foreach(SaveEntity entity in Entities){ entity.Load(this, _saveDataContainer); } } public void LoadFromFile<T>() where T:SaveDataContainer { string json = File.ReadAllText(Path); SaveFileData data = JsonUtility.FromJson<SaveFileData>(json); _saveDataContainer = JsonUtility.FromJson<T>(data.DataContainer); _curId = data.CurId; } public void SaveToFile(){ SaveFileData data = new SaveFileData(); data.CurId = _curId; data.DataContainer = JsonUtility.ToJson(_saveDataContainer); string json = JsonUtility.ToJson(data); File.WriteAllText(Path, json); } }
SaveDataContainer:
// 這個類型存儲了實際的數據,相當于是一個數據庫 [Serializable] public class SaveDataContainer{ // 這個中存儲這實際物體的數據,需要將這個字典轉換成數組并序列化 private Dictionary<uint, IId> _data; public Dictionary<unit, IId> Data{get{return _data}} public IId GetData(uint id){ return _data[id]; } public void SetData(IId data){ _data[data.Id] = data; } }
好了,框架就講到這里,接下來實現示例代碼:
Unit:
[Serializable] public class UnitSave:IId{ [SerializeField] private uint _id; public uint PrefabId; public uint InventoryId; public int Hp; public int Level; public uint Id {get{return _id;}set{_id = value;}} } public class Unit:MonoBehaviour, ISavable{ public int Hp; public int Level; public int PrefabId; public Inventory Inventory; public uint Id{get;set;} ISavable.DataType{get{return typeof(UnitSave);}} ISavable.DataContainerType{get{return typeof(ExampleSaveDataContainer);}} ISavable.Read(object data){ UnitSave save = data as UnitSave; Hp = save.Hp; Level = save.Level; } ISavable.Write(object data){ UnitSave save = data as UnitSave; save.Hp = Hp; save.Level = Level; save.InventoryId = Inventory.Id; } }
Inventory:
[Serializable] public class InventorySave:IId{ [SerializeField] private uint _id; public uint UnitId; public uint[] Items; public uint Id{get{return _id;}set{_id = value;}} } public class Inventory:MonoBehaviour, ISavable, ISavableContainer{ public Unit Unit; public List<Item> Items; public uint Id{get;set;} ISavable.DataType{get{return typeof(InventorySave);}} ISavable.DataContainerType{get{return typeof(ExampleSaveDataContainer));}} ISavable.Read(object data){ // 空 } ISavable.Write(object data){ InventorySave save = data as InventorySave; save.UnitId = Unit.Id; save.Items = Items.Select(item => item.Id).ToArray(); } ISavableContainer.Savables{ return Items; } }
Item:
[Serializable] public ItemSave: IId{ [SerializeField] private uint _id; public uint PrefabId; public int Count; public uint Id{get{return _id;}set{_id = value;}} } // 道具并不是繼承自MonoBehaviour的,是一個普通的類 public class Item:ISavable{ // 道具源數據所在Prefab,用于重新創建道具 public uint PrefabId; public int Count; public uint Id {get;set;} public uint Id{get;set;} ISavable.DataType{get{return typeof(ItemSave);}} ISavable.DataContainerType{get{return typeof(ExampleSaveDataContainer));}} ISavable.Read(object data){ ItemSave save = data as ItemSave; Count = save.Count; } ISavable.Write(object data){ ItemSave save = data as ItemSave; save.PrefabId = PrefabId; save.Count = Count; } }
ExampleSaveDataContainer:
[Serializable] public class ExampleSaveDataContainer: SaveDataContainer, ISerializationCallbackReceiver { public UnitSave[] Units; public ItemSave[] Items; public InventorySave[] Inventories; public void OnBeforeSerialize(){ // 將Data字典中的數據復制到數組中,實現略過 } public void OnAfterDeserialize(){ // 將數組中的數據賦值到Data字典中,實現略過 } }
ExampleGame:
public class ExampleGame:MonoBehaviour{ public void LoadGame(SaveFile file){ // 從文件中讀入數據到SaveDataContainer file.LoadFromFile<ExampleSaveDataContainer>(); SaveDataContainer dataContainer = file.SaveDataContainer; // 創建所有物體并賦值相應Id Unit[] units = dataContainer.Units.Select(u=>CreateUnit(u)); Item[] items = dataContainer.Items.Select(item=>CreateItem(item)); // 將道具放入相應的道具欄中 foreach(Unit unit in units){ uint inventoryId = unit.Inventory.Id; InventorySave inventorySave = dataContainer.GetData(inventoryId); foreach(Item item in items.Where(i=>inventorySave.Items.Contains(i.Id))){ unit.Inventory.Put(item); } } // 調用Load進行實際的數據載入 file.Load(); } public void SaveGame(SaveFile file){ // 相對來說,存檔的實現比載入簡單了許多 file.Save<ExampleSaveDataContainer>(); file.SaveToFile(); } public Unit CreateUnit(UnitSave save){ Unit unit = Instantiate(GetPrefab(save.PrefabId)).GetComponent<Unit>(); unit.Id = save.Id; unit.Inventory.Id = save.InventoryId; return unit; } public Item CreateItem(ItemSave save){ Item item = GetPrefab(save.PrefabId).GetComponent<ItemPrefab>().CreateItem(); item.Id = save.Id; return item; } }
使用方法:
給單位Prefab中的Unit組件和Inventory組件所在的GameObject上放SaveEntity組件即可。
思考問題:
1.擴展功能,讓SaveFile包含一個SaveDataContainer數組,這樣子可以實現包含多個數據容器(數據庫)的情況
2.對SaveFile存儲內容進行壓縮,減少存儲體積
3.SaveFile存儲到文件時進行加密,避免玩家修改存檔
4.如何避免存儲時候卡頓
存儲過程:
1.從場景中搜集數據到SaveFile中(SaveFile.Save),得到一個SaveFileData的數據
2.將SaveFileData序列化成一個json字符串
3.對字符串進行壓縮
4.對壓縮后的數據進行加密
5.將加密后的數據存儲于文件
可以發現,只要完成第1步,得到一個SaveFileData,實際上就已經完成了存檔了,接下來實際上就是一個數據轉換的過程。所以,這也給出了避免游戲卡頓的一種方法:
完成第一步之后,將后面的步驟全部都放到另一個線程里面處理。實際上,第一步的速度是相當快的。往往不會超過50ms,可以說,卡頓并不會很明顯。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。