代码链接:RootEntertainment/LearnARPG
Demo是一个基于UE4 AbilitySystem的动作类游戏。主要设计了数据驱动的武器和技能框架,背包库存装备系统,全局动态数据层。在动画方面学习了ALS的动画系统,主要包括分层混合提供不同武器的Overlap能力和蒙太奇,六方向状态机和基于步频步幅的走跑混合等。
技能系统的设计方面参考了ActionRPG和GASShooter。
数据驱动设计
全局基础配置
利用DataAsset和单例读取全局配置为角色初始化装备槽。
物品资产
物品的信息使用PrimaryAssetData设计,由AssetManager负责加载,后续技能、UI等系统都会依据其Item对象进行显示等逻辑。
全局动态数据
利用Guid为全局的物品构造动态数据。所有的物品数据存放在AssetManager单例中。
USTRUCT(BlueprintType)
struct LEARNARPG_API FARPGItemData
{
GENERATED_BODY()
FARPGItemData():Level(0), Count(0)
{}
FARPGItemData(const FGuid& InGID, int InLevel, const FPrimaryAssetId& InAssetId, int InCount = 1, UARPGInventory* InInventory = nullptr, FName InSlotName = NAME_None)
: ItemAssetId(InAssetId), GID(InGID), Level(InLevel), Count(InCount), Inventory(InInventory), CurrentSlot(InSlotName)
{}
UPROPERTY(BlueprintReadWrite)
FPrimaryAssetId ItemAssetId;
UPROPERTY(BlueprintReadWrite)
FGuid GID;
//动态数据
UPROPERTY(BlueprintReadWrite)
int Level;
UPROPERTY(BlueprintReadWrite)
int Count;
UPROPERTY(BlueprintReadWrite)
TWeakObjectPtr<UARPGInventory> Inventory;
UPROPERTY(BlueprintReadWrite)
FName CurrentSlot;
}
为每个Character设计Inventory存放拥有的物品GID,装备槽等情况。通过InventorySource接口的设计让AI和玩家可以拥有不同的Inventory并能够被Character使用。Inventory是有Controller创建并持有的,因为Controller是玩家持久化的代表对象而不是Character。
class LEARNARPG_API UARPGInventory : public UObject
{
...
protected:
//Inventory
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ARPGInventory)
TSet<FGuid> OwnedItems;
TMap<FPrimaryAssetId, TSet<FGuid>> ItemsMap;
TMap<FPrimaryAssetType, TSet<FGuid>> TypedItems;
protected:
//Equips
//优化蓝图遍历
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ARPGInventory)
TArray<FARPGItemSlot> Slots;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ARPGInventory)
TMap<FName, int32> SlotMap;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ARPGInventory)
TMap<FName, FGuid> SlottedItems;
public:
FOnInventoryItemUpdateNative OnInventoryItemUpdateNative;
FOnInventorySyncNative OnInventorySyncNative;
FOnSlottedItemUpdateNative OnSlottedItemUpdateNative;
...
};
在逻辑层提供物品的消耗、授予等功能。他会对Inventory和全局数据进行对应的修改,并发起事件。不应该使用除了该逻辑层以外的方法修改Inventory和AssetManager中的全局数据。
class LEARNARPG_API UARPGInventoryLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "ARPGInventory")
static class UARPGItem* GetItemByGID(const FGuid& GID);
UFUNCTION(BlueprintCallable, Category = "ARPGInventory")
static FARPGItemData GetItemDataByGID(const FGuid& GID);
UFUNCTION(BlueprintCallable, Category = "ARPGInventory")
static bool GiveItemsToInventory(UARPGInventory* Inventory, const FGuid& GID, int Count = 0);
UFUNCTION(BlueprintCallable, Category = "ARPGInventory")
static bool ConsumeItemsInInventory(const FGuid& GID, int Count = 0, UARPGInventory* Inventory = nullptr);
UFUNCTION(BlueprintCallable, Category = "ARPGInventory")
static bool SetSlottedItem(UARPGInventory* Inventory, FName SlotName, const FGuid& GID);
UFUNCTION(BlueprintCallable, Category = "ARPGInventory")
static bool RemoveSlottedItem(UARPGInventory* Inventory, FName SlotName);
UFUNCTION(BlueprintCallable, Category = "ARPGInventory")
static class UARPGItem* GetSlottedItem(UARPGInventory* Inventory, FName SlotName);
UFUNCTION(BlueprintCallable, Category = "ARPGInventory")
static TArray<FGameplayAbilitySpecHandle> GetAllSlottedAbilityHandles(AARPGCharacterBase* Character, FName SlotName);
UFUNCTION(BlueprintCallable, Category = "ARPGInventory")
static FGameplayAbilitySpecHandle GetSlottedAbilityHandlesByClass(AARPGCharacterBase* Character, FName SlotName, TSubclassOf<UARPGGameplayAbility> Class);
UFUNCTION(BlueprintCallable, Category = "ARPGInventory")
static void GetSlottedPrimaryAbilityHandleAndInstanceByClass(AARPGCharacterBase* Character, FName SlotName, TSubclassOf<UARPGGameplayAbility> Class, FGameplayAbilitySpecHandle& OutHandle, UARPGGameplayAbility*& OutAbilityInstance);
};
技能系统设计
基本结构
首先,所有的技能基本使用InstancePerActor的实例化策略。这样好处有很多,除了节省性能以外,在整个技能循环当中几乎不需要与CDO交互,所有的操作都可以找到对应的技能实例,因此可以自定义装备和卸下的事件,也可以在技能中与其他技能实例交互,这对于复杂的连招技能有帮助。
使用Slot来存放物品和技能实例,slot存放的物品在Inventory中,slot存放的技能在Character中,这样Inventory专注于物品的管理,而Character专注于技能的give和remove。Character通过监听slot变化事件来装备和卸载技能。
class LEARNARPG_API AARPGCharacterBase : public ACharacter, public IAbilitySystemInterface
{
...
//Equipment
UPROPERTY(BlueprintReadOnly)
UARPGInventory* Inventory;
TMap<FName, TArray<FGameplayAbilitySpecHandle>> SlottedAbility;
...
};
一个武器可能有多个Actor,比如双刀、盾牌和剑,也可能只有一个,所以用PrimaryAsset来统一Actor并不合适,同时一个武器可能有多个技能,比如枪械有开枪、换弹、瞄准,而剑则可以攻击和防御他们之间的按键也不一样,所以需要让技能自行决定释放的按键。
使用PrimaryAsset存放武器所有的GA class,在装备武器时授予技能并设计一个sourceobject存放该物品的GID,这样能够让技能获取到其所在的槽位等信息。
每当技能被装备时,使用一个主要技能负责将该技能期望的Actor生成在Character身上并利用ALS提供的接口设置Overlap。
技能触发
将ASC绑定到Playerinput来实现技能的触发,对于每个技能而言有三种可能的触发方式,一个是按下装备槽对应的按键如攻击、使用物品,一个是按下自己的专属按键如瞄准,另一个是被其他技能触发。
所以在GA中设计了InputID来确定技能触发按键,在授予时通过CDO即可获取。
EARPGInputs InputID = Ability.GetDefaultObject()->InputID;
if(Ability.GetDefaultObject()->bUseSlotInput) InputID = Inventory->GetSlot(SlotName).InputAction;
UARPGAbilitySource* AbilitySource = NewObject<UARPGAbilitySource>();
AbilitySource->SourceItemGID = CurrentItemID;
FGameplayAbilitySpecHandle Handle = AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(Ability, ItemData.Level, static_cast<int32>(InputID), AbilitySource));
AbilityHandles.Add(Handle);
技能循环
对于GE的设计参考了ActionRPG的做法,利用EffectContainer将event tag和effect绑定起来。
0
暂无讨论