Abilities are only supported in a C++ game because Attribute Sets must be a C++ subclass of UAttributeSet
URPGAttributeSet defines attributes for current/max health and mana, attack and defense buffs, movement speed, and a temporary damage attribute used in the damage formula.
Each of these attributes is defined as an FGameplayAttributeData structure, which stores a Base value that is only modified by permanent changes and a Current value which is modified by temporary buffs/debuffs.
在很多游戏中,会有一个Core Attribute Set共享于player 和 enemy, 然后会由此继承一个另外的set用于player
Before attributes are modified, the PreAttributeChange function handles scaling the current health/mana with the max value.After attributes are modified, the PostGameplayEffectExecute function handles clamping and notifying other objects about the changes
The constructor for RPGCharacterBase is responsible for creating the URPGAbilitySystemComponent and URPGAttributeSet subobjects that enables gameplay effects to work.
Also, you may wish to spawn AttributeSets or AbilitySystemComponents when an Actor is first interacted with to avoid object overhead.
ARPGCharacterBase::AddStartupGameplayAbilities where it reads the list of PassiveGameplayEffects from the character Blueprint and applies them, at the current CharacterLevel. If CharacterLevel changes, it removes and re-adds them at the new level
Here is what the GE_StatsBase gameplay effect used for NPCs looks like inside the Unreal Engine 4 (UE4) editor:
在modifier中会读表(maxhealth,characterlevel)获取当前等级maxhealth.
to do damage, the RPGDamageExecution class is used。
The execution calculations consist of two parts, a set of capture declarations and an execution function。
struct RPGDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(DefensePower);
DECLARE_ATTRIBUTE_CAPTUREDEF(AttackPower);
DECLARE_ATTRIBUTE_CAPTUREDEF(Damage);
RPGDamageStatics()
{
// Capture the Target's DefensePower attribute. Do not snapshot it, because we want to use the health value at the moment we apply the execution.
DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, DefensePower, Target, false);
// Capture the Source's AttackPower. We do want to snapshot this at the moment we create the GameplayEffectSpec that will execute the damage.
// (imagine we fire a projectile: we create the GE Spec when the projectile is fired. When it hits the target, we want to use the AttackPower at the moment
// the projectile was launched, not when it hits).
DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, AttackPower, Source, true);
// Also capture the source's raw Damage, which is normally passed in directly via the execution
DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, Damage, Source, true);
}
};
static const RPGDamageStatics& DamageStatics()
{
static RPGDamageStatics DmgStatics;
return DmgStatics;
}
URPGDamageExecution::URPGDamageExecution()
{
RelevantAttributesToCapture.Add(DamageStatics().DefensePowerDef);
RelevantAttributesToCapture.Add(DamageStatics().AttackPowerDef);
RelevantAttributesToCapture.Add(DamageStatics().DamageDef);
}
通过宏去定义capture。在DEFINE_ATTRIBUTE_CAPTUREDEF(AttributeSet, Attribute,Target/Source, true/false(是否snapshot))
上面例如,防御力需要获取目标的并且不能snapshot,因为需要当GE实际发生的时候使用。
之后的攻击力和伤害值都是需要获取source的并且需要snapshot,因为就像射出子弹的一刻攻击力就已经确定,而不能等到子弹已经打到被击者身上。
The capture declaration macros register information with the UE4 Editor, so Gameplay Effects can use the execution in your project.
For each captured attribute, the list of currently active temporary modifiers is captured along with their gameplay tags。
Then in URPGDamageExecution::Execute_Implementation it applies only those modifiers that match the Gameplay Tags that were passed in at effect execution time
After combining those modifiers to get a "calculated" number for Damage, AttackPower, and DefensePower, it turns that into "final" damage using the formula SourceDamage * AttackPower / DefensePower.
float DamageDone = Damage * AttackPower / DefensePower;
if (DamageDone > 0.f)
{
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().DamageProperty, EGameplayModOp::Additive, DamageDone));
}
The final damage then turns into a Health modifier in URPGAttributeSet::PostGameplayEffectExecute
Melee Abilities In ARPG
GA_MeleeBase is the base for all the Melee abilities:
ActivateAbility is called when an ability starts, and CommitAbility is used to commit the Cost (Mana for ARPG generally) and Cooldown for an ability. EndAbility is called to tell the system that the ability is done executing.
PlayMontageandWaitForEvent is an AbilityTask node, and corresponds to URPGAbilityTask_PlayMontageAndWaitForEvent
AbilityTasks are special objects that define a set of static functions to create the task (PlayMontageAndWaitForEvent in this case), variables and functions used to execute the task
The way these tasks work is by first playing a Montage and then listening for a Gameplay Event to be emitted from the AbilitySystemComponent. If an emitted Gameplay Event matches the passed in EventTags it will activate the EventReceived execution pin with a tag and a payload that will then call the ApplyEffectContainer function.
when the weapon actor overlaps a character.it constructs a GameplayEventData payload and passes in a Target Actor + Instigator.
It then sends a gameplay event using a tag set by an Anim Notify state placed in a Montage
when that event is triggered, the ability graph will then activate the EventReceived execution pin
The ApplyEffectContainer node corresponds to URPGGameplayAbility::ApplyEffectContainer, which applies a set of gameplay effects.
Each URPGGameplayAbility has a map of tags to FRPGGameplayEffectContainer structures
contain target information and a list of Gameplay Effects to apply.
When the AM_Attack_Axe montage is executed, it indicates that when the Event.Montage.Shared.WeaponHit GameplayEvent is emitted it will execute the GE_PlayerAxeMelee effect, using the targeting class RPGTargetType_UseEventData. That target type is implemented in native C++ in the RPGTargetType.cpp file and extracts the target Actors and the hit results from the passed in EventData. The second element in that map executes the BurstPound** special attack, which is a skill that will be described later.
The ApplyEffectContainer does two things: First, it looks for a FRPGGameplayEffectContainer inside this map that matches the passed in tag. If it finds one, it creates a FRPGGameplayEffectContainerSpec containing TargetData and a list of EffectSpecs.
Then, it applies that ContainerSpec, which will do the actual damage to the target. Target data is a FGameplayAbilityTargetDataHandle that points to an array of polymorphic target structures that contain hit results, Actors, or something else that is game specific.
Each game using the Ability System will probably want to implement a system similar to ApplyEffectContainer, because it makes it easy to put Blueprint logic in a parent ability while placing the list of effects to execute in child Blueprints
However, each game will be slightly different, and if your game includes client prediction targeting it will be much more complicated than the version used in ARPG.