Unity實(shí)現(xiàn)游戲存檔框架
最近重構(gòu)了一下我的存檔框架。我在這里對(duì)實(shí)現(xiàn)方法進(jìn)行簡(jiǎn)單的解析。注意這里主要演示算法,所以,效率上并不是最佳。一個(gè)游戲中,可能有成百上千個(gè)物體需要存儲(chǔ),而且有幾十種類(lèi)型,接下來(lái)就用一個(gè)簡(jiǎn)單的例子來(lái)解釋。一個(gè)很簡(jiǎn)單的例子,有一個(gè)Unit(單位)類(lèi)型,有一個(gè)Inventory(背包)類(lèi)型,有一個(gè)Item(道具)類(lèi)型。
接下來(lái)先介紹框架中最重要的接口,ISavable,表示這個(gè)類(lèi)型可以存檔
public interface ISavable{
uint Id {get; set;}
Type DataType {get;} // 存檔數(shù)據(jù)類(lèi)型
Type DataContainerType {get;} // 存檔數(shù)據(jù)容器類(lèi)型
void Read(object data);
void Write(object data);
}
ISavableContainer,用來(lái)返回一組ISavable的容器:
public interface ISavableContainer{
IEnumerable<ISavable> Savables;
}
IId, 具有Id的接口:
public interface IId
{
uint Id {get; set;}
}
SaveEntity, 這是一個(gè)MonoBehaviour,將這個(gè)組件放到需要存檔的GameObject上就可以實(shí)現(xiàn)該GameObject的存檔了,這是最核心的類(lèi)之一:
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代表一個(gè)文件
[Serializable]
public class SaveFileData{
public uint CurId;
public string DataContainer;
}
// 代表一個(gè)存檔文件
public class SaveFile: MonoBehaviour{
// 包含實(shí)際數(shù)據(jù)的數(shù)據(jù)類(lèi)
private SaveDataContainer _saveDataContainer;
private uint _curId;
public string Path{get;set;}
public SaveDataContainer SaveDataContainer{get{return _saveDataContainer;}}
private uint NextId{get{return ++_curId;}}
// 得到場(chǎng)景里所有的SaveEntity
private IEnumerable<SaveEntity> GetEntities(){
// 實(shí)現(xiàn)略過(guò)
}
// 將場(chǎng)景物體中的數(shù)據(jù)存入到_saveDataContainer中
public void Save<T>() where T:SaveDataContainer, new()
{
// 一輪Id賦值,保證Id為0的所有ISavable都賦值一個(gè)新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中的數(shù)據(jù)載入到場(chǎng)景物體中
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:
// 這個(gè)類(lèi)型存儲(chǔ)了實(shí)際的數(shù)據(jù),相當(dāng)于是一個(gè)數(shù)據(jù)庫(kù)
[Serializable]
public class SaveDataContainer{
// 這個(gè)中存儲(chǔ)這實(shí)際物體的數(shù)據(jù),需要將這個(gè)字典轉(zhuǎn)換成數(shù)組并序列化
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;
}
}
好了,框架就講到這里,接下來(lái)實(shí)現(xiàn)示例代碼:
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的,是一個(gè)普通的類(lèi)
public class Item:ISavable{
// 道具源數(shù)據(jù)所在Prefab,用于重新創(chuàng)建道具
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字典中的數(shù)據(jù)復(fù)制到數(shù)組中,實(shí)現(xiàn)略過(guò)
}
public void OnAfterDeserialize(){
// 將數(shù)組中的數(shù)據(jù)賦值到Data字典中,實(shí)現(xiàn)略過(guò)
}
}
ExampleGame:
public class ExampleGame:MonoBehaviour{
public void LoadGame(SaveFile file){
// 從文件中讀入數(shù)據(jù)到SaveDataContainer
file.LoadFromFile<ExampleSaveDataContainer>();
SaveDataContainer dataContainer = file.SaveDataContainer;
// 創(chuàng)建所有物體并賦值相應(yīng)Id
Unit[] units = dataContainer.Units.Select(u=>CreateUnit(u));
Item[] items = dataContainer.Items.Select(item=>CreateItem(item));
// 將道具放入相應(yīng)的道具欄中
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);
}
}
// 調(diào)用Load進(jìn)行實(shí)際的數(shù)據(jù)載入
file.Load();
}
public void SaveGame(SaveFile file){
// 相對(duì)來(lái)說(shuō),存檔的實(shí)現(xiàn)比載入簡(jiǎn)單了許多
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組件即可。
思考問(wèn)題:
1.擴(kuò)展功能,讓SaveFile包含一個(gè)SaveDataContainer數(shù)組,這樣子可以實(shí)現(xiàn)包含多個(gè)數(shù)據(jù)容器(數(shù)據(jù)庫(kù))的情況
2.對(duì)SaveFile存儲(chǔ)內(nèi)容進(jìn)行壓縮,減少存儲(chǔ)體積
3.SaveFile存儲(chǔ)到文件時(shí)進(jìn)行加密,避免玩家修改存檔
4.如何避免存儲(chǔ)時(shí)候卡頓
存儲(chǔ)過(guò)程:
1.從場(chǎng)景中搜集數(shù)據(jù)到SaveFile中(SaveFile.Save),得到一個(gè)SaveFileData的數(shù)據(jù)
2.將SaveFileData序列化成一個(gè)json字符串
3.對(duì)字符串進(jìn)行壓縮
4.對(duì)壓縮后的數(shù)據(jù)進(jìn)行加密
5.將加密后的數(shù)據(jù)存儲(chǔ)于文件
可以發(fā)現(xiàn),只要完成第1步,得到一個(gè)SaveFileData,實(shí)際上就已經(jīng)完成了存檔了,接下來(lái)實(shí)際上就是一個(gè)數(shù)據(jù)轉(zhuǎn)換的過(guò)程。所以,這也給出了避免游戲卡頓的一種方法:
完成第一步之后,將后面的步驟全部都放到另一個(gè)線程里面處理。實(shí)際上,第一步的速度是相當(dāng)快的。往往不會(huì)超過(guò)50ms,可以說(shuō),卡頓并不會(huì)很明顯。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#/VB.NET實(shí)現(xiàn)在Word文檔中添加頁(yè)眉和頁(yè)腳
頁(yè)眉位于文檔中每個(gè)頁(yè)面的頂部區(qū)域,常用于顯示文檔的附加信息;頁(yè)腳位于文檔中每個(gè)頁(yè)面的底部的區(qū)域,常用于顯示文檔的附加信息。今天這篇文章就將為大家展示如何以編程的方式在在?Word?文檔中添加頁(yè)眉和頁(yè)腳2023-03-03
C#中使用Override和New關(guān)鍵字進(jìn)行版本控制
在?C#?中,override?和?new?關(guān)鍵字用于控制類(lèi)之間的成員方法的隱藏和重寫(xiě),理解它們之間的差異和使用場(chǎng)景對(duì)于設(shè)計(jì)靈活且易于維護(hù)的代碼至關(guān)重要,在這篇博客中,我們將詳細(xì)探討這兩個(gè)關(guān)鍵字的用法,并通過(guò)示例來(lái)說(shuō)明它們的實(shí)際應(yīng)用,需要的朋友可以參考下2024-10-10
C#批量插入數(shù)據(jù)到Sqlserver中的三種方式
這篇文章主要為大家詳細(xì)介紹了C#批量插入數(shù)據(jù)到Sqlserver中的三種方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
c# 配置文件App.config操作類(lèi)庫(kù)的方法
下面小編就為大家?guī)?lái)一篇c# 配置文件App.config操作類(lèi)庫(kù)的方法。小編覺(jué)的挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12

