RPGCharacter主要负责Gameplay Ability System部分。直接上代码:
头文件:
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ActionRPG.h"
#include "GameFramework/Character.h"
#include "UObject/ScriptInterface.h"
#include "RPGInventoryInterface.h"
#include "AbilitySystemInterface.h"
#include "Abilities/RPGAbilitySystemComponent.h"
#include "Abilities/RPGAttributeSet.h"
#include "RPGCharacterBase.generated.h"
class URPGGameplayAbility;
class UGameplayEffect;
/** Base class for Character, Designed to be blueprinted */
UCLASS()
class ACTIONRPG_API ARPGCharacterBase : public ACharacter, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
// Constructor and overrides
ARPGCharacterBase();
/** Controller -------------------------------------------------------------------------------------------------- */
//角色被controller possess时调用:绑定背包的delegate,更新ability system component信息
virtual void PossessedBy(AController* NewController) override;
virtual void UnPossessed() override;
virtual void OnRep_Controller() override;
/** -------------------------------------------------------------------------------------------------- */
// replicate property
virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override;
// Implement IAbilitySystemInterface
UAbilitySystemComponent* GetAbilitySystemComponent() const override;
#pragma region Attributes Getter ( 属性getter方法 )
/** Returns current health, will be 0 if dead */
UFUNCTION(BlueprintCallable)
float GetHealth() const;
/** Returns maximum health, health will never be greater than this */
UFUNCTION(BlueprintCallable)
float GetMaxHealth() const;
/** Returns current mana */
UFUNCTION(BlueprintPure)
float GetMana() const;
/** Returns maximum mana, mana will never be greater than this */
UFUNCTION(BlueprintCallable)
float GetMaxMana() const;
/** Returns current movement speed */
UFUNCTION(BlueprintCallable)
float GetMoveSpeed() const;
#pragma endregion
/** Returns the character level that is passed to the ability system */
UFUNCTION(BlueprintCallable)
int32 GetCharacterLevel() const;
/** Modifies the character level, this may change abilities. Returns true on success */
UFUNCTION(BlueprintCallable)
bool SetCharacterLevel(int32 NewLevel);
/**
* Attempts to activate any ability in the specified item slot. Will return false if no activatable ability found or activation fails
* Returns true if it thinks it activated, but it may return false positives due to failure later in activation.
* If bAllowRemoteActivation is true, it will remotely activate local/server abilities, if false it will only try to locally activate the ability
*/
// 尝试激活item slot里对应道具的ability
UFUNCTION(BlueprintCallable, Category = "Abilities")
bool ActivateAbilitiesWithItemSlot(FRPGItemSlot ItemSlot, bool bAllowRemoteActivation = true);
/** Returns a list of active abilities bound to the item slot. This only returns if the ability is currently running */
//返回特定slot中正在生效中的ability list
UFUNCTION(BlueprintCallable, Category = "Abilities")
void GetActiveAbilitiesWithItemSlot(FRPGItemSlot ItemSlot, TArray& ActiveAbilities);
/**
* Attempts to activate all abilities that match the specified tags
* Returns true if it thinks it activated, but it may return false positives due to failure later in activation.
* If bAllowRemoteActivation is true, it will remotely activate local/server abilities, if false it will only try to locally activate the ability
*/
// 通过tag来激活activity
UFUNCTION(BlueprintCallable, Category = "Abilities")
bool ActivateAbilitiesWithTags(FGameplayTagContainer AbilityTags, bool bAllowRemoteActivation = true);
/** Returns a list of active abilities matching the specified tags. This only returns if the ability is currently running */
UFUNCTION(BlueprintCallable, Category = "Abilities")
void GetActiveAbilitiesWithTags(FGameplayTagContainer AbilityTags, TArray& ActiveAbilities);
/** Returns total time and remaining time for cooldown tags. Returns false if no active cooldowns found */
UFUNCTION(BlueprintCallable, Category = "Abilities")
bool GetCooldownRemainingForTag(FGameplayTagContainer CooldownTags, float& TimeRemaining, float& CooldownDuration);
protected:
/** The level of this character, should not be modified directly once it has already spawned */
UPROPERTY(EditAnywhere, Replicated, Category = "Abilities")
int32 CharacterLevel;
/** Abilities to grant to this character on creation. These will be activated by tag or event and are not bound to specific inputs */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Abilities")
TArray> GameplayAbilities;
/** Map of item slot to gameplay ability class, these are bound before any abilities added by the inventory */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Abilities")
TMap> DefaultSlottedAbilities;
/** Passive gameplay effects applied on creation */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Abilities")
TArray> PassiveGameplayEffects;
/** The component used to handle ability system interactions */
UPROPERTY()
URPGAbilitySystemComponent* AbilitySystemComponent;
/** List of attributes modified by the ability system */
UPROPERTY()
URPGAttributeSet* AttributeSet;
/** Cached pointer to the inventory source for this character, can be null */
UPROPERTY()
TScriptInterface InventorySource;
/** If true we have initialized our abilities */
UPROPERTY()
int32 bAbilitiesInitialized;
/** Map of slot to ability granted by that slot. I may refactor this later */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Inventory)
TMap SlottedAbilities;
/** Delegate handles */
FDelegateHandle InventoryUpdateHandle;
FDelegateHandle InventoryLoadedHandle;
/**
* Called when character takes damage, which may have killed them
*
* @param DamageAmount Amount of damage that was done, not clamped based on current health
* @param HitInfo The hit info that generated this damage
* @param DamageTags The gameplay tags of the event that did the damage
* @param InstigatorCharacter The character that initiated this damage
* @param DamageCauser The actual actor that did the damage, might be a weapon or projectile
*/
UFUNCTION(BlueprintImplementableEvent)
void OnDamaged(float DamageAmount, const FHitResult& HitInfo, const struct FGameplayTagContainer& DamageTags, ARPGCharacterBase* InstigatorCharacter, AActor* DamageCauser);
/**
* Called when health is changed, either from healing or from being damaged
* For damage this is called in addition to OnDamaged/OnKilled
*
* @param DeltaValue Change in health value, positive for heal, negative for cost. If 0 the delta is unknown
* @param EventTags The gameplay tags of the event that changed mana
*/
UFUNCTION(BlueprintImplementableEvent)
void OnHealthChanged(float DeltaValue, const struct FGameplayTagContainer& EventTags);
/**
* Called when mana is changed, either from healing or from being used as a cost
*
* @param DeltaValue Change in mana value, positive for heal, negative for cost. If 0 the delta is unknown
* @param EventTags The gameplay tags of the event that changed mana
*/
UFUNCTION(BlueprintImplementableEvent)
void OnManaChanged(float DeltaValue, const struct FGameplayTagContainer& EventTags);
/**
* Called when movement speed is changed
*
* @param DeltaValue Change in move speed
* @param EventTags The gameplay tags of the event that changed mana
*/
UFUNCTION(BlueprintImplementableEvent)
void OnMoveSpeedChanged(float DeltaValue, const struct FGameplayTagContainer& EventTags);
/** Called when slotted items change, bound to delegate on interface */
void OnItemSlotChanged(FRPGItemSlot ItemSlot, URPGItem* Item);
void RefreshSlottedGameplayAbilities();
/** Apply the startup gameplay abilities and effects */
void AddStartupGameplayAbilities();
/** Attempts to remove any startup gameplay abilities */
void RemoveStartupGameplayAbilities();
/** Adds slotted item abilities if needed */
void AddSlottedGameplayAbilities();
/** Fills in with ability specs, based on defaults and inventory */
void FillSlottedAbilitySpecs(TMap& SlottedAbilitySpecs);
/** Remove slotted gameplay abilities, if force is false it only removes invalid ones */
void RemoveSlottedGameplayAbilities(bool bRemoveAll);
// Called from RPGAttributeSet, these call BP events above
virtual void HandleDamage(float DamageAmount, const FHitResult& HitInfo, const struct FGameplayTagContainer& DamageTags, ARPGCharacterBase* InstigatorCharacter, AActor* DamageCauser);
virtual void HandleHealthChanged(float DeltaValue, const struct FGameplayTagContainer& EventTags);
virtual void HandleManaChanged(float DeltaValue, const struct FGameplayTagContainer& EventTags);
virtual void HandleMoveSpeedChanged(float DeltaValue, const struct FGameplayTagContainer& EventTags);
// Friended to allow access to handle functions above
// 上面所有的Handle XXX函数,都会在URPGAttributeSet中被调用
friend URPGAttributeSet;
};
头文件的public方法表明,我们可以通过item slot和gameplay tag来activate ability。
character具有初始的ability list,并且负责所有属性变化的回调。
源文件:
只摘取部分讲解:
构造函数
注意在此处创建AbilitySystemComponent
和AttributeSet
作为Character的SubObject。
ARPGCharacterBase::ARPGCharacterBase()
{
// Create ability system component, and set it to be explicitly replicated
AbilitySystemComponent = CreateDefaultSubobject(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
// Create the attribute set, this replicates by default
AttributeSet = CreateDefaultSubobject(TEXT("AttributeSet"));
CharacterLevel = 1;
bAbilitiesInitialized = false;
}
实现GetAbilitySystemComponent()
接口
UAbilitySystemComponent* ARPGCharacterBase::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
初始阶段:赋予Ability、触发被动effect
void ARPGCharacterBase::AddStartupGameplayAbilities()
{
check(AbilitySystemComponent);
// 确认是否在服务端
if (Role == ROLE_Authority && !bAbilitiesInitialized)
{
// Grant abilities, but only on the server
for (TSubclassOf& StartupAbility : GameplayAbilities)
{
//赋予技能(技能是必须被赋予后才能激活的)
AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(StartupAbility, GetCharacterLevel(), INDEX_NONE, this));
}
// Now apply passives
for (TSubclassOf& GameplayEffect : PassiveGameplayEffects)
{
// 创建effect context ,即effect的施放者,施放者位置等等其他的数据
FGameplayEffectContextHandle EffectContext = AbilitySystemComponent->MakeEffectContext();
EffectContext.AddSourceObject(this);
//生成gameplay effect spec供应用
FGameplayEffectSpecHandle NewHandle = AbilitySystemComponent->MakeOutgoingSpec(GameplayEffect, GetCharacterLevel(), EffectContext);
if (NewHandle.IsValid())
{
//正式的激活效果
FActiveGameplayEffectHandle ActiveGEHandle = AbilitySystemComponent->ApplyGameplayEffectSpecToTarget(*NewHandle.Data.Get(), AbilitySystemComponent);
}
}
AddSlottedGameplayAbilities();
bAbilitiesInitialized = true;
}
}
激活item slot里的技能
bool ARPGCharacterBase::ActivateAbilitiesWithItemSlot(FRPGItemSlot ItemSlot, bool bAllowRemoteActivation)
{
FGameplayAbilitySpecHandle* FoundHandle = SlottedAbilities.Find(ItemSlot);
if (FoundHandle && AbilitySystemComponent)
{
return AbilitySystemComponent->TryActivateAbility(*FoundHandle, bAllowRemoteActivation);
}
return false;
}
通过tag来激活技能
bool ARPGCharacterBase::ActivateAbilitiesWithTags(FGameplayTagContainer AbilityTags, bool bAllowRemoteActivation)
{
if (AbilitySystemComponent)
{
return AbilitySystemComponent->TryActivateAbilitiesByTag(AbilityTags, bAllowRemoteActivation);
}
return false;
}
还有几个ability system component的函数值得注意:
- 使用effect query来获取效果的持续时间
FGameplayEffectQuery const Query = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(CooldownTags);
TArray< TPair > DurationAndTimeRemaining = AbilitySystemComponent->GetActiveEffectsTimeRemainingAndDuration(Query);
- 利用handle返回ability spec
FGameplayAbilitySpec* FoundSpec = AbilitySystemComponent->FindAbilitySpecFromHandle(*FoundHandle);