300 lines
10 KiB
Markdown
300 lines
10 KiB
Markdown
|
## 前言
|
|||
|
ActionRPG在RPGType.h定义了各个类型,在RPGControllerBase类中定义背包逻辑。存档逻辑写在RPGGameInstance与RPGSaveGame中。
|
|||
|
|
|||
|
个人建议:如果你打算制作个人项目,这里的代码基本不用改,直接就可以用。当然最好还是将共用部分封装成插件,这样后续Ue4项目也可以使用。这也是官方GameplayAbility视频教程中所建议的。
|
|||
|
## 物品
|
|||
|
### RPGItem.h
|
|||
|
FPrimaryAssetType ItemType:物品类型。其中这些类型都定义在URPGAssetManager类中<br>
|
|||
|
其他还有物品名称、描述、图标、价格、最大数量、最高等级、附带的Ability等级变量。
|
|||
|
|
|||
|
TSubclassOf<URPGGameplayAbility> GrantedAbility:物品附带的Ability的对象
|
|||
|
|
|||
|
FString GetIdentifierString():返回主资源的Id字符串
|
|||
|
|
|||
|
FPrimaryAssetId GetPrimaryAssetId():返回主资源的Id
|
|||
|
|
|||
|
### RPGType.h
|
|||
|
#### RPGItemSlot
|
|||
|
FRPGItemSlot位于RPGType.h中:比如黑魂里的快捷物品栏、装备武器栏。你可以给Slot指定类型来进行区分。因为他通常表现为放置特定类型物品的格子,所以之后我就将其翻译成物品格子了。
|
|||
|
|
|||
|
FPrimaryAssetType ItemType:物品类型
|
|||
|
int32 SlotNumber:该格子所在容器的索引值
|
|||
|
#### FRPGItemData
|
|||
|
int32 ItemCount:背包中的物品数量
|
|||
|
|
|||
|
int32 ItemLevel:物品等级
|
|||
|
|
|||
|
UpdateItemData函数:物品发生变动时的容错处理。
|
|||
|
#### 委托
|
|||
|
```
|
|||
|
//背包物品发生变化时
|
|||
|
FOnInventoryItemChanged, bool, bAdded, URPGItem*, Item
|
|||
|
FOnInventoryItemChangedNative, bool, URPGItem*
|
|||
|
|
|||
|
//背包物品格子的信息发生改变
|
|||
|
FOnSlottedItemChanged, FRPGItemSlot, ItemSlot, URPGItem*, Item
|
|||
|
FOnSlottedItemChangedNative, FRPGItemSlot, URPGItem*
|
|||
|
|
|||
|
//打包背包系统加载Asset与数据时
|
|||
|
FOnInventoryLoaded
|
|||
|
FOnInventoryLoadedNative
|
|||
|
|
|||
|
//加载游戏存档
|
|||
|
FOnSaveGameLoaded, URPGSaveGame*, SaveGame
|
|||
|
FOnSaveGameLoadedNative, URPGSaveGame*
|
|||
|
```
|
|||
|
### RPGPlayerControllerBase.h
|
|||
|
个人不太喜欢把背包逻辑都写在控制类中,还是倾向于封装成一个Component。所以本人通过继承UActorComponent来移植Inventory方面的代码。
|
|||
|
#### 变量
|
|||
|
```
|
|||
|
/** Map of all items owned by this player, from definition to data */
|
|||
|
//玩家所拥有物品的Map
|
|||
|
TMap<URPGItem*, FRPGItemData> InventoryData;
|
|||
|
|
|||
|
/** Map of slot, from type/num to item, initialized from ItemSlotsPerType on RPGGameInstanceBase */
|
|||
|
//物品格子Map,保存物品的类型与数量数据,使用RPGGameInstanceBase中的ItemSlotsPerType Map初始化。
|
|||
|
TMap<FRPGItemSlot, URPGItem*> SlottedItems;
|
|||
|
```
|
|||
|
#### 函数
|
|||
|
```
|
|||
|
//当背包发生变化时,会调用这个函数,并且通知所有委托
|
|||
|
void InventoryItemChanged(bool bAdded, URPGItem* Item);
|
|||
|
|
|||
|
/** Called after an item was equipped and we notified all delegates */
|
|||
|
//当物品被装备时,会调用这个函数,并且通知所有委托
|
|||
|
void SlottedItemChanged(FRPGItemSlot ItemSlot, URPGItem* Item);
|
|||
|
|
|||
|
//往背包中增加物品
|
|||
|
bool AddInventoryItem(URPGItem* NewItem, int32 ItemCount = 1, int32 ItemLevel = 1, bool bAutoSlot = true);
|
|||
|
|
|||
|
//从背包中移除物品
|
|||
|
bool RemoveInventoryItem(URPGItem* RemovedItem, int32 RemoveCount = 1);
|
|||
|
|
|||
|
//返回背包中指定类型的所有物品,如果没有指定类型将会返回所有物品
|
|||
|
void GetInventoryItems(TArray<URPGItem*>& Items, FPrimaryAssetType ItemType);
|
|||
|
|
|||
|
//返回这个背包实例的物品数
|
|||
|
int32 GetInventoryItemCount(URPGItem* Item) const;
|
|||
|
|
|||
|
/** Returns the item data associated with an item. Returns false if none found */
|
|||
|
//返回指定物品的数据
|
|||
|
bool GetInventoryItemData(URPGItem* Item, FRPGItemData& ItemData) const;
|
|||
|
|
|||
|
//将物品移动到指定物品格子
|
|||
|
bool SetSlottedItem(FRPGItemSlot ItemSlot, URPGItem* Item);
|
|||
|
|
|||
|
/** Returns item in slot, or null if empty */
|
|||
|
//返回指定物品格子中的物品指针
|
|||
|
URPGItem* GetSlottedItem(FRPGItemSlot ItemSlot) const;
|
|||
|
|
|||
|
/** Returns all slotted items of a given type. If none is passed as type it will return all */
|
|||
|
//返回物品格子中指定类型的物品,如果没有指定类型,将会返回所有物品
|
|||
|
void GetSlottedItems(TArray<URPGItem*>& Items, FPrimaryAssetType ItemType, bool bOutputEmptyIndexes);
|
|||
|
|
|||
|
//填充空的物品格子
|
|||
|
void FillEmptySlots();
|
|||
|
|
|||
|
//保存背包数据
|
|||
|
bool SaveInventory();
|
|||
|
|
|||
|
//载入背包数据
|
|||
|
bool LoadInventory();
|
|||
|
```
|
|||
|
#### 委托
|
|||
|
因为本人制作的Demo为魂like游戏,所以Token类型的Item会作为“魂”来定义。魂并不会影响UI显示,所以本人的项目里会多加一个委托:FOnInventoryItemChanged OnInventoryTokenItemChanged;
|
|||
|
|
|||
|
并且在通知函数FillEmptySlotWithItem中进行判断,如果是Token类型就只会对OnInventoryTokenItemChanged进行多播。
|
|||
|
```
|
|||
|
//背包物品增加或者减少
|
|||
|
FOnInventoryItemChanged OnInventoryItemChanged;
|
|||
|
|
|||
|
//本地版本,在BP委托之前执行
|
|||
|
FOnInventoryItemChangedNative OnInventoryItemChangedNative;
|
|||
|
|
|||
|
//当物品格子发生改变时
|
|||
|
FOnSlottedItemChanged OnSlottedItemChanged;
|
|||
|
|
|||
|
//本地版本
|
|||
|
FOnSlottedItemChangedNative OnSlottedItemChangedNative;
|
|||
|
|
|||
|
//背包数据载入时调用委托
|
|||
|
FOnInventoryLoaded OnInventoryLoaded;
|
|||
|
|
|||
|
//本地版本
|
|||
|
FOnInventoryLoadedNative OnInventoryLoadedNative;
|
|||
|
```
|
|||
|
#### 接口
|
|||
|
```
|
|||
|
// 实现IRPGInventoryInterface
|
|||
|
virtual const TMap<URPGItem*, FRPGItemData>& GetInventoryDataMap() const override
|
|||
|
{
|
|||
|
return InventoryData;
|
|||
|
}
|
|||
|
virtual const TMap<FRPGItemSlot, URPGItem*>& GetSlottedItemMap() const override
|
|||
|
{
|
|||
|
return SlottedItems;
|
|||
|
}
|
|||
|
virtual FOnInventoryItemChangedNative& GetInventoryItemChangedDelegate() override
|
|||
|
{
|
|||
|
return OnInventoryItemChangedNative;
|
|||
|
}
|
|||
|
virtual FOnSlottedItemChangedNative& GetSlottedItemChangedDelegate() override
|
|||
|
{
|
|||
|
return OnSlottedItemChangedNative;
|
|||
|
}
|
|||
|
virtual FOnInventoryLoadedNative& GetInventoryLoadedDelegate() override
|
|||
|
{
|
|||
|
return OnInventoryLoadedNative;
|
|||
|
}
|
|||
|
```
|
|||
|
#### 通知
|
|||
|
```
|
|||
|
/** 给物品格子指定物品,如果返回True则代表成功指定物品 */
|
|||
|
bool FillEmptySlotWithItem(URPGItem *NewItem);
|
|||
|
|
|||
|
/** 物品系统数据更新回调函数 */
|
|||
|
void NotifyInventoryItemChanged(bool bAdded, URPGItem *Item);
|
|||
|
void NotifySlottedItemChanged(FRPGItemSlot ItemSlot, URPGItem *Item);
|
|||
|
void NotifyInventoryLoaded();
|
|||
|
|
|||
|
/** Called when a global save game as been loaded */
|
|||
|
void HandleSaveGameLoaded(URPGSaveGame* NewSaveGame);
|
|||
|
```
|
|||
|
## 存档
|
|||
|
### RPGSaveGame.h
|
|||
|
它本质上就是个用于存储游戏数据的存储对象。
|
|||
|
```
|
|||
|
/** 两个存储物品信息的map */
|
|||
|
TMap<FPrimaryAssetId, FRPGItemData> InventoryData;
|
|||
|
|
|||
|
TMap<FRPGItemSlot, FPrimaryAssetId> SlottedItems;
|
|||
|
|
|||
|
/** 用户独立id */
|
|||
|
FString UserId;
|
|||
|
protected:
|
|||
|
/** 存储废弃物品,这些物品将不会被保存 */
|
|||
|
TArray<FPrimaryAssetId> InventoryItems_DEPRECATED;
|
|||
|
|
|||
|
/** 最后的保存版本 */
|
|||
|
int32 SavedDataVersion;
|
|||
|
```
|
|||
|
### RPGGameInstanceBase.h
|
|||
|
主要的存档逻辑都写在这里,最主要的就是HandleSaveGameLoaded、WriteSaveGame、LoadOrCreateSaveGame、HandleAsyncSave函数。
|
|||
|
#### 变量
|
|||
|
```
|
|||
|
/** 给新玩家的默认物品 */
|
|||
|
TMap<FPrimaryAssetId, FRPGItemData> DefaultInventory;
|
|||
|
|
|||
|
/** 每个物品格子数目 */
|
|||
|
TMap<FPrimaryAssetType, int32> ItemSlotsPerType;
|
|||
|
|
|||
|
/** 存档槽名称 */
|
|||
|
FString SaveSlot;
|
|||
|
|
|||
|
/** 系统指定的用户序号 */
|
|||
|
int32 SaveUserIndex;
|
|||
|
|
|||
|
/** 当前的存档对象 */
|
|||
|
URPGSaveGame* CurrentSaveGame;
|
|||
|
|
|||
|
/** 是否需要保存 */
|
|||
|
bool bSavingEnabled;
|
|||
|
|
|||
|
/** 是否处于保存中 */
|
|||
|
bool bCurrentlySaving;
|
|||
|
|
|||
|
/** 在保存过程中是否出现了新的保存请求*/
|
|||
|
bool bPendingSaveRequested;
|
|||
|
```
|
|||
|
#### 函数
|
|||
|
```
|
|||
|
/** 增加新玩家的默认物品 */
|
|||
|
void AddDefaultInventory(URPGSaveGame* SaveGame, bool bRemoveExtra = false);
|
|||
|
|
|||
|
/** 判断物品格子是否有效 */
|
|||
|
bool IsValidItemSlot(FRPGItemSlot ItemSlot) const;
|
|||
|
|
|||
|
/** 返回当前的存档对象 */
|
|||
|
URPGSaveGame* GetCurrentSaveGame();
|
|||
|
|
|||
|
/** 设置当前是否可以保存 */
|
|||
|
void SetSavingEnabled(bool bEnabled);
|
|||
|
|
|||
|
/** 载入或者创建存档对象 */
|
|||
|
bool LoadOrCreateSaveGame();
|
|||
|
|
|||
|
/** 存档的载入流程控制(如果存档类型符合则,加载物品,不符合创建新的存档对象并且加入默认物品) */
|
|||
|
bool HandleSaveGameLoaded(USaveGame* SaveGameObject);
|
|||
|
|
|||
|
/** Gets the save game slot and user index used for inventory saving, ready to pass to GameplayStatics save functions */
|
|||
|
/** 取得存档槽名称与用户序号 */
|
|||
|
void GetSaveSlotInfo(FString& SlotName, int32& UserIndex) const;
|
|||
|
|
|||
|
/** Writes the current save game object to disk. The save to disk happens in a background thread*/
|
|||
|
/** 将存档写入磁盘,此为异步操作 */
|
|||
|
bool WriteSaveGame();
|
|||
|
|
|||
|
/** 将存档数据清空 */
|
|||
|
void ResetSaveGame();
|
|||
|
|
|||
|
/** 当异步存储进行时调用(为了处理当存档过程中有了新的存档需要的情况) */
|
|||
|
virtual void HandleAsyncSave(const FString& SlotName, const int32 UserIndex, bool bSuccess);
|
|||
|
```
|
|||
|
#### 委托
|
|||
|
```
|
|||
|
/** 当存档载入或者重置时 */
|
|||
|
UPROPERTY(BlueprintAssignable, Category = Inventory)
|
|||
|
FOnSaveGameLoaded OnSaveGameLoaded;
|
|||
|
|
|||
|
/** 本地版本 */
|
|||
|
FOnSaveGameLoadedNative OnSaveGameLoadedNative;
|
|||
|
```
|
|||
|
|
|||
|
## 备注
|
|||
|
这个游戏中ItemSlot的设置与魂系列游戏不太一样。所以这里建议删除所有自动填充ItemSlot的逻辑,重写SetSlottedItem、FillEmptySlots、FillEmptySlotWithItem等函数。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
ActionRPG首先会在继承RPGGameInstanceBase的BP_GameInstace中初始化默认存档数据,以及加载各个Slot类型的物品Asset。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
ARPGPlayerControllerBase::FillEmptySlotWithItem(URPGItem* NewItem)
|
|||
|
{
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
## UMG逻辑
|
|||
|
该物品系统是把 消耗品、装备、技能物品都做在一起的。
|
|||
|
主要的Asset位于Blueprints-WidgetBP-Inventory
|
|||
|
|
|||
|
对于背包系统我们只需要看:
|
|||
|
### WB_Equipment
|
|||
|
#### 设计界面
|
|||
|
将5个WB_EquipmentSlot拖到界面中,并且将其中3个的ItemType设置为Weapon,MinEquippedItems为1。最后按顺序将SlotNumber设置为0~2。
|
|||
|
|
|||
|
另外两个将ItemType分别设置为Skill与Potion。
|
|||
|
#### Construct
|
|||
|
取得所有的WB_EquipmentSlot对象,并以此构造EquipmentListSlot TArray变量
|
|||
|
### WB_EquipmentSlot
|
|||
|
主要的逻辑都写在这里
|
|||
|
#### Construct
|
|||
|
执行刷新RefreshItem事件=》绑定OnSlottedItemChanged事件=》设置控件上Label显示(显示为ItemType)
|
|||
|
#### RefreshItem
|
|||
|
从控制类中执行GetSlottedItem,获取Slot信息来初始化Item变量=》再以这个信息设置Brush=》并根据Item是否获取成功来设置PotionNumberLabel是否可见。=》设置PotionNumberLabel文字
|
|||
|
|
|||
|
|
|||
|
|
|||
|
## 本人设计思路
|
|||
|
### ItemType
|
|||
|
CanBeUseItemType 可使用物体
|
|||
|
SkillItemType 技能
|
|||
|
TokenItemType 货币
|
|||
|
EquipmentItemType 装备
|
|||
|
KeyItemType 钥匙
|
|||
|
RubbishItemType 垃圾
|