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;
publicDictionaryData{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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。