# BagSystem
通用的游戏背包系统
# 前言
在没有真正开发过背包系统时对这个系统存在一定程度的误区,认为只有显示在背包里的才被称之为背包中的道具,然而并非如此,真正游戏开发中会有很多很多模块,例如一个游戏中存在:英雄,小兵,消耗道具,货币,宠物等等,其实都可以存在背包中,如果每个系统都自己管理那么一定有很多重复的增删改查,想想都是一件很恐怖的事情,因此一个好的背包系统需要做到能涵盖游戏中所有存在的物体,需要有一个概念:所有玩家身上存档的东西都是道具或者道具的衍生品。如果分类分的好,连角色本身也是一个道具,衍生开讲英雄可以携带技能,可以携带装备,技能以及装备就是不同的道具,被存在了英雄身上的不同包裹中,以这种思路展开,那么现在游戏中背包的架构如下:
![背包](./背包.png "背包")
# 道具
## 道具数据结构
加上角色本身,现在游戏中存在8种不同类型的道具,不同类型的道具其数据结构一定是不同的,如下这种方式肯定是不可取的:
```
public class Inventroy
{
int id;
string name;
int level;
int attack;
int def;
//...
}
```
为了能适合所有道具的数据结构一定需要改成可扩展的,如下:
```
public enum ItemKey
{
Quality = 1, //品质
Level = 2, //等级
Name = 3, //名称
Exp = 4, //经验
HP = 100, //血量
Defense, //防御
Attack, //攻击力
// ... 等等,
// 如血量,防御等词条性质的其实不能简单放在这里,因为存在百分比和直接加成等,当然也可以直接用两个Key分别表示百分比值和加成值。
}
public class Item
{
/// <summary>
/// 道具的唯一Id
/// </summary>
public ulong id;
/// <summary>
/// 道具的配置Id
/// </summary>
public int metaId;
/// <summary>
/// 道具堆叠数量
/// </summary>
public int count;
/// <summary>
/// Kv数据:所有道具身上的属性
/// </summary>
public Dictionary<ItemKey, long> keyValues;
/// <summary>
/// 字符串数据:为了名称等一些字符串的属性,其实除了名称很少有字符串数据,可以把name这个字段拿出来
/// </summary>
public Dictionary<ItemKey, string> keyValueStrs;
/// <summary>
/// 该道具身上的背包
/// </summary>
public Dictionary<BagType, Bag> itemBags;
// 道具配置
public ItemConfig itemConfig;
public bool IsFull => count >= itemConfig.overlayCount;
}
```
除了几个必须存在的字段:唯一Id,配置Id,堆叠数量,其余的所有属性全部放进不同的Map中。以上的数据结构是我在多次小项目中总结,如果有不符合可以自行扩展。上面的BagType,Bag都在下面会讲到。
## 道具配置
在不同项目中难免存在不同的需求,通常在配置方面,我的思路是不动道具配置,而是针对不同类型不同需求的道具另起配置结构,打个比方:如果游戏中存在技能,装备等,技能存在等级1-10级,每级的效果都不一样,装备也存在等级1-10级,每级加成效果都不一样,不可能把这些所有道具的差异性全部往里面塞,而是从其他配置中关联到这个道具Id,如下:
```
// 技能配置
public class SkillConfig
{
public int id; //配置Id
public int itemId; //对应的道具Id
public int level; //等级
// 不同的技能效果配置等等
}
// 装备配置
public class EquipConfig
{
public int id; //配置Id
public int itemId; //对应的道具Id
public int level; //等级
// 不同的属性加成等等,词条的配置等
}
// 道具配置
public class ItemConfig
{
public int id; //配置Id
public string name; //名称
public string des; //描述
public string icon; //图标
public string prefabPath; //预制体路径,掉落显示
public int overlayCount = 1; //最大可叠加多少个
public BagType bagType; //背包类型
public ItemType type; //道具类型
public ItemQualityType qualityType; //道具品质
// ... 扩展
}
// 道具类型
public enum ItemType
{
None,
// 需要细分,由功能细分,如:强化卷,经验类的,等等主要目的为了筛选方便,以及区分类型
// 不同类型的装备,不同类型的英雄都需要细分ItemType
}
// 道具品质:通常一个游戏中品质也是共用的
public enum ItemQualityType
{
Normal = 0,
Excellent = 1,
Rarity = 2,
Legend = 3,
// ...
}
```
从上面数据结构可以看出,道具配置尽量通用化,而道具特殊性的配置尽量单独提取出来,放到其他配置表中,尽可能不让道具的数据结构存在冗余,存在的每一条字段都是所有道具都需要的。
# 背包
上面讲了道具怎么实现通用和扩展性,下面讲一下背包的实现,首先要明确背包有什么具体功能,除了存储功能,还需要有什么其他功能?举个例子:
1. 每个英雄有3个技能,但同时只可以装备一个技能,英雄的技能从哪里来?可以通过配置表配置3个技能,在获得英雄时,给这个英雄道具的背包上存放这3个技能道具,那么装备了哪个技能这个功能怎么实现?
2. 现在是SLG游戏,玩家一共可以配置x个队伍,每个队伍可以配置一个英雄,n个小兵,且已被配置过的英雄小兵无法使用,又要怎么实现?
1. 思路1:
可以再创建x个背包在人物身上,然后将英雄和小兵从它的背包移到这x个背包中的某一个,但是这样会有一个问题,我在一个英雄图鉴界面,或者其他要展示所有英雄的界面又需要将x个背包的单位全部遍历整合才能得到,而且我的背包数量也会因此变多。
2. 思路2:
在背包里面实现一套穿戴系统,那么任何有用到穿戴/卸下的地方都可以直接在当前背包操作,而且穿戴/卸载其实在很多系统都会或多或少的使用,这里说的穿戴/卸载是一个概念,同类型的都可以这样使用。这样的好处就是所有操作在一个背包中完成,设计上简单了,使用上也不复杂了,不需要整理多个背包。
## 背包的数据结构
上面我们提到现在游戏中存在8种不同类型的道具,分背包类型主要为了方便数据的直接使用,当然你可以把所有道具全部放在一个背包里,使用时再筛选,那样后期管理起来肯定存在一定难度。根据游戏中的UI表现来讲,这n类道具需要分出n个不同背包,分别存放,使用时操作对应背包。不管什么背包在游戏中是什么作用,都不影响他们拥有统一的数据结构。下面我们来看下背包的数据结构:
```
public enum BagType
{
None,
Default, //默认背包(将消耗品,材料等归于一个,如果游戏中是分开显示,那么背包中也分开)
Role, //角色
Hero, //英雄
Skill, //技能
Equip, //装备
Soldier, //小兵
Pet, //宠物
Currency, //货币
}
// 背包配置
public class BagConfig
{
public BagType bagType; //背包类型
public int initCapacity; //初始容量
// ... 如果游戏中有消耗钻石扩容等,可以配置在这里
}
// 背包实现
public class Bag
{