物品存储与背包系统

    游戏中,物品存储和背包系统是最基础的模块,因为两者关联密切,这里放在一起论述。

    物品和背包都是一个广义的概念,物品通常包括道具、材料、任务物品、坐骑和宠物等,背包对应可以分为道具背包、材料背包、任务物品背包、坐骑栏和宠物栏等。有时为了方便,道具、材料和任务物品可以合并在同一个背包,这样道具和材料将采用同样的数据结构存储,虽然会浪费一些存储空间(因为材料一般比道具的属性要少得多),但可以通过其他有效的方法减少这种损耗(下面将提及)。道具和宠物通常采用两种不同的数据结构存储在不同的背包中,原因在于其属性和特性都有很大区别。背包系统也会包含其他一些特殊用途的背包,比如仓库(存储通过仓库NPC寄存的道具)、售卖栏(存储玩家售卖给NPC的道具,便于回购)和摆摊栏(存储玩家摆摊中的商品)等。

    下面的描述中,我们假定物品包括道具和宠物两种,背包包括道具背包、仓库、宠物栏、售卖栏、摆摊栏。

    道具和宠物采用不同的数据结构存储,为了管理和使用上的方便,可以让其派生于同一个物品基类。物品基类保存共有的一些字段,比如如下定义:

struct Item {
  int64_t m_id; // 全局唯一ID
  int m_type; // 物品类型(1级红药、2级红药等)
  int m_kind; // 物品种类(道具、宠物等)
  ...
};

道具中也分很多种,比如材料、装备等,在实现时,可以采用union定义不同的属性。

    物品数据需要固化,即保存在数据库中。很多游戏为了效率,一般采用共享内存和cache的方式在服务器开始时,加载数据库数据到内存。游戏进行时读取和修改的都是内存中的数据。这些数据中,大多会每隔一定时间写入数据库,而有些会立即写入。同时基于效率的原因,游戏中的物品的数据库存储也和一般的存储不同,不会每个道具作为一行,每个道具属性作为一列。而是每行存储一个玩家的物品数据,以玩家ID为键值,每列为一blob,存储一个背包,背包物品数据采用定长存储,以二进制形式写入。比如在我们的假定情况下,该物品表将包括六列,分别为玩家ID、道具背包列、仓库列、宠物栏列、售卖栏列和摆摊栏列。背包数据结构为一块连续内存,一个背包格子对应一个该格子上的物品数据结构,空格子的物品数据结构的m_id设为INVALID_ID以进行区分,物品数据连续存储。存储方式为,比如道具背包最大有100格,则以位置为下标顺序,0-99格连续存入,这样在载入的时候也会每列直接映射到该背包数据结构。

    单个道具的大小大约在100个字节,而宠物会达到300-400字节。似乎不大,但各种背包格子加在一起,数目会达到300左右,总的空间需求就是几十K。而玩家越来越多,总的空间消耗将会非常大。所以可以采用变长存储,即只存储有物品的格子。这种情况下,上面的Item结构就要增加一个成员变量,如m_pos,记录该物品所在格子。同时背包数据结构改为包括两部分,header和content。Header记录该背包当前物品数,有效格子数,最大格子数等。Content则保存该背包以m_pos为序的全部物品数据

    还有另外一层考虑。在游戏中,物品的移动操作非常频繁,尤其整理背包的操作,短时间内会进行大量移动。如果每次移动都要从原位置删除物品数据,然后在新位置添加,显然是低效的,逻辑上也不合理。一个很自然的方法就是,把物品数据分为两块,一块为索引信息,一块为实际数据。索引信息结构可以如此定义:

struct ItemBrief {
  int64_t m_id; // 全局唯一ID
  int m_kind; // 物品种类(道具、宠物等)
  ...
};

实际数据可以依然采用上述Item结构。数据表变为有五个索引信息列,分别对应背包、仓库、宠物栏、售卖栏、摆摊栏,有两个实际数据列,分别存储道具和宠物数据。索引信息和实际数据通过m_id关联,而通过m_kind判断物品是在道具列还是宠物列中。索引信息采用定长存储,为了效率可以添加header。实际数据采用变长存储,以节省空间。这样,移动或交换物品的过程,可以只修改索引信息,而不用关心实际数据。只有在物品的添加、删除或合并等操作时,才去同时关心索引信息和实际数据。

    上面是对该模块的一般设计的简要描述,不涉及具体的设计实现细节和技巧,只为给不熟悉游戏设计的读者提供概略的了解和感性的认识。


https://blog.csdn.net/boyxiaolong/article/details/22471575

猜你喜欢

转载自blog.csdn.net/larry_zeng1/article/details/80074857
今日推荐