Unity实现游戏存档框架
最近重构了一下我的存档框架。我在这里对实现方法进行简单的解析。注意这里主要演示算法,所以,效率上并不是最佳。一个游戏中,可能有成百上千个物体需要存储,而且有几十种类型,接下来就用一个简单的例子来解释。一个很简单的例子,有一个Unit(单位)类型,有一个Inventory(背包)类型,有一个Item(道具)类型。
接下来先介绍框架中最重要的接口,ISavable,表示这个类型可以存档
publicinterfaceISavable{ uintId{get;set;} TypeDataType{get;}//存档数据类型 TypeDataContainerType{get;}//存档数据容器类型 voidRead(objectdata); voidWrite(objectdata); }
ISavableContainer,用来返回一组ISavable的容器:
publicinterfaceISavableContainer{ IEnumerableSavables; }
IId,具有Id的接口:
publicinterfaceIId { uintId{get;set;} }
SaveEntity,这是一个MonoBehaviour,将这个组件放到需要存档的GameObject上就可以实现该GameObject的存档了,这是最核心的类之一:
publicclassSaveEntity:MonoBehaviour{ publicvoidSave(SaveDataContainercontainer){ foreach(ISavablesavableinGetSavables()){ if(savable.DataContainerType=container.GetType()){ IIdnewData=Activator.CreateInstance(savable.DataType)asIId; newData.Id=savable.Id; savable.Write(newData); container.SetData(newData); } } } publicvoidLoad(SaveDataContainercontainer){ foreach(ISavablesavableinGetSavables()){ if(savable.DataContainerType=container.GetType()){ IIddata=container.GetData(savable.Id); savable.Read(data); } } } publicIEnumerableGetSavables(){ foreach(ISavablesavableinGetComponents ()){ yieldreturnsavable; } foreach(ISavablesavableContainerinGetComponents ()){ foreach(ISavablesavableinsavableContainer.Savables){ yieldreturnsavable; } } } }
SaveFile代表一个文件
[Serializable] publicclassSaveFileData{ publicuintCurId; publicstringDataContainer; } //代表一个存档文件 publicclassSaveFile:MonoBehaviour{ //包含实际数据的数据类 privateSaveDataContainer_saveDataContainer; privateuint_curId; publicstringPath{get;set;} publicSaveDataContainerSaveDataContainer{get{return_saveDataContainer;}} privateuintNextId{get{return++_curId;}} //得到场景里所有的SaveEntity privateIEnumerableGetEntities(){ //实现略过 } //将场景物体中的数据存入到_saveDataContainer中 publicvoidSave ()whereT:SaveDataContainer,new() { //一轮Id赋值,保证Id为0的所有ISavable都赋值一个新Id foreach(SaveEntityentityinEntities){ foreach(Savablesavableinentity.GetSavables()){ if(savable.DataContainerType==typeof(T)){ if(savable.Id==0){ savable.Id=NextId; } } } } TdataContainer=newT(); foreach(SaveEntityentityinEntities){ entity.Save(this,dataContainer); } _saveDataContainer=dataContainer; } //将_saveDataContainer中的数据载入到场景物体中 publicvoidLoad(){ foreach(SaveEntityentityinEntities){ entity.Load(this,_saveDataContainer); } } publicvoidLoadFromFile ()whereT:SaveDataContainer { stringjson=File.ReadAllText(Path); SaveFileDatadata=JsonUtility.FromJson (json); _saveDataContainer=JsonUtility.FromJson (data.DataContainer); _curId=data.CurId; } publicvoidSaveToFile(){ SaveFileDatadata=newSaveFileData(); data.CurId=_curId; data.DataContainer=JsonUtility.ToJson(_saveDataContainer); stringjson=JsonUtility.ToJson(data); File.WriteAllText(Path,json); } }
SaveDataContainer:
//这个类型存储了实际的数据,相当于是一个数据库 [Serializable] publicclassSaveDataContainer{ //这个中存储这实际物体的数据,需要将这个字典转换成数组并序列化 privateDictionary_data; publicDictionary Data{get{return_data}} publicIIdGetData(uintid){ return_data[id]; } publicvoidSetData(IIddata){ _data[data.Id]=data; } }
好了,框架就讲到这里,接下来实现示例代码:
Unit:
[Serializable] publicclassUnitSave:IId{ [SerializeField] privateuint_id; publicuintPrefabId; publicuintInventoryId; publicintHp; publicintLevel; publicuintId{get{return_id;}set{_id=value;}} } publicclassUnit:MonoBehaviour,ISavable{ publicintHp; publicintLevel; publicintPrefabId; publicInventoryInventory; publicuintId{get;set;} ISavable.DataType{get{returntypeof(UnitSave);}} ISavable.DataContainerType{get{returntypeof(ExampleSaveDataContainer);}} ISavable.Read(objectdata){ UnitSavesave=dataasUnitSave; Hp=save.Hp; Level=save.Level; } ISavable.Write(objectdata){ UnitSavesave=dataasUnitSave; save.Hp=Hp; save.Level=Level; save.InventoryId=Inventory.Id; } }
Inventory:
[Serializable] publicclassInventorySave:IId{ [SerializeField] privateuint_id; publicuintUnitId; publicuint[]Items; publicuintId{get{return_id;}set{_id=value;}} } publicclassInventory:MonoBehaviour,ISavable,ISavableContainer{ publicUnitUnit; publicList- Items; publicuintId{get;set;} ISavable.DataType{get{returntypeof(InventorySave);}} ISavable.DataContainerType{get{returntypeof(ExampleSaveDataContainer));}} ISavable.Read(objectdata){ //空 } ISavable.Write(objectdata){ InventorySavesave=dataasInventorySave; save.UnitId=Unit.Id; save.Items=Items.Select(item=>item.Id).ToArray(); } ISavableContainer.Savables{ returnItems; } }
Item:
[Serializable] publicItemSave:IId{ [SerializeField] privateuint_id; publicuintPrefabId; publicintCount; publicuintId{get{return_id;}set{_id=value;}} } //道具并不是继承自MonoBehaviour的,是一个普通的类 publicclassItem:ISavable{ //道具源数据所在Prefab,用于重新创建道具 publicuintPrefabId; publicintCount; publicuintId{get;set;} publicuintId{get;set;} ISavable.DataType{get{returntypeof(ItemSave);}} ISavable.DataContainerType{get{returntypeof(ExampleSaveDataContainer));}} ISavable.Read(objectdata){ ItemSavesave=dataasItemSave; Count=save.Count; } ISavable.Write(objectdata){ ItemSavesave=dataasItemSave; save.PrefabId=PrefabId; save.Count=Count; } }
ExampleSaveDataContainer:
[Serializable] publicclassExampleSaveDataContainer:SaveDataContainer,ISerializationCallbackReceiver{ publicUnitSave[]Units; publicItemSave[]Items; publicInventorySave[]Inventories; publicvoidOnBeforeSerialize(){ //将Data字典中的数据复制到数组中,实现略过 } publicvoidOnAfterDeserialize(){ //将数组中的数据赋值到Data字典中,实现略过 } }
ExampleGame:
publicclassExampleGame:MonoBehaviour{ publicvoidLoadGame(SaveFilefile){ //从文件中读入数据到SaveDataContainer file.LoadFromFile(); SaveDataContainerdataContainer=file.SaveDataContainer; //创建所有物体并赋值相应Id Unit[]units=dataContainer.Units.Select(u=>CreateUnit(u)); Item[]items=dataContainer.Items.Select(item=>CreateItem(item)); //将道具放入相应的道具栏中 foreach(Unitunitinunits){ uintinventoryId=unit.Inventory.Id; InventorySaveinventorySave=dataContainer.GetData(inventoryId); foreach(Itemiteminitems.Where(i=>inventorySave.Items.Contains(i.Id))){ unit.Inventory.Put(item); } } //调用Load进行实际的数据载入 file.Load(); } publicvoidSaveGame(SaveFilefile){ //相对来说,存档的实现比载入简单了许多 file.Save (); file.SaveToFile(); } publicUnitCreateUnit(UnitSavesave){ Unitunit=Instantiate(GetPrefab(save.PrefabId)).GetComponent (); unit.Id=save.Id; unit.Inventory.Id=save.InventoryId; returnunit; } publicItemCreateItem(ItemSavesave){ Itemitem=GetPrefab(save.PrefabId).GetComponent ().CreateItem(); item.Id=save.Id; returnitem; } }
使用方法:
给单位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,可以说,卡顿并不会很明显。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。