文章目录
- 一、CanActivateAbility
- 二、ShouldActivateAbility、ShouldAbilityRespondToEvent
- 三、GetCooldownTimeRemaining、GetCooldownTimeRemaining、GetCooldownTimeRemainingAndDuration
- 三、DoesAbilitySatisfyTagRequirements
- 四、IsBlockingOtherAbilities
- 五、SetShouldBlockOtherAbilities
-
- 详细说明:
-
- [1. 条件检查](#1. 条件检查)
- [2. 核心功能](#2. 核心功能)
- 具体例子说明:
- 标签系统的工作原理:
-
- [BlockAbilitiesWithTag 的使用](#BlockAbilitiesWithTag 的使用)
- [CancelAbilitiesWithTag 的使用](#CancelAbilitiesWithTag 的使用)
- 实际应用场景:
- 五、GetCooldownTags()
-
- 详细说明:
-
- [1. 冷却系统的工作原理](#1. 冷却系统的工作原理)
- [2. 方法解析](#2. 方法解析)
- 具体例子说明:
- 实际应用场景
-
- [场景1:UI 冷却显示](#场景1:UI 冷却显示)
- 场景2:条件性能力激活
- 场景3:冷却减少效果
- 场景4:冷却状态同步(多人游戏)
一、CanActivateAbility
cpp
bool UGameplayAbility::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, OUT FGameplayTagContainer* OptionalRelevantTags) const
{
// Don't set the actor info, CanActivate is called on the CDO
// 注意:这个方法是在CDO(Class Default Object)上调用的,不会设置actor信息
// A valid AvatarActor is required. Simulated proxy check means only authority or autonomous proxies should be executing abilities.
// 需要有效的AvatarActor(角色actor)。模拟代理检查意味着只有权威端或自主代理才能执行能力
AActor* const AvatarActor = ActorInfo ? ActorInfo->AvatarActor.Get() : nullptr;
if (AvatarActor == nullptr || !ShouldActivateAbility(AvatarActor->GetLocalRole()))
{
return false; // 如果没有AvatarActor或角色权限不允许激活,返回false
}
//make into a reference for simplicity
// 创建一个静态的临时标签容器用于简化操作
static FGameplayTagContainer DummyContainer;
DummyContainer.Reset();
// make sure the ability system component is valid, if not bail out.
// 确保能力系统组件有效
UAbilitySystemComponent* const AbilitySystemComponent = ActorInfo->AbilitySystemComponent.Get();
if (!AbilitySystemComponent)
{
return false; // 没有有效的能力系统组件,返回false
}
// 通过句柄查找对应的能力规格
FGameplayAbilitySpec* Spec = AbilitySystemComponent->FindAbilitySpecFromHandle(Handle);
if (!Spec)
{
ABILITY_LOG(Warning, TEXT("CanActivateAbility %s failed, called with invalid Handle"), *GetName());
return false; // 找不到对应的能力规格,返回false
}
// 检查用户能力激活是否被抑制(例如UI打开时)
if (AbilitySystemComponent->GetUserAbilityActivationInhibited())
{
/**
* 输入被抑制(UI打开,其他能力阻塞了所有输入等)
* 对于触发型能力,可能需要区分CanActivate和CanUserActivate
* 例如:在菜单UI中要抑制LMB/RMB,但不应该阻止"低生命值时触发的buff"能力
*/
if (FScopedCanActivateAbilityLogEnabler::IsLoggingEnabled())
{
UE_LOG(LogAbilitySystem, Verbose, TEXT("%s: %s could not be activated due to GetUserAbilityActivationInhibited"), *GetNameSafe(ActorInfo->OwnerActor.Get()), *GetNameSafe(Spec->Ability));
UE_VLOG(ActorInfo->OwnerActor.Get(), VLogAbilitySystem, Verbose, TEXT("%s could not be activated due to GetUserAbilityActivationInhibited"), *GetNameSafe(Spec->Ability));
}
return false;
}
// 获取能力系统全局设置
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
// 检查冷却时间(除非全局设置忽略冷却)
if (!AbilitySystemGlobals.ShouldIgnoreCooldowns() && !CheckCooldown(Handle, ActorInfo, OptionalRelevantTags))
{
if (FScopedCanActivateAbilityLogEnabler::IsLoggingEnabled())
{
UE_LOG(LogAbilitySystem, Verbose, TEXT("%s: %s could not be activated due to Cooldown"), *GetNameSafe(ActorInfo->OwnerActor.Get()), *GetNameSafe(Spec->Ability));
UE_VLOG(ActorInfo->OwnerActor.Get(), VLogAbilitySystem, Verbose, TEXT("%s could not be activated due to Cooldown"), *GetNameSafe(Spec->Ability));
}
return false; // 能力在冷却中,无法激活
}
// 检查能力消耗(除非全局设置忽略消耗)
if (!AbilitySystemGlobals.ShouldIgnoreCosts() && !CheckCost(Handle, ActorInfo, OptionalRelevantTags))
{
if (FScopedCanActivateAbilityLogEnabler::IsLoggingEnabled())
{
UE_LOG(LogAbilitySystem, Verbose, TEXT("%s: %s could not be activated due to Cost"), *GetNameSafe(ActorInfo->OwnerActor.Get()), *GetNameSafe(Spec->Ability));
UE_VLOG(ActorInfo->OwnerActor.Get(), VLogAbilitySystem, Verbose, TEXT("%s could not be activated due to Cost"), *GetNameSafe(Spec->Ability));
}
return false; // 能力消耗不足,无法激活
}
// 检查标签要求
if (!DoesAbilitySatisfyTagRequirements(*AbilitySystemComponent, SourceTags, TargetTags, OptionalRelevantTags))
{ // 如果能力的标签被阻塞,或者有"阻塞"标签,或者缺少"必需"标签,则无法激活
if (FScopedCanActivateAbilityLogEnabler::IsLoggingEnabled())
{
UE_LOG(LogAbilitySystem, Verbose, TEXT("%s: %s could not be activated due to Blocking Tags or Missing Required Tags"), *GetNameSafe(ActorInfo->OwnerActor.Get()), *GetNameSafe(Spec->Ability));
UE_VLOG(ActorInfo->OwnerActor.Get(), VLogAbilitySystem, Verbose, TEXT("%s could not be activated due to Blocking Tags or Missing Required Tags"), *GetNameSafe(Spec->Ability));
}
return false;
}
// 检查能力的输入绑定是否被阻塞
if (AbilitySystemComponent->IsAbilityInputBlocked(Spec->InputID))
{
if (FScopedCanActivateAbilityLogEnabler::IsLoggingEnabled())
{
UE_LOG(LogAbilitySystem, Verbose, TEXT("%s: %s could not be activated due to blocked input ID %d"), *GetNameSafe(ActorInfo->OwnerActor.Get()), *GetNameSafe(Spec->Ability), Spec->InputID);
UE_VLOG(ActorInfo->OwnerActor.Get(), VLogAbilitySystem, Verbose, TEXT("%s could not be activated due to blocked input ID %d"), *GetNameSafe(Spec->Ability), Spec->InputID);
}
return false; // 输入被阻塞,无法激活
}
// 检查是否有蓝图的CanUse检查
if (bHasBlueprintCanUse)
{
FGameplayTagContainer K2FailTags;
// 调用蓝图的K2_CanActivateAbility进行自定义检查
if (K2_CanActivateAbility(*ActorInfo, Handle, K2FailTags) == false)
{
if (FScopedCanActivateAbilityLogEnabler::IsLoggingEnabled())
{
UE_LOG(LogAbilitySystem, Verbose, TEXT("%s: CanActivateAbility on %s failed, Blueprint override returned false"), *GetNameSafe(ActorInfo->OwnerActor.Get()), *GetNameSafe(Spec->Ability));
UE_VLOG(ActorInfo->OwnerActor.Get(), VLogAbilitySystem, Verbose, TEXT("CanActivateAbility on %s failed, Blueprint override returned false"), *GetNameSafe(Spec->Ability));
}
// 如果有输出参数,添加失败标签
if (OptionalRelevantTags)
{
const FGameplayTag& FailTag = GetDefault<UGameplayAbilitiesDeveloperSettings>()->ActivateFailCanActivateAbilityTag;
if (FailTag.IsValid())
{
OptionalRelevantTags->AddTag(FailTag);
}
OptionalRelevantTags->AppendTags(K2FailTags);
}
return false; // 蓝图自定义检查失败
}
}
// 所有检查通过,可以激活能力
return true;
}
1. 基础Actor和组件检查
cpp
// A valid AvatarActor is required. Simulated proxy check means only authority or autonomous proxies should be executing abilities.
AActor* const AvatarActor = ActorInfo ? ActorInfo->AvatarActor.Get() : nullptr;
if (AvatarActor == nullptr || !ShouldActivateAbility(AvatarActor->GetLocalRole()))
{
return false;
}
// make sure the ability system component is valid, if not bail out.
UAbilitySystemComponent* const AbilitySystemComponent = ActorInfo->AbilitySystemComponent.Get();
if (!AbilitySystemComponent)
{
return false;
}
作用:检查执行能力的基本条件是否满足\
- 确认存在AvatarActor(实际执行能力的角色)
- 检查网络角色权限(只有Authority或Autonomous Proxy才能执行能力)
- 确认AbilitySystemComponent存在
例子:如果玩家角色死亡(AvatarActor为nullptr),或者这是一个Simulated Proxy(网络同步的客户端角色),则能力无法激活。
2. 能力规格查找
cpp
FGameplayAbilitySpec* Spec = AbilitySystemComponent->FindAbilitySpecFromHandle(Handle);
if (!Spec)
{
ABILITY_LOG(Warning, TEXT("CanActivateAbility %s failed, called with invalid Handle"), *GetName());
return false;
}
作用:通过句柄查找对应的能力规格(AbilitySpec)
- AbilitySpec包含了能力的实例数据,如等级、冷却时间、已使用次数等
例子:如果能力已经被移除但系统仍尝试激活它,会在这里失败。
3. 用户激活抑制检查
cpp
if (AbilitySystemComponent->GetUserAbilityActivationInhibited())
{
// ... 日志记录
return false;
}
作用:检查用户输入是否被全局抑制
- 通常发生在UI打开、对话进行、过场动画等情况下
例子:当玩家打开背包UI时,所有通过按键触发的能力都无法激活。
4. 冷却时间检查
cpp
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
if (!AbilitySystemGlobals.ShouldIgnoreCooldowns() && !CheckCooldown(Handle, ActorInfo, OptionalRelevantTags))
{
// ... 日志记录
return false;
}
作用:检查能力是否处于冷却状态
ShouldIgnoreCooldowns()
通常在调试模式下使用CheckCooldown()
检查具体的冷却逻辑
例子:火球术有5秒冷却时间,如果在冷却期间尝试再次释放,这里会返回false。
5. 资源消耗检查
cpp
if (!AbilitySystemGlobals.ShouldIgnoreCosts() && !CheckCost(Handle, ActorInfo, OptionalRelevantTags))
{
// ... 日志记录
return false;
}
作用:检查执行能力所需的资源是否足够
- 可能消耗魔法值、体力、弹药等
例子:治疗术需要消耗25点魔法值,如果玩家只有20点魔法值,这里会返回false。
6. 标签要求检查
cpp
if (!DoesAbilitySatisfyTagRequirements(*AbilitySystemComponent, SourceTags, TargetTags, OptionalRelevantTags))
{
// ... 日志记录
return false;
}
作用:检查Gameplay Tag相关的条件
- 阻塞标签:如果能力或角色有某些标签,能力无法激活(如"沉默"、"眩晕")
- 必需标签:必须拥有的标签才能激活能力
- 源/目标标签:检查施法者和目标的标签条件
例子:
- 玩家被施加了"沉默"效果(有
State.Silenced
标签),所有法术能力都无法激活 - 某个能力需要玩家有
Status.PoweredUp
标签才能使用
7. 输入阻塞检查
cpp
if (AbilitySystemComponent->IsAbilityInputBlocked(Spec->InputID))
{
// ... 日志记录
return false;
}
作用:检查特定输入ID是否被阻塞
- 不同于全局抑制,这是针对特定按键的阻塞
例子:某些技能可能会临时阻塞普通攻击键(InputID=0),但允许技能键(InputID=1)使用。
8. 蓝图自定义检查
cpp
if (bHasBlueprintCanUse)
{
FGameplayTagContainer K2FailTags;
if (K2_CanActivateAbility(*ActorInfo, Handle, K2FailTags) == false)
{
// ... 日志记录和标签处理
return false;
}
}
作用:调用蓝图层的自定义检查逻辑
- 允许设计师在蓝图中实现复杂的激活条件
- 可以返回详细的失败原因标签
例子:
- 只能在白天使用的技能
- 需要特定装备才能使用的能力
- 基于地形或位置的条件检查
9. 最终通过
cpp
return true;
作用:所有检查都通过,能力可以激活
总结
这个函数是一个综合性的能力激活条件检查器,它按照从基础到复杂的顺序检查多个维度的条件:
- 基础存在性 → 2. 全局状态 → 3. 能力状态 → 4. 资源条件 → 5. 标签系统 → 6. 输入系统 → 7. 自定义逻辑
这种分层检查的设计确保了:
- 性能优化:早期失败避免不必要的计算
- 模块化:每个检查环节独立且可扩展
- 调试友好:详细的日志记录帮助排查问题
- 灵活性:支持蓝图自定义扩展
具体例子说明:
假设我们有一个"火球术"技能:
- 基础检查:首先确认角色存在且具有正确的网络角色
- 激活抑制 :如果玩家正在打开背包UI,
GetUserAbilityActivationInhibited()
返回true,火球术无法释放 - 冷却检查 :如果火球术还在5秒冷却中,
CheckCooldown
返回false,无法释放 - 消耗检查 :如果玩家魔法值不足(比如需要20MP但只有15MP),
CheckCost
返回false,无法释放 - 标签检查 :如果玩家处于"沉默"状态(有
State.Silenced
标签),而火球术需要Ability.CanCast
标签,DoesAbilitySatisfyTagRequirements
返回false - 输入检查:如果当前输入ID(比如鼠标左键)被其他系统阻塞,无法释放
- 蓝图检查 :如果蓝图中有自定义逻辑(比如只能在白天使用),
K2_CanActivateAbility
返回false
只有当所有这些检查都通过时,CanActivateAbility
才返回true,火球术才能被成功激活释放。
二、ShouldActivateAbility、ShouldAbilityRespondToEvent
第一段代码:ShouldActivateAbility
cpp
bool UGameplayAbility::ShouldActivateAbility(ENetRole Role) const
{
// 检查网络角色是否为模拟代理,模拟代理不应该激活能力
// 模拟代理是网络同步的客户端角色,没有本地控制权
return Role != ROLE_SimulatedProxy &&
// 检查网络安全策略:
// 如果是权威端(服务器),总是可以激活
// 如果是客户端,检查安全策略是否允许客户端激活
(Role == ROLE_Authority ||
(NetSecurityPolicy != EGameplayAbilityNetSecurityPolicy::ServerOnly &&
NetSecurityPolicy != EGameplayAbilityNetSecurityPolicy::ServerOnlyExecution)); // Don't violate security policy if we're not the server
}
详细说明 :
作用:基于网络角色和安全策略决定能力是否应该激活
网络角色检查:
ROLE_SimulatedProxy
:网络同步的客户端角色,不能激活能力ROLE_AutonomousProxy
:本地控制的客户端角色,可以激活能力(根据安全策略)ROLE_Authority
:服务器端,总是可以激活能力
安全策略检查:
ServerOnly
:只能在服务器执行,客户端完全不能激活ServerOnlyExecution
:只能在服务器执行逻辑,客户端可以预测但不能真正激活ClientOrServer
:客户端和服务器都可以激活
具体例子:
- 模拟代理情况:
cpp
// 玩家A在客户端控制角色,玩家B在另一个客户端看到玩家A的角色
// 玩家B看到的玩家A角色是ROLE_SimulatedProxy
// 这个角色不能激活任何能力,只能接收网络同步的动画和效果
- 服务器权限能力:
cpp
// 一个重要的管理型能力,如GM命令
// 设置NetSecurityPolicy = ServerOnly
// 只有服务器角色(ROLE_Authority)可以激活,客户端无法激活
- 客户端预测能力:
cpp
// 一个普通的攻击技能
// 设置NetSecurityPolicy = ClientOrServer
// 本地客户端(ROLE_AutonomousProxy)可以立即激活(预测执行)
// 服务器也会执行并验证
第二段代码:ShouldAbilityRespondToEvent
cpp
bool UGameplayAbility::ShouldAbilityRespondToEvent(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayEventData* Payload) const
{
// 检查是否有蓝图的ShouldAbilityRespondToEvent实现
if (bHasBlueprintShouldAbilityRespondToEvent)
{
// 调用蓝图的K2_ShouldAbilityRespondToEvent进行自定义检查
// 如果蓝图返回false,则拒绝响应事件
if (K2_ShouldAbilityRespondToEvent(*ActorInfo, *Payload) == false)
{
// 记录日志说明拒绝原因是蓝图逻辑
ABILITY_LOG(Log, TEXT("ShouldAbilityRespondToEvent %s failed, blueprint refused"), *GetName());
return false;
}
}
// 如果没有蓝图实现,或者蓝图同意响应,则返回true
return true;
}
详细说明 :
作用:决定能力是否应该响应特定的事件触发
事件触发机制:
- Gameplay Ability System 可以通过事件来触发能力
- 这个函数在事件触发时被调用,决定能力是否响应该事件
蓝图自定义逻辑:
- 允许设计师在蓝图中实现复杂的响应条件
- 可以基于事件数据、角色状态、环境条件等决定是否响应
具体例子:
- 条件性触发治疗:
cpp
// 事件:玩家受到伤害时触发
// 能力:自动治疗(当生命值低于30%时触发)
// 蓝图逻辑:检查当前生命值百分比,只有低于30%时才返回true
2.特定武器触发技能:
cpp
// 事件:玩家按下技能键
// 能力:武器特殊技能
// 蓝图逻辑:检查当前装备的武器类型,只有装备特定武器时才响应
- 环境依赖能力:
cpp
// 事件:进入特定区域
// 能力:区域增益效果
// 蓝图逻辑:检查玩家所在区域ID,只有特定区域才激活能力
- 组合技触发:
cpp
// 事件:连续攻击命中
// 能力:终结技
// 蓝图逻辑:检查连击计数,只有达到一定连击数时才响应
两个函数的协同工作:
cpp
// 完整的事件响应流程示例
void UAbilitySystemComponent::HandleGameplayEvent(FGameplayEventData* Payload)
{
for (UGameplayAbility* Ability : EventTriggeredAbilities)
{
// 第一步:检查网络权限
if (!Ability->ShouldActivateAbility(ActorInfo->AvatarActor->GetLocalRole()))
continue;
// 第二步:检查事件响应条件
if (!Ability->ShouldAbilityRespondToEvent(ActorInfo, Payload))
continue;
// 第三步:激活能力
TryActivateAbility(Ability->GetCurrentAbilitySpecHandle());
}
}
总结:
ShouldActivateAbility
:处理网络层的激活权限,确保能力在网络环境中正确执行ShouldAbilityRespondToEvent
:处理逻辑层的响应条件,允许设计师实现复杂的触发条件
这两个函数共同构成了Gameplay Ability System中灵活而强大的能力触发机制,既保证了网络同步的正确性,又提供了高度的可定制性。
三、GetCooldownTimeRemaining、GetCooldownTimeRemaining、GetCooldownTimeRemainingAndDuration
第一段代码:基础冷却时间查询
cpp
float UGameplayAbility::GetCooldownTimeRemaining() const
{
// 检查能力是否已实例化(是否有具体的实例对象)
// 如果已实例化,使用当前actor信息查询剩余冷却时间
// 如果没有实例化(如在CDO上调用),返回0表示没有冷却
return IsInstantiated() ? GetCooldownTimeRemaining(CurrentActorInfo) : 0.f;
}
作用:提供一个便捷的接口来获取当前能力实例的剩余冷却时间
例子:
cpp
// 在UI中显示技能冷却进度
UFUNCTION(BlueprintCallable)
float GetSkillCooldownPercent()
{
UGameplayAbility* Ability = GetEquippedSkill();
if (Ability)
{
float Remaining = Ability->GetCooldownTimeRemaining();
float Total = Ability->GetCooldownDuration();
return (Total > 0) ? (Remaining / Total) : 0.0f;
}
return 0.0f;
}
三、DoesAbilitySatisfyTagRequirements
cpp
bool UGameplayAbility::DoesAbilitySatisfyTagRequirements(
const UAbilitySystemComponent& AbilitySystemComponent,
const FGameplayTagContainer* SourceTags,
const FGameplayTagContainer* TargetTags,
OUT FGameplayTagContainer* OptionalRelevantTags) const
{
// 定义检查阻塞标签的lambda函数
bool bBlocked = false;
auto CheckForBlocked = [&](const FGameplayTagContainer& ContainerA, const FGameplayTagContainer& ContainerB)
{
// 如果任一容器为空或者没有共同标签,则不阻塞
if (ContainerA.IsEmpty() || ContainerB.IsEmpty() || !ContainerA.HasAny(ContainerB))
{
return;
}
// 如果需要输出相关标签信息
if (OptionalRelevantTags)
{
// 确保全局阻塞标签只添加一次
if (!bBlocked)
{
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
const FGameplayTag& BlockedTag = AbilitySystemGlobals.ActivateFailTagsBlockedTag;
OptionalRelevantTags->AddTag(BlockedTag);
}
// 添加所有匹配的阻塞标签到输出容器
OptionalRelevantTags->AppendMatchingTags(ContainerA, ContainerB);
}
bBlocked = true; // 标记为阻塞状态
};
// 定义检查必需标签的lambda函数
bool bMissing = false;
auto CheckForRequired = [&](const FGameplayTagContainer& TagsToCheck, const FGameplayTagContainer& RequiredTags)
{
// 如果没有要求或者已经满足所有要求,则返回
if (RequiredTags.IsEmpty() || TagsToCheck.HasAll(RequiredTags))
{
return;
}
if (OptionalRelevantTags)
{
// 确保全局缺失标签只添加一次
if (!bMissing)
{
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
const FGameplayTag& MissingTag = AbilitySystemGlobals.ActivateFailTagsMissingTag;
OptionalRelevantTags->AddTag(MissingTag);
}
// 计算并添加缺失的标签
FGameplayTagContainer MissingTags = RequiredTags;
MissingTags.RemoveTags(TagsToCheck.GetGameplayTagParents()); // 移除已存在的标签,得到缺失的标签
OptionalRelevantTags->AppendTags(MissingTags);
}
bMissing = true; // 标记为缺失状态
};
// 首先检查所有阻塞标签(这样OptionalRelevantTags会先包含阻塞标签)
// 检查能力系统组件的阻塞标签与能力的资产标签是否有冲突
CheckForBlocked(AbilitySystemComponent.GetBlockedAbilityTags(), GetAssetTags());
// 检查能力系统组件拥有的标签是否在能力的激活阻塞标签列表中
CheckForBlocked(AbilitySystemComponent.GetOwnedGameplayTags(), ActivationBlockedTags);
// 检查源标签是否在源阻塞标签列表中
if (SourceTags != nullptr)
{
CheckForBlocked(*SourceTags, SourceBlockedTags);
}
// 检查目标标签是否在目标阻塞标签列表中
if (TargetTags != nullptr)
{
CheckForBlocked(*TargetTags, TargetBlockedTags);
}
// 然后检查所有必需标签
// 检查能力系统组件是否拥有所有激活必需的标签
CheckForRequired(AbilitySystemComponent.GetOwnedGameplayTags(), ActivationRequiredTags);
// 检查源标签是否满足源必需标签要求
if (SourceTags != nullptr)
{
CheckForRequired(*SourceTags, SourceRequiredTags);
}
// 检查目标标签是否满足目标必需标签要求
if (TargetTags != nullptr)
{
CheckForRequired(*TargetTags, TargetRequiredTags);
}
// 成功条件:没有阻塞标签且没有缺失必需标签
return !bBlocked && !bMissing;
}
具体例子说明:
例子1:火球术能力
假设有一个火球术能力,定义如下:
- 资产标签 :
Ability.Fire
,Damage.Spell
- 激活必需标签 :
Skill.Pyromancy
(需要 pyro 技能) - 激活阻塞标签 :
Status.Silenced
(沉默状态下不能施法)
场景1 :玩家拥有 Skill.Pyromancy
标签,没有 Status.Silenced
标签
- 检查阻塞:通过(没有阻塞标签冲突)
- 检查必需:通过(拥有必需标签)
- 返回值:
true
- 能力可以激活
场景2 :玩家被施加了 Status.Silenced
标签
- 检查阻塞:
Status.Silenced
与ActivationBlockedTags
匹配 - 返回值:
false
- 能力被阻塞
例子2:治疗能力
假设治疗能力定义:
- 源必需标签 :
Alignment.Good
(施法者必须是善良阵营) - 目标必需标签 :
Status.Wounded
(目标必须是受伤状态) - 目标阻塞标签 :
Status.Undead
(不能治疗亡灵)
场景:
- 施法者标签:
Alignment.Good
,Class.Cleric
- 目标标签:
Status.Wounded
,Race.Human
检查过程:
- 源标签检查:
Alignment.Good
匹配源必需标签 ✓ - 目标标签检查:
Status.Wounded
匹配目标必需标签 ✓ - 目标阻塞检查:没有
Status.Undead
标签 ✓ - 返回值:
true
- 可以施放治疗
例子3:OptionalRelevantTags 的使用
当能力检查失败时,OptionalRelevantTags
会包含失败原因:
cpp
FGameplayTagContainer RelevantTags;
bool bCanActivate = Ability->DoesAbilitySatisfyTagRequirements(ASC, SourceTags, TargetTags, &RelevantTags);
if (!bCanActivate)
{
// RelevantTags 可能包含:
// "Ability.ActivateFail.TagsBlocked" (全局阻塞标签)
// "Status.Silenced" (具体的阻塞标签)
// 或者
// "Ability.ActivateFail.TagsMissing" (全局缺失标签)
// "Skill.Pyromancy" (具体缺失的标签)
}
这个函数是 UE 游戏能力系统的核心部分,确保能力只在适当的标签条件下激活,为复杂的游戏机制提供了灵活的标签驱动控制。
四、IsBlockingOtherAbilities
cpp
bool UGameplayAbility::IsBlockingOtherAbilities() const
{
// 检查能力的实例化策略是否为非实例化
if (GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced)
{
// 对于实例化能力,返回bIsBlockingOtherAbilities标志
return bIsBlockingOtherAbilities;
}
// 非实例化能力总是被认为阻塞其他能力
return true;
}
详细说明:
1. 实例化策略 (Instancing Policy)
在 UE 的能力系统中,能力有三种实例化策略:
- NonInstanced: 不创建实例,使用 CDO (Class Default Object)
- InstancedPerActor: 每个 Actor 创建一个实例
- InstancedPerExecution: 每次执行都创建新实例
2. 代码逻辑分析
对于非实例化能力 (NonInstanced)
cpp
// 非实例化能力总是返回 true
return true;
为什么总是阻塞?
- 非实例化能力没有独立的状态跟踪
- 它们通常是简单、瞬时、无状态的能力
- 为了安全起见,默认阻塞其他能力
对于实例化能力
cpp
// 返回 bIsBlockingOtherAbilities 标志的值
return bIsBlockingOtherAbilities;
这个标志可以在能力蓝图中设置,或者在 C++ 中通过 bIsBlockingOtherAbilities = true/false;
控制。
具体例子说明:
例子1:非实例化能力 - 简单攻击
cpp
// 假设有一个简单的近战攻击能力
UCLASS()
class UMeleeAttackAbility : public UGameplayAbility
{
// 在构造函数中设置为非实例化
UMeleeAttackAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::NonInstanced;
}
};
// 使用
UMeleeAttackAbility* AttackAbility = ...;
bool bIsBlocking = AttackAbility->IsBlockingOtherAbilities(); // 总是返回 true
效果:当这个攻击能力激活时,它会阻塞所有其他能力的执行。
例子2:实例化能力 - 持续施法
cpp
// 假设有一个持续施法的火球术
UCLASS()
class UChargedFireballAbility : public UGameplayAbility
{
// 在构造函数中设置
UChargedFireballAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerExecution;
bIsBlockingOtherAbilities = true; // 显式设置为阻塞
}
};
// 使用
UChargedFireballAbility* FireballAbility = ...;
bool bIsBlocking = FireballAbility->IsBlockingOtherAbilities(); // 返回 true
效果:蓄力期间阻塞其他能力,但蓄力结束后可以取消阻塞。
例子3:实例化能力 - 被动光环
cpp
// 假设有一个被动光环能力
UCLASS()
class UPassiveAuraAbility : public UGameplayAbility
{
// 在构造函数中设置
UPassiveAuraAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
bIsBlockingOtherAbilities = false; // 不阻塞其他能力
}
};
// 使用
UPassiveAuraAbility* AuraAbility = ...;
bool bIsBlocking = AuraAbility->IsBlockingOtherAbilities(); // 返回 false
效果:这个被动光环可以与其他能力同时运行。
实际应用场景:
场景1:角色移动和技能系统
cpp
// 当玩家尝试使用新能力时,系统会检查:
void UAbilitySystemComponent::TryActivateAbility(FGameplayAbilitySpecHandle Handle)
{
UGameplayAbility* Ability = GetAbilityFromHandle(Handle);
// 检查当前是否有阻塞能力在运行
for (UGameplayAbility* ActiveAbility : GetActiveAbilities())
{
if (ActiveAbility->IsBlockingOtherAbilities())
{
// 有阻塞能力在运行,不能激活新能力
return;
}
}
// 激活新能力...
}
场景2:翻滚动作
cpp
// 翻滚能力应该阻塞其他能力
class UDodgeAbility : public UGameplayAbility
{
UDodgeAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerExecution;
bIsBlockingOtherAbilities = true;
}
};
// 在翻滚期间,玩家不能:
// - 攻击
// - 使用技能
// - 交互
// - 等等...
场景3:死亡状态
cpp
// 死亡能力阻塞一切其他能力
class UDeathAbility : public UGameplayAbility
{
UDeathAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
bIsBlockingOtherAbilities = true;
}
};
// 角色死亡后,所有其他能力都被阻塞
这个机制确保了能力之间的优先级和互斥性,是构建复杂能力交互系统的基础。
五、SetShouldBlockOtherAbilities
cpp
void UGameplayAbility::SetShouldBlockOtherAbilities(bool bShouldBlockAbilities)
{
// 检查条件:能力必须处于激活状态,且不是非实例化能力,且阻塞状态有变化
if (bIsActive &&
GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced &&
bShouldBlockAbilities != bIsBlockingOtherAbilities)
{
// 更新阻塞状态
bIsBlockingOtherAbilities = bShouldBlockAbilities;
// 获取能力系统组件
UAbilitySystemComponent* Comp = CurrentActorInfo->AbilitySystemComponent.Get();
if (Comp)
{
// 应用能力的阻塞和取消标签
Comp->ApplyAbilityBlockAndCancelTags(
GetAssetTags(), // 能力的资产标签
this, // 能力实例
bIsBlockingOtherAbilities, // 是否阻塞
BlockAbilitiesWithTag, // 阻塞哪些标签的能力
false, // 是否忽略阻塞能力标签
CancelAbilitiesWithTag // 取消哪些标签的能力
);
}
}
}
详细说明:
1. 条件检查
cpp
if (bIsActive &&
GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced &&
bShouldBlockAbilities != bIsBlockingOtherAbilities)
三个条件必须同时满足:
- bIsActive: 能力必须处于激活状态
- 不是非实例化能力(非实例化能力总是阻塞,不能动态改变)
- 阻塞状态确实发生了变化
2. 核心功能
调用 ApplyAbilityBlockAndCancelTags
来:
- 添加/移除阻塞标签:影响其他能力是否能激活
- 取消特定能力:强制中断某些正在运行的能力
具体例子说明:
例子1:蓄力射击能力
cpp
class UChargedShotAbility : public UGameplayAbility
{
// 蓄力阶段 - 开始阻塞其他能力
void StartCharging()
{
// 开始蓄力时阻塞其他能力
SetShouldBlockOtherAbilities(true);
// 这意味着:
// - 玩家不能使用其他能力
// - 根据 BlockAbilitiesWithTag 设置,特定标签的能力被阻塞
}
// 射击完成 - 停止阻塞
void FireShot()
{
// 射击后停止阻塞其他能力
SetShouldBlockOtherAbilities(false);
// 现在玩家可以使用其他能力了
}
// 取消蓄力
void CancelCharging()
{
// 取消时也停止阻塞
SetShouldBlockOtherAbilities(false);
}
};
例子2:可切换阻塞状态的格挡能力
cpp
class UBlockAbility : public UGameplayAbility
{
bool bIsBlocking = false;
// 输入处理 - 切换格挡状态
void InputPressed()
{
bIsBlocking = !bIsBlocking;
SetShouldBlockOtherAbilities(bIsBlocking);
if (bIsBlocking)
{
// 进入格挡状态,阻塞移动和攻击能力
UE_LOG(LogTemp, Warning, TEXT("开始格挡,阻塞其他能力"));
}
else
{
// 停止格挡,允许其他能力
UE_LOG(LogTemp, Warning, TEXT("停止格挡,允许其他能力"));
}
}
};
例子3:状态依赖的阻塞
cpp
class UBerserkAbility : public UGameplayAbility
{
// 狂暴状态随时间变化阻塞行为
void UpdateBerserkState(float RageLevel)
{
// 根据狂暴等级决定是否阻塞其他能力
if (RageLevel > 0.8f)
{
// 高狂暴:阻塞所有其他能力,只能使用狂暴攻击
SetShouldBlockOtherAbilities(true);
}
else if (RageLevel > 0.3f)
{
// 中等狂暴:只阻塞特定能力
SetShouldBlockOtherAbilities(true);
}
else
{
// 低狂暴:不阻塞任何能力
SetShouldBlockOtherAbilities(false);
}
}
};
标签系统的工作原理:
BlockAbilitiesWithTag 的使用
cpp
// 在能力构造函数中设置
UMyAbility::UMyAbility()
{
// 只阻塞带有这些标签的能力
BlockAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.Movement"));
BlockAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.Attack"));
// 不阻塞带有这些标签的能力
// BlockAbilitiesWithTag 为空表示阻塞所有能力
}
CancelAbilitiesWithTag 的使用
cpp
// 设置要取消的能力标签
CancelAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.Channeling"));
实际应用场景:
场景1:精准瞄准模式
cpp
void UPrecisionAimAbility::OnAimStart()
{
// 进入瞄准模式时
SetShouldBlockOtherAbilities(true);
// 阻塞移动和普通攻击,但允许切换武器等操作
BlockAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.Movement"));
BlockAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.BasicAttack"));
}
void UPrecisionAimAbility::OnAimEnd()
{
// 退出瞄准模式
SetShouldBlockOtherAbilities(false);
}
场景2:终极技能吟唱
cpp
void UUltimateAbility::StartChanneling()
{
// 开始吟唱时阻塞并取消其他能力
SetShouldBlockOtherAbilities(true);
// 取消所有正在进行的普通能力
CancelAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.Normal"));
// 但不取消被动能力
CancelAbilitiesWithTag.RemoveTag(FGameplayTag::RequestGameplayTag("Ability.Passive"));
}
场景3:状态机式的能力管理
cpp
void UCombatAbility::ChangeCombatState(ECombatState NewState)
{
switch(NewState)
{
case ECombatState::Neutral:
SetShouldBlockOtherAbilities(false);
break;
case ECombatState::Attacking:
SetShouldBlockOtherAbilities(true);
BlockAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.Movement"));
break;
case ECombatState::Stunned:
SetShouldBlockOtherAbilities(true); // 眩晕时阻塞所有能力
BlockAbilitiesWithTag.Reset(); // 清空特定标签,阻塞所有
break;
}
}
这个函数提供了运行时动态控制能力阻塞状态的能力,使得开发者可以根据游戏状态、玩家输入或其他条件来精细控制能力的互斥关系,为复杂的游戏战斗系统提供了强大的灵活性。
五、GetCooldownTags()
cpp
const FGameplayTagContainer* UGameplayAbility::GetCooldownTags() const
{
// 获取冷却相关的GameplayEffect
UGameplayEffect* CDGE = GetCooldownGameplayEffect();
// 如果存在冷却GE,返回其授予的标签;否则返回空指针
return CDGE ? &CDGE->GetGrantedTags() : nullptr;
}
详细说明:
1. 冷却系统的工作原理
在 UE 的游戏能力系统中,冷却机制是通过 GameplayEffect
实现的:
- 当能力激活时,会应用一个特殊的冷却 GameplayEffect
- 这个 GE 包含冷却时间和相关的标签
- 标签用于标识能力处于冷却状态
2. 方法解析
GetCooldownGameplayEffect()
: 获取为这个能力定义的冷却 GameplayEffectGetGrantedTags()
: 获取该 GE 授予的标签(即冷却标签)
具体例子说明:
例子1:基础火球术能力
cpp
// 火球术能力的冷却GE定义
UGameplayEffect* UFireballAbility::GetCooldownGameplayEffect() const
{
// 返回火球术的冷却GE
return FireballCooldownGE;
}
// 冷却GE的设置(通常在数据资产中配置)
/*
GameplayEffect: Fireball_Cooldown
- Duration: 3.0f (3秒冷却)
- Granted Tags:
- "Cooldown.Fireball"
- "Ability.OnCooldown"
*/
使用场景:
cpp
UFireballAbility* FireballAbility = GetFireballAbility();
const FGameplayTagContainer* CooldownTags = FireballAbility->GetCooldownTags();
if (CooldownTags)
{
// 检查特定的冷却标签
if (CooldownTags->HasTag(FGameplayTag::RequestGameplayTag("Cooldown.Fireball")))
{
// 火球术正在冷却中
UE_LOG(LogTemp, Warning, TEXT("Fireball is on cooldown!"));
}
}
例子2:共享冷却的能力组
cpp
// 多个冰系技能共享冷却
class UIceSpikeAbility : public UGameplayAbility
{
// 使用共享的冷却GE
};
class UIceNovaAbility : public UGameplayAbility
{
// 使用相同的共享冷却GE
};
// 共享冷却GE配置:
/*
GameplayEffect: IceSpells_SharedCooldown
- Duration: 5.0f
- Granted Tags:
- "Cooldown.IceSpells"
- "Ability.OnCooldown"
*/
使用场景:
cpp
// 当使用冰刺能力后
UIceSpikeAbility* IceSpike = GetIceSpikeAbility();
IceSpike->ActivateAbility(...);
// 检查冰霜新星能力的冷却状态
UIceNovaAbility* IceNova = GetIceNovaAbility();
const FGameplayTagContainer* CooldownTags = IceNova->GetCooldownTags();
if (CooldownTags && CooldownTags->HasTag(FGameplayTag::RequestGameplayTag("Cooldown.IceSpells")))
{
// 冰霜新星也在冷却中(因为共享冷却)
ShowCooldownDisplay("All ice spells on cooldown");
}
例子3:分层冷却系统
cpp
// 能力有不同的冷却层级
class UBasicAttackAbility : public UGameplayAbility
{
// 基础攻击冷却GE:
/*
Granted Tags:
- "Cooldown.Attack.Basic"
- "Cooldown.Attack"
- "Ability.OnCooldown"
*/
};
class UHeavyAttackAbility : public UGameplayAbility
{
// 重攻击冷却GE:
/*
Granted Tags:
- "Cooldown.Attack.Heavy"
- "Cooldown.Attack"
- "Ability.OnCooldown"
*/
};
使用场景:
cpp
// 检查攻击类能力的通用冷却状态
bool IsAnyAttackOnCooldown(UAbilitySystemComponent* ASC)
{
// 获取所有激活的能力
for (UGameplayAbility* Ability : ASC->GetActivatableAbilities())
{
const FGameplayTagContainer* CooldownTags = Ability->GetCooldownTags();
if (CooldownTags && CooldownTags->HasTag(FGameplayTag::RequestGameplayTag("Cooldown.Attack")))
{
return true; // 有攻击能力在冷却中
}
}
return false;
}
实际应用场景
场景1:UI 冷却显示
cpp
void UAbilityCooldownWidget::UpdateCooldownDisplay()
{
for (UGameplayAbility* Ability : TrackedAbilities)
{
const FGameplayTagContainer* CooldownTags = Ability->GetCooldownTags();
if (CooldownTags)
{
// 根据冷却标签更新UI元素
FString AbilityName = Ability->GetName();
FGameplayTag CooldownTag = FindCooldownTagForAbility(CooldownTags);
DisplayCooldown(AbilityName, CooldownTag);
}
else
{
// 能力不在冷却中
HideCooldown(Ability->GetName());
}
}
}
场景2:条件性能力激活
cpp
bool UCombatManager::CanUseAbility(UGameplayAbility* Ability)
{
// 检查冷却状态
const FGameplayTagContainer* CooldownTags = Ability->GetCooldownTags();
if (CooldownTags && !CooldownTags->IsEmpty())
{
// 能力在冷却中
return false;
}
// 检查其他条件(法力、状态等)
return HasEnoughMana(Ability) && !IsStunned();
}
场景3:冷却减少效果
cpp
void UCooldownReductionEffect::ApplyCooldownReduction()
{
// 查找所有带有冷却标签的能力
for (UGameplayAbility* Ability : AffectedAbilities)
{
const FGameplayTagContainer* CooldownTags = Ability->GetCooldownTags();
if (CooldownTags && CooldownTags->HasTag(CooldownReductionTag))
{
// 减少这个能力的冷却时间
ReduceCooldownDuration(Ability, ReductionAmount);
}
}
}
场景4:冷却状态同步(多人游戏)
cpp
void UAbilityReplicationComponent::ReplicateCooldownState()
{
for (UGameplayAbility* Ability : ReplicatedAbilities)
{
const FGameplayTagContainer* CooldownTags = Ability->GetCooldownTags();
// 将冷却标签同步到客户端
if (CooldownTags)
{
ReplicateCooldownTagsToClient(Ability->GetAbilityID(), *CooldownTags);
}
}
}
这个函数是能力冷却系统的核心组成部分,它使得:
- 冷却状态查询:可以检查能力是否在冷却中
- 冷却分组:通过共享标签实现能力组冷却
- UI反馈:为玩家显示冷却状态
- 游戏逻辑:基于冷却状态做出决策
- 系统集成:与其他游戏系统(如buff、装备效果)交互
通过冷却标签,开发者可以构建复杂而灵活的冷却机制,满足各种游戏设计需求。