嘗試用面向數(shù)據(jù)的思維來分析,但本人對面向數(shù)據(jù)的理解還不夠深刻,因此這里只是一些思考。
功能描述:
- 有不同類型(法術(shù)、武器、藥水)和數(shù)量的道具槽,道具需裝備到槽內(nèi)才能使用。
- 支持同種道具的堆疊 ,如在一個藥水道具槽內(nèi)裝備100瓶生命回復(fù)藥。
- 在背包中保存獲取的道具,容量無限,可隨時從背包中裝備到道具槽內(nèi),或從道具槽中卸下
其實(shí),嚴(yán)格來講,道具槽感覺不應(yīng)該劃分在背包系統(tǒng)中,而是自己的一套。這里就先這樣吧。
定義數(shù)據(jù)
背包
“我”現(xiàn)在是背包,不管其他所有的系統(tǒng),為了完成職能,“我”需要知道哪些信息?
- 我要把什么道具放進(jìn)背包? -------> item type( item name)
- 背包里同種道具的數(shù)量是多少? ----------> int
- 一種道具最大堆疊多少? --------------> int
- 道具是否在背包內(nèi)/怎么將道具放進(jìn)、取出背包?------------> bool / ......
數(shù)據(jù)用來記錄游戲任一時刻的狀態(tài)??紤]某一時刻背包的狀態(tài):
- 有哪些道具?-----------------> array/list/......
- 每個道具的數(shù)量是多少?---------------->int
考慮數(shù)據(jù)之間的關(guān)聯(lián)性,我們可以得到一張表:
| 道具類型 | 背包中當(dāng)前擁有的數(shù)量 | 背包中可容納的最大數(shù)量 |
|---|---|---|
| 生命回復(fù)藥 | 1 | 10 |
| 戰(zhàn)斧 | 1 | 1 |
| 火球術(shù) | 1 | 1 |
為了區(qū)分每一條記錄,我們要選取主鍵(必須唯一)。
方案一、
struct ItemData
{
ItemType type;
int itemCount;
int itemMaxCount;
}
Array<ItemData> or List<ItemData> inventory;
以前的我大概很快就會想到并決定這樣的數(shù)據(jù)布局,當(dāng)與外部系統(tǒng)交互時,就是struct ItemData的引用滿天飛了。
此時的主鍵是:道具類型 + 背包中當(dāng)前擁有的數(shù)量 + 背包中可容納的最大數(shù)量
方案二、
struct ItemData
{
int itemCount;
int itemMaxCount;
}
Map<ItemType, ItemData> inventory;
此時的主鍵是:道具類型
道具槽
按照上面的思路,“我”現(xiàn)在變成道具槽了,要知道的信息:
- 道具槽的類型? -----> slot type
- 道具槽是否被裝備? -----> bool
- 裝備了什么道具? -----> item Type
那如果要有一堆道具槽要管理,那么我們就要為槽添加主key。
| 道具槽類型 | 是否被裝備 | 裝備的道具類型 |
|---|---|---|
| 藥水 | 是 | 生命回復(fù)藥 |
| 武器 | 是 | 戰(zhàn)斧 |
| 法術(shù) | 是 | 火球術(shù) |
| 藥水 | 否 | 空 |
| 法術(shù) | 否 | 空 |
仔細(xì)看一上表,你會發(fā)現(xiàn)是否被裝備這一列是冗余的,于是:
| 道具槽類型 | 裝備的道具 |
|---|---|
| 藥水 | 生命回復(fù)藥 |
| 武器 | 戰(zhàn)斧 |
| 法術(shù) | 火球術(shù) |
| 藥水 | 空 |
| 法術(shù) | 空 |
從上表中可以發(fā)現(xiàn),主鍵只能是:道具槽類型 + 裝備的道具
方案一:
struct ItemSlot
{
SlotType slotType;
ItemType itemType;
}
TArray<ItemSlot> slots;
當(dāng)我們要確定slot的唯一性時,是以struct ItemSlot為單元比較了。
方案二:
甲:我不想用Array,我想用Map?。?!
乙:OK。不過要加一個表項(xiàng)。
| 道具槽類型 | 索引 | 裝備的道具 |
|---|---|---|
| 藥水 | 0 | 生命回復(fù)藥 |
| 藥水 | 1 | 空 |
| 法術(shù) | 0 | 火球術(shù) |
| 法術(shù) | 1 | 空 |
| 武器 | 0 | 戰(zhàn)斧 |
主鍵為:道具槽類型 + 索引
struct ItemSlot
{
SlotType slotType;
int slotNumber;
}
Map<ItemSlot,ItemType> slots;
背包 + 道具槽 + ......
這里我們很容易發(fā)現(xiàn)ItemType是兩個系統(tǒng)都有的數(shù)據(jù),因此可以借此將兩個系統(tǒng)的數(shù)據(jù)關(guān)聯(lián)起來。
如果要UI顯示背包中的道具,很明顯我們?nèi)鄙賵D標(biāo)資源。所以ItemType還要和圖標(biāo)資源建立聯(lián)系。但要注意的是背包的運(yùn)作并不要關(guān)心圖標(biāo)資源。
對于不同數(shù)據(jù)的關(guān)聯(lián):在ECS中,可利用entity來為不同的Component Data建立起邏輯關(guān)聯(lián);在面向?qū)ο缶幊讨?,我們通常定義一個大的Item類(確立了data的關(guān)聯(lián)性:同屬于一個類實(shí)例),然后將其傳入不同系統(tǒng),盡管系統(tǒng)只關(guān)注其中的一部分?jǐn)?shù)據(jù)。
ActionRPG的背包系統(tǒng)
看過之前code部分的同學(xué),應(yīng)該很容易就能理解。ActionRPG的背包系統(tǒng)是一個很典型的面向?qū)ο笤O(shè)計(jì)。
- 有一個較大的ItemType類(會用這個類中數(shù)據(jù)的系統(tǒng)有:UI、資源管理、GAS、背包等等)
- 背包的數(shù)據(jù)容器放在PlayerController的子類中,交互方法一部分放在接口Interface中,由PlayerController的子類實(shí)現(xiàn),另一部直接聲明為public方法。.
- 其他系統(tǒng)(比如UI)與背包系統(tǒng)的交互的方法為綁定delegate,也就是消息傳遞。(因?yàn)镻layerController的實(shí)例是可以從全局訪問到的,因此獲取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;