尝试用面向数据的思维来分析,但本人对面向数据的理解还不够深刻,因此这里只是一些思考。
功能描述:
- 有不同类型(法术、武器、药水)和数量的道具槽,道具需装备到槽内才能使用。
- 支持同种道具的堆叠 ,如在一个药水道具槽内装备100瓶生命回复药。
- 在背包中保存获取的道具,容量无限,可随时从背包中装备到道具槽内,或从道具槽中卸下
其实,严格来讲,道具槽感觉不应该划分在背包系统中,而是自己的一套。这里就先这样吧。
定义数据
背包
“我”现在是背包,不管其他所有的系统,为了完成职能,“我”需要知道哪些信息?
- 我要把什么道具放进背包? -------> item type( item name)
- 背包里同种道具的数量是多少? ----------> int
- 一种道具最大堆叠多少? --------------> int
- 道具是否在背包内/怎么将道具放进、取出背包?------------> bool / ......
数据用来记录游戏任一时刻的状态。考虑某一时刻背包的状态:
- 有哪些道具?-----------------> array/list/......
- 每个道具的数量是多少?---------------->int
考虑数据之间的关联性,我们可以得到一张表:
道具类型 | 背包中当前拥有的数量 | 背包中可容纳的最大数量 |
---|---|---|
生命回复药 | 1 | 10 |
战斧 | 1 | 1 |
火球术 | 1 | 1 |
为了区分每一条记录,我们要选取主键(必须唯一)。
方案一、
struct ItemData
{
ItemType type;
int itemCount;
int itemMaxCount;
}
Array<ItemData> or List<ItemData> inventory;
以前的我大概很快就会想到并决定这样的数据布局,当与外部系统交互时,就是struct ItemData
的引用满天飞了。
此时的主键是:道具类型 + 背包中当前拥有的数量 + 背包中可容纳的最大数量
方案二、
struct ItemData
{
int itemCount;
int itemMaxCount;
}
Map<ItemType, ItemData> inventory;
此时的主键是:道具类型
道具槽
按照上面的思路,“我”现在变成道具槽了,要知道的信息:
- 道具槽的类型? -----> slot type
- 道具槽是否被装备? -----> bool
- 装备了什么道具? -----> item Type
那如果要有一堆道具槽要管理,那么我们就要为槽添加主key。
道具槽类型 | 是否被装备 | 装备的道具类型 |
---|---|---|
药水 | 是 | 生命回复药 |
武器 | 是 | 战斧 |
法术 | 是 | 火球术 |
药水 | 否 | 空 |
法术 | 否 | 空 |
仔细看一上表,你会发现是否被装备这一列是冗余的,于是:
道具槽类型 | 装备的道具 |
---|---|
药水 | 生命回复药 |
武器 | 战斧 |
法术 | 火球术 |
药水 | 空 |
法术 | 空 |
从上表中可以发现,主键只能是:道具槽类型 + 装备的道具
方案一:
struct ItemSlot
{
SlotType slotType;
ItemType itemType;
}
TArray<ItemSlot> slots;
当我们要确定slot的唯一性时,是以struct ItemSlot
为单元比较了。
方案二:
甲:我不想用Array,我想用Map!!!
乙:OK。不过要加一个表项。
道具槽类型 | 索引 | 装备的道具 |
---|---|---|
药水 | 0 | 生命回复药 |
药水 | 1 | 空 |
法术 | 0 | 火球术 |
法术 | 1 | 空 |
武器 | 0 | 战斧 |
主键为:道具槽类型 + 索引
struct ItemSlot
{
SlotType slotType;
int slotNumber;
}
Map<ItemSlot,ItemType> slots;
背包 + 道具槽 + ......
这里我们很容易发现ItemType是两个系统都有的数据,因此可以借此将两个系统的数据关联起来。
如果要UI显示背包中的道具,很明显我们缺少图标资源。所以ItemType还要和图标资源建立联系。但要注意的是背包的运作并不要关心图标资源。
对于不同数据的关联:在ECS中,可利用entity来为不同的Component Data建立起逻辑关联;在面向对象编程中,我们通常定义一个大的Item类(确立了data的关联性:同属于一个类实例),然后将其传入不同系统,尽管系统只关注其中的一部分数据。
ActionRPG的背包系统
看过之前code部分的同学,应该很容易就能理解。ActionRPG的背包系统是一个很典型的面向对象设计。
- 有一个较大的ItemType类(会用这个类中数据的系统有:UI、资源管理、GAS、背包等等)
- 背包的数据容器放在PlayerController的子类中,交互方法一部分放在接口Interface中,由PlayerController的子类实现,另一部直接声明为public方法。.
- 其他系统(比如UI)与背包系统的交互的方法为绑定delegate,也就是消息传递。(因为PlayerController的实例是可以从全局访问到的,因此获取delegate的方法放在接口中)
/** Gets the delegate for inventory item changes */
virtual FOnInventoryItemChangedNative& GetInventoryItemChangedDelegate() = 0;
/** Gets the delegate for inventory slot changes */
virtual FOnSlottedItemChangedNative& GetSlottedItemChangedDelegate() = 0;
/** Gets the delegate for when the inventory loads */
virtual FOnInventoryLoadedNative& GetInventoryLoadedDelegate() = 0;