Unity实现游戏存档框架
最近重构了一下我的存档框架。我在这里对实现方法进行简单的解析。注意这里主要演示算法,所以,效率上并不是最佳。一个游戏中,可能有成百上千个物体需要存储,而且有几十种类型,接下来就用一个简单的例子来解释。一个很简单的例子,有一个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,可以说,卡顿并不会很明显。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。