侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Unity实现游戏存档框架

技术  /  管理员 发布于 7年前   209

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    Unity向量按照某一点进行旋转
    下一条:
    VMS中解协议常用方法备忘(小结)
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 2024.07.09日OpenAI将终止对中国等国家和地区API服务(0个评论)
    • 2024/6/9最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(0个评论)
    • 国外服务器实现api.openai.com反代nginx配置(0个评论)
    • 2024/4/28最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2017-09
    • 2018-01
    • 2018-07
    • 2018-08
    • 2018-09
    • 2018-12
    • 2019-01
    • 2019-02
    • 2019-03
    • 2019-04
    • 2019-05
    • 2019-06
    • 2019-07
    • 2019-08
    • 2019-09
    • 2019-10
    • 2019-11
    • 2019-12
    • 2020-01
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-12
    • 2024-02
    • 2024-04
    • 2024-05
    • 2024-06
    • 2025-02
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客