一些说明
GamePlay Ability 一般称为技能。缩写GA
GamePlay Effect 一般称为效果,其他技能系统可能直接就叫buff了,所需一般叫GE
https://gitee.com/weilin3101/teampleta-gas
如何用GAS注册并释放一个技能
创建一个Ability
UE5.2.1
必要插件:
体验优化插件,蓝图格式化,蓝图截屏(免费)
build.cs如果没有记得加上"GameplayAbilities", "GameplayTags", "GameplayTasks"
右键创建技能
添加GamePlayTag。相当于一个字符串类型的技能ID。
也可以在项目设置->gameplay tag 这里添加
然后我们创建一个Actor
需要一个static mesh 随便拿一个模型表示一下,关掉碰撞
那一个box collision,碰撞只查询,其他以后弄
加一个projectile movement component。让box设置一下初速度,其他随便比如重力设置。
然后开始打开GA_FireBox 开始写技能。这个技能在角色前方向生成一个Box,1s后技能结束
注册技能
打开角色character的蓝图。我们需要给角色加上这个组件
然后注册技能并释放。及其三种释放技能的方式
注册并马上释放仅仅执行一次
第二次想再拿这个handle 激活是无效的。可以用于做一些低频行为。这个技能是不是角色的技能,是一个采集行为这种不需要一直挂在角色身上。
GameplayAbility_Montage
继承自 GameplayAbility。他的Tag“CWL.Skill.FireBoxEx”
我这里还是第三人称模板包,所以我们随便创建一个跳跃蒙太奇丢这里
技能里面只延迟
角色蓝图直接快速释放
效果按下1,角色跳了一下。同理可以换成攻击动画。
创建蓝图,基础自GameplayEffect。叫GE_Damage。
Effect可以理解为buff,或者事件回调的概念。
我们这个技能效果可能是打出了xx伤害,播放了一个特效,给谁加了防御等等。
上面的GameplayAbility_Montage 刚刚好有一个加Effect的地方。
这个Effect可以,直接编辑角色的的属性(到这里还没讲GAS的属性)比如扣血20等
其他的可以直接去看源码,激活技能的时候 ApplyGameplayEffectToSelf 触发effect。蒙太奇结束的时候移除。
GameplayAbility_Jump
创建一个 Jump技能, 并改一下箭头位置,不改会警告然后再让你改。
直接释放
基本就是官方给出的学习模板了
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Abilities/GameplayAbility_CharacterJump.h"
#include "GameFramework/Character.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GameplayAbility_CharacterJump)
// --------------------------------------------------------------------------------------------------------------------------------------------------------
//
// UGameplayAbility_CharacterJump
//
// --------------------------------------------------------------------------------------------------------------------------------------------------------
UGameplayAbility_CharacterJump::UGameplayAbility_CharacterJump(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
InstancingPolicy = EGameplayAbilityInstancingPolicy::NonInstanced;
}
void UGameplayAbility_CharacterJump::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
if (HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo))
{
if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
{
return;
}
ACharacter * Character = CastChecked<ACharacter>(ActorInfo->AvatarActor.Get());
Character->Jump();
}
}
void UGameplayAbility_CharacterJump::InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
if (ActorInfo != NULL && ActorInfo->AvatarActor != NULL)
{
CancelAbility(Handle, ActorInfo, ActivationInfo, true);
}
}
bool UGameplayAbility_CharacterJump::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, OUT FGameplayTagContainer* OptionalRelevantTags) const
{
if (!Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags))
{
return false;
}
const ACharacter* Character = CastChecked<ACharacter>(ActorInfo->AvatarActor.Get(), ECastCheckedType::NullAllowed);
return (Character && Character->CanJump());
}
/**
* Canceling an non instanced ability is tricky. Right now this works for Jump since there is nothing that can go wrong by calling
* StopJumping() if you aren't already jumping. If we had a montage playing non instanced ability, it would need to make sure the
* Montage that *it* played was still playing, and if so, to cancel it. If this is something we need to support, we may need some
* light weight data structure to represent 'non intanced abilities in action' with a way to cancel/end them.
*/
void UGameplayAbility_CharacterJump::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility)
{
if (ScopeLockCount > 0)
{
WaitingToExecute.Add(FPostLockDelegate::CreateUObject(this, &UGameplayAbility_CharacterJump::CancelAbility, Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility));
return;
}
Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility);
ACharacter * Character = CastChecked<ACharacter>(ActorInfo->AvatarActor.Get());
Character->StopJumping();
}
FGameplayAbilityActorInfo
在Ability可以调用GetActorInfo() 获取谁释放的技能。及其其他信息。在1.1 中我们从中获取了施法者Actor。
/**
* FGameplayAbilityActorInfo
*
* Cached data associated with an Actor using an Ability.
* -Initialized from an AActor* in InitFromActor
* -Abilities use this to know what to actor upon. E.g., instead of being coupled to a specific actor class.
* -These are generally passed around as pointers to support polymorphism.
* -Projects can override UAbilitySystemGlobals::AllocAbilityActorInfo to override the default struct type that is created.
*
*/
USTRUCT(BlueprintType)
struct GAMEPLAYABILITIES_API FGameplayAbilityActorInfo
{
GENERATED_USTRUCT_BODY()
virtual ~FGameplayAbilityActorInfo() {}
/** The actor that owns the abilities, shouldn't be null */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<AActor> OwnerActor;
/** The physical representation of the owner, used for targeting and animation. This will often be null! */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<AActor> AvatarActor;
/** PlayerController associated with the owning actor. This will often be null! */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<APlayerController> PlayerController;
/** Ability System component associated with the owner actor, shouldn't be null */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
/** Skeletal mesh of the avatar actor. Often null */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<USkeletalMeshComponent> SkeletalMeshComponent;
/** Anim instance of the avatar actor. Often null */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<UAnimInstance> AnimInstance;
/** Movement component of the avatar actor. Often null */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
TWeakObjectPtr<UMovementComponent> MovementComponent;
/** The linked Anim Instance that this component will play montages in. Use NAME_None for the main anim instance. */
UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
FName AffectedAnimInstanceTag;
/** Accessor to get the affected anim instance from the SkeletalMeshComponent */
UAnimInstance* GetAnimInstance() const;
/** Returns true if this actor is locally controlled. Only true for players on the client that owns them (differs from APawn::IsLocallyControlled which requires a Controller) */
bool IsLocallyControlled() const;
/** Returns true if this actor has a PlayerController that is locally controlled. */
bool IsLocallyControlledPlayer() const;
/** Returns true if the owning actor has net authority */
bool IsNetAuthority() const;
/** Initializes the info from an owning actor. Will set both owner and avatar */
virtual void InitFromActor(AActor *OwnerActor, AActor *AvatarActor, UAbilitySystemComponent* InAbilitySystemComponent);
/** Sets a new avatar actor, keeps same owner and ability system component */
virtual void SetAvatarActor(AActor *AvatarActor);
/** Clears out any actor info, both owner and avatar */
virtual void ClearActorInfo();
};
如何造成伤害 & Attribute
到这里就不能纯蓝图了,部分换C++
如果报错了检查这里。build.cs记得加上"GameplayAbilities", "GameplayTags", "GameplayTasks"
由于某种原因项目炸了,下面的部分是我又新建了一个项目。
新建项目:
这是一个技能。Tag 是 WL.Skill.FireBox
注意这里Spawn Actor的Instigator需要填,以便于生成的碰撞体盒子,能够获取谁生成的。
然后是这个盒子,通过Static Mesh 作为碰撞查询,Projectile 用于发射。
盒子打到人的时候我们打印一个 -10
然后按键1发射技能
大概是这个效果,发射一个蓝Box,击中打印-10
然后我们把打印-10改掉。
我们把打印-10改成了对目标照成一个效果。只是这个效果是扣血。
这个效果叫GameplayEffect。左右在Attribute上。我们先需要创建一个Attribute类。
Attribute值的是角色属性,生命,物理攻击,魔法攻击等数值,我们自己用变量维护当然可以。
接入虚幻的优势在于,我们可以创建GamplayEffect,并在编辑器中直接编辑属性。
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "T_GAS/DataTable/WCharacterDataTable.h"
#include "WAttributeSet.generated.h"
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
/**
*
*/
UCLASS()
class T_GAS_API UWAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
virtual bool PreGameplayEffectExecute(FGameplayEffectModCallbackData& Data) override;
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UFUNCTION(BlueprintCallable, Category="Attribute")
virtual void RegisterProperties(const FWCharacterDataTable& InData);
protected:
static void RegisterParam(FGameplayAttributeData& InAttributeData, const float InValue);
UFUNCTION()
virtual void OnRep_Level(const FGameplayAttributeData& OldLevel);
UFUNCTION()
virtual void OnRep_PhysicsAttack(const FGameplayAttributeData& OldPhysicsAttack);
UFUNCTION()
virtual void OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth);
UFUNCTION()
virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);
UFUNCTION()
virtual void OnRep_Damage(const FGameplayAttributeData& OldDamage);
public:
UPROPERTY(BlueprintReadOnly, Category="Attribute", ReplicatedUsing=OnRep_Level)
FGameplayAttributeData Level;
ATTRIBUTE_ACCESSORS(UWAttributeSet, Level)
UPROPERTY(BlueprintReadOnly, Category="Attribute", ReplicatedUsing=OnRep_PhysicsAttack)
FGameplayAttributeData PhysicsAttack;
ATTRIBUTE_ACCESSORS(UWAttributeSet, PhysicsAttack)
UPROPERTY(BlueprintReadOnly, Category="Attribute", ReplicatedUsing=OnRep_MaxHealth)
FGameplayAttributeData MaxHealth;
ATTRIBUTE_ACCESSORS(UWAttributeSet, MaxHealth)
UPROPERTY(BlueprintReadOnly, Category="Attribute", ReplicatedUsing=OnRep_Health)
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UWAttributeSet, Health)
UPROPERTY(BlueprintReadOnly, Category="Attribute", ReplicatedUsing=OnRep_Damage)
FGameplayAttributeData Damage;
ATTRIBUTE_ACCESSORS(UWAttributeSet, Damage)
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "WAttributeSet.h"
#include "GameplayEffectExtension.h"
#include "Net/UnrealNetwork.h"
bool UWAttributeSet::PreGameplayEffectExecute(FGameplayEffectModCallbackData& Data)
{
return Super::PreGameplayEffectExecute(Data);
}
void UWAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetDamageAttribute())
{
const float Value = GetDamage();
SetHealth(FMath::Clamp(GetHealth() - Value, 0.0f, GetMaxHealth()));
}
}
void UWAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UWAttributeSet, Level);
DOREPLIFETIME(UWAttributeSet, PhysicsAttack);
DOREPLIFETIME(UWAttributeSet, MaxHealth);
DOREPLIFETIME(UWAttributeSet, Health);
DOREPLIFETIME(UWAttributeSet, Damage);
}
void UWAttributeSet::RegisterProperties(const FWCharacterDataTable& InData)
{
RegisterParam(Level, 1);
RegisterParam(Health, InData.Health);
RegisterParam(MaxHealth, InData.Health);
RegisterParam(PhysicsAttack, InData.PhysicsAttack);
}
void UWAttributeSet::RegisterParam(FGameplayAttributeData& InAttributeData, const float InValue)
{
InAttributeData.SetBaseValue(InValue);
InAttributeData.SetCurrentValue(InValue);
}
void UWAttributeSet::OnRep_Level(const FGameplayAttributeData& OldLevel)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UWAttributeSet, Level, OldLevel);
}
void UWAttributeSet::OnRep_PhysicsAttack(const FGameplayAttributeData& OldPhysicsAttack)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UWAttributeSet, PhysicsAttack, OldPhysicsAttack);
}
void UWAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UWAttributeSet, MaxHealth, OldMaxHealth);
}
void UWAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UWAttributeSet, Health, OldHealth);
}
void UWAttributeSet::OnRep_Damage(const FGameplayAttributeData& OldDamage)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UWAttributeSet, Damage, OldDamage);
}
同时我们还有一张配置表,在BeginPlay的时候读取并设置角色的属性值。比较简单,不展开
void UWAttributeSet::RegisterProperties(const FWCharacterDataTable& InData)
{
RegisterParam(Level, 1);
RegisterParam(Health, InData.Health);
RegisterParam(MaxHealth, InData.Health);
RegisterParam(PhysicsAttack, InData.PhysicsAttack);
}
void UWAttributeSet::RegisterParam(FGameplayAttributeData& InAttributeData, const float InValue)
{
InAttributeData.SetBaseValue(InValue);
InAttributeData.SetCurrentValue(InValue);
}
然后需要一个角色血条
UI代码随便写一下
来看我们创建的GameplayEffect。效果是对属性Damage进行Add 10的操作。上文我们apply 一个 effect to target 的时候,我们填了一个effect就是这个。
然后代码会跑到这里,更新血量,然后UI实时更新数据。
void UWAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetDamageAttribute())
{
const float Value = GetDamage();
SetHealth(FMath::Clamp(GetHealth() - Value, 0.0f, GetMaxHealth()));
}
}
到此我们实现了一个释放技能并把对方打扣血的一个流程。
AbilityTask_WaitGameplayEvent
上面的代码有一点不好,apply的代码写在了碰撞这边。
所以我们Ability里面改成这样。这里有一个wait event
碰撞检测那边改成send event
本质是这个 UAbilityTask_WaitGameplayEvent
PS: 上面是测试逻辑,实际上如果攻击打空了,碰撞体销毁了,也需要EndAbility。
在技能wait击中事件的地方后,wait销毁事件
技能消耗
属性里面加上魔法值属性
UPROPERTY(BlueprintReadOnly, Category="Attribute", ReplicatedUsing=OnRep_MaxMana)
FGameplayAttributeData MaxMana;
ATTRIBUTE_ACCESSORS(UWAttributeSet, MaxMana)
UPROPERTY(BlueprintReadOnly, Category="Attribute", ReplicatedUsing=OnRep_Mana)
FGameplayAttributeData Mana;
ATTRIBUTE_ACCESSORS(UWAttributeSet, Mana)
然后简单做个蓝条
在技能的右下角有一个消耗的 Gameplay Effect。
我们创建这个effect,并写上mana扣10
记得在技能的开头提交技能,CommitAbility内部会进行cost是否成功的计算。
冷却时间
在屏幕右小角创建一个CD文本。
创建一个GE,区间,4s
并且这里有一个标签。
ability配置里面有一个CD,填上
受击效果 GameplayCue
创建标签,并添加处理器。我这里创建的是static的处理器。
然后在effect里面这样配置,当damage触发的时候,触发这个cue。这里标签,定位到了刚刚我们创建的Cue。
然后我们在Hit里面重写接口,创建一个特效
数值计算 ExecutionCalculation
void UWAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetDamageAttribute())
{
const float Value = GetDamage();
SetHealth(FMath::Clamp(GetHealth() - Value, 0.0f, GetMaxHealth()));
}
}
我们每一个属性变更都需要在这里写代码,GAS支持把这一部分独立为一个单独的对象。
在GE的这里,可以填很多个 Executions
这里类我们用C++写
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameplayEffectExecutionCalculation.h"
#include "WExecCalDamage.generated.h"
/**
*
*/
UCLASS()
class T_GAS_API UWExecCalDamage : public UGameplayEffectExecutionCalculation
{
GENERATED_BODY()
public:
UWExecCalDamage();
virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "WExecCalDamage.h"
#include "T_GAS/Ability/Attribute/WAttributeSet.h"
struct FExecCalDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(PhysicsAttack);
DECLARE_ATTRIBUTE_CAPTUREDEF(Health);
DECLARE_ATTRIBUTE_CAPTUREDEF(Damage);
FExecCalDamageStatics()
{
DEFINE_ATTRIBUTE_CAPTUREDEF(UWAttributeSet, PhysicsAttack, Source, false);
DEFINE_ATTRIBUTE_CAPTUREDEF(UWAttributeSet, Damage, Source, true);
DEFINE_ATTRIBUTE_CAPTUREDEF(UWAttributeSet, Health, Target, true);
}
};
UWExecCalDamage::UWExecCalDamage()
{
FExecCalDamageStatics ExecCalDamage;
RelevantAttributesToCapture.Add(ExecCalDamage.PhysicsAttackDef);
RelevantAttributesToCapture.Add(ExecCalDamage.DamageDef);
RelevantAttributesToCapture.Add(ExecCalDamage.HealthDef);
// InvalidScopedModifierAttributes.Add(ExecCalDamage.PhysicsAttackDef);
// InvalidScopedModifierAttributes.Add(ExecCalDamage.DamageDef);
InvalidScopedModifierAttributes.Add(ExecCalDamage.HealthDef);
}
void UWExecCalDamage::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
// 执行顺序在 PostGameplayEffectExecute 后
FExecCalDamageStatics ExecCalDamage;
UAbilitySystemComponent* TargetASC = ExecutionParams.GetTargetAbilitySystemComponent();
UAbilitySystemComponent* SourceASC = ExecutionParams.GetSourceAbilitySystemComponent();
const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = SourceTags;
EvaluationParameters.TargetTags = TargetTags;
float Health = 0.0f;
float PhysicsAttack = 0.0f;
float Damage = 0.0f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(ExecCalDamage.HealthDef, EvaluationParameters, Health);
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(ExecCalDamage.PhysicsAttackDef, EvaluationParameters, PhysicsAttack);
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(ExecCalDamage.DamageDef, EvaluationParameters, Damage);
float FinalDamage = Damage + PhysicsAttack;
FinalDamage = FMath::Clamp(FinalDamage, 0.0f, Health);
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(ExecCalDamage.HealthProperty, EGameplayModOp::Additive, -FinalDamage));
}
可以注意到 ExecutionCalculation 可以加很多个,可以支持其他效果,比如卡肉。
比如
void UExecCalKa::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
UAbilitySystemComponent* TargetASC = ExecutionParams.GetTargetAbilitySystemComponent();
UAbilitySystemComponent* SourceASC = ExecutionParams.GetSourceAbilitySystemComponent();
if (AT_GASCharacter* InCharacter = Cast<AT_GASCharacter>(SourceASC->GetOwner()))
{
InCharacter->DelayFrame(0.1f);
}
}
然后在角色身上用异步触发一下pause true 和 false
void AT_GASCharacter::DelayFrame(const float InTime)
{
if (APlayerController* InPlayerController = Cast<APlayerController>(GetController()))
{
InPlayerController->SetPause(true);
}
}
Condition GE
effect可以再次触发effect。
比如一个技能打到怪,又触发了一次二次伤害。
下面可以填一个tag。
比如触发这个damage的tag的 skill.fire。并且这个二次effect的tag也是这个。那么就可以连锁触发。否则就不会连锁触发。
技能释放的条件和限制
(支持前缀匹配,比如TAG是,WL.Skill 那么 WL.Skill.A, WL.SKill.B 都生效)
- Cancel Ability With Tag
- 释放当前技能的时候会,取消这个tag的释放
- Block Ability With Tag
- 释放这个技能的时候,对于tag的技能都不能释放
A技能释放后,才可以释放B技能,才可以释放C技能如何配置
- Activation Owned Tag
- 在技能开始到结束之间,可以用这个标签加一个标记。
- Activation Required Tag
- 和上面联用,有某个标记的时候才能激活这个技能。
- Activation Blocked Tag
- 我是用了A技能,加了一个buff。另一个技能判断拥有这个buff,就不会释放了
GE Custom Calculation
- 我是用了A技能,加了一个buff。另一个技能判断拥有这个buff,就不会释放了
在damage的这里,我们上面是直接写了一个-10。
其实可以选成一个自定义的计算过程。
这里的数值也可以用Set By Caller
如果这样,技能释放的时候就需要提前设置caller
GE
- 一次性触发
- 无限,比如穿上装备的时候增加x属性,脱下的时候去掉
- 周期性触发
案例:周期时间内回血
创建GE
方便测试,直接把Damage换成了AddHP
并把初始血量调低
打中回血的效果
其中,这里是周期,第一次触发释放执行
第三个比较复杂。有另一个免疫标签。
比如0~10s内的一个持续性伤害。
1.5~5.5s内有了一个免疫buff。这个免疫区间内的伤害是不生效的。过了这个区间后。剩下的周期内扣血如何表现,就是这个选项。具体需要和免疫标签配合
Buff多次叠加
以上面的加血buff为例。
我触发了一个10s的加血buff。2s的时候有被触发了一个10s。
那么:
- 两个buff是相互独立,各自作用10s?也就是0~10s 加了100血,2s到12s又加了100血,总共加200血。(上面的Stacking Type None)
如果选by Source 和 Target,并且limit不是0,是2
首先Source和 Target 有什么区别呢?
如果1和2,都给3加了两层buff。如果按照target的叠层算。那么数量就是4,超出2。叠加不上去。
如果按Source算,1和2能叠加成功。
- Stack Duration Fresh Policy: Refresh On Successful Application
Buff A 持续时间0~5s,第3s又叠了一层。那么buff持续事件为 3+5。
如果不勾选刷新,那么第一个buff的5s结束的时候两个buff都会消失。
- Stack Period Reset Policy: Reset On Successful Application
0~5s触发一个buff。并且在整数秒的时候触发伤害。
在1.5s触发了另一个buff叠加。如果不Reset。那么触发点还是2s,3s。。。
如果reset。那么就是1.5s 重新触发周期。1.5, 2.5 ,3.5 。。。
比如buff叠加了很多层。
第一个buff周期结束的时候,这里buff叠加了很多层。
- 全部清空
- 移除单层buff,并刷新周期
- 刷新周期