深入解析:UE5 GAS GameAbility源码解析 CanActivateAbility

文章目录


一、CanActivateAbility

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和组件检查

// 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. 能力规格查找

FGameplayAbilitySpec* Spec = AbilitySystemComponent->FindAbilitySpecFromHandle(Handle);
if (!Spec)
{
ABILITY_LOG(Warning, TEXT("CanActivateAbility %s failed, called with invalid Handle"), *GetName());
return false;
}

作用:通过句柄查找对应的能力规格(AbilitySpec)

  • AbilitySpec包含了能力的实例数据,如等级、冷却时间、已使用次数等

例子:如果能力已经被移除但系统仍尝试激活它,会在这里失败。

3. 用户激活抑制检查

if (AbilitySystemComponent->GetUserAbilityActivationInhibited())
{
// ... 日志记录
return false;
}

作用:检查用户输入是否被全局抑制

  • 通常发生在UI打开、对话进行、过场动画等情况下

例子:当玩家打开背包UI时,所有通过按键触发的能力都无法激活。

4. 冷却时间检查

UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
if (!AbilitySystemGlobals.ShouldIgnoreCooldowns() && !CheckCooldown(Handle, ActorInfo, OptionalRelevantTags))
{
// ... 日志记录
return false;
}

作用:检查能力是否处于冷却状态

  • ShouldIgnoreCooldowns() 通常在调试模式下使用
  • CheckCooldown() 检查具体的冷却逻辑

例子:火球术有5秒冷却时间,如果在冷却期间尝试再次释放,这里会返回false。

5. 资源消耗检查

if (!AbilitySystemGlobals.ShouldIgnoreCosts() && !CheckCost(Handle, ActorInfo, OptionalRelevantTags))
{
// ... 日志记录
return false;
}

作用:检查执行能力所需的资源是否足够

  • 可能消耗魔法值、体力、弹药等

例子:治疗术需要消耗25点魔法值,如果玩家只有20点魔法值,这里会返回false。

6. 标签要求检查

if (!DoesAbilitySatisfyTagRequirements(*AbilitySystemComponent, SourceTags, TargetTags, OptionalRelevantTags))
{
// ... 日志记录
return false;
}

作用:检查Gameplay Tag相关的条件

  • 阻塞标签:如果能力或角色有某些标签,能力无法激活(如"沉默"、“眩晕”)
  • 必需标签:必须拥有的标签才能激活能力
  • 源/目标标签:检查施法者和目标的标签条件

例子

  • 玩家被施加了"沉默"效果(有State.Silenced标签),所有法术能力都无法激活
  • 某个能力需要玩家有Status.PoweredUp标签才能使用

7. 输入阻塞检查

if (AbilitySystemComponent->IsAbilityInputBlocked(Spec->InputID))
{
// ... 日志记录
return false;
}

作用:检查特定输入ID是否被阻塞

  • 不同于全局抑制,这是针对特定按键的阻塞

例子:某些技能可能会临时阻塞普通攻击键(InputID=0),但允许技能键(InputID=1)使用。

8. 蓝图自定义检查

if (bHasBlueprintCanUse)
{
FGameplayTagContainer K2FailTags;
if (K2_CanActivateAbility(*ActorInfo, Handle, K2FailTags) == false)
{
// ... 日志记录和标签处理
return false;
}
}

作用:调用蓝图层的自定义检查逻辑

  • 允许设计师在蓝图中实现复杂的激活条件
  • 可以返回详细的失败原因标签

例子

  • 只能在白天使用的技能
  • 需要特定装备才能使用的能力
  • 基于地形或位置的条件检查

9. 最终通过

return true;

作用:所有检查都通过,能力可以激活

总结

这个函数是一个综合性的能力激活条件检查器,它按照从基础到复杂的顺序检查多个维度的条件:

  1. 基础存在性 → 2. 全局状态 → 3. 能力状态 → 4. 资源条件 → 5. 标签系统 → 6. 输入系统 → 7. 自定义逻辑

这种分层检查的设计确保了:

  • 性能优化:早期失败避免不必要的计算
  • 模块化:每个检查环节独立且可扩展
  • 调试友好:详细的日志记录帮助排查问题
  • 灵活性:支持蓝图自定义扩展

具体例子说明:

假设我们有一个"火球术"技能:

  1. 基础检查:首先确认角色存在且具有正确的网络角色
  2. 激活抑制:如果玩家正在打开背包UI,GetUserAbilityActivationInhibited()返回true,火球术无法释放
  3. 冷却检查:如果火球术还在5秒冷却中,CheckCooldown返回false,无法释放
  4. 消耗检查:如果玩家魔法值不足(比如需要20MP但只有15MP),CheckCost返回false,无法释放
  5. 标签检查:如果玩家处于"沉默"状态(有State.Silenced标签),而火球术需要Ability.CanCast标签,DoesAbilitySatisfyTagRequirements返回false
  6. 输入检查:如果当前输入ID(比如鼠标左键)被其他系统阻塞,无法释放
  7. 蓝图检查:如果蓝图中有自定义逻辑(比如只能在白天使用),K2_CanActivateAbility返回false

只有当所有这些检查都通过时,CanActivateAbility才返回true,火球术才能被成功激活释放。

二、ShouldActivateAbility、ShouldAbilityRespondToEvent

第一段代码:ShouldActivateAbility

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:客户端和服务器都可以激活

具体例子:

  1. 模拟代理情况
// 玩家A在客户端控制角色,玩家B在另一个客户端看到玩家A的角色
// 玩家B看到的玩家A角色是ROLE_SimulatedProxy
// 这个角色不能激活任何能力,只能接收网络同步的动画和效果
  1. 服务器权限能力:
// 一个重要的管理型能力,如GM命令
// 设置NetSecurityPolicy = ServerOnly
// 只有服务器角色(ROLE_Authority)可以激活,客户端无法激活
  1. 客户端预测能力:
// 一个普通的攻击技能
// 设置NetSecurityPolicy = ClientOrServer  
// 本地客户端(ROLE_AutonomousProxy)可以立即激活(预测执行)
// 服务器也会执行并验证

第二段代码:ShouldAbilityRespondToEvent

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 可以通过事件来触发能力
  • 这个函数在事件触发时被调用,决定能力是否响应该事件

蓝图自定义逻辑

  • 允许设计师在蓝图中实现复杂的响应条件
  • 可以基于事件数据、角色状态、环境条件等决定是否响应

具体例子

  1. 条件性触发治疗
// 事件:玩家受到伤害时触发
// 能力:自动治疗(当生命值低于30%时触发)
// 蓝图逻辑:检查当前生命值百分比,只有低于30%时才返回true

2.特定武器触发技能:

// 事件:玩家按下技能键
// 能力:武器特殊技能
// 蓝图逻辑:检查当前装备的武器类型,只有装备特定武器时才响应
  1. 环境依赖能力:
// 事件:进入特定区域
// 能力:区域增益效果
// 蓝图逻辑:检查玩家所在区域ID,只有特定区域才激活能力
  1. 组合技触发:
// 事件:连续攻击命中
// 能力:终结技
// 蓝图逻辑:检查连击计数,只有达到一定连击数时才响应

两个函数的协同工作:

// 完整的事件响应流程示例
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

第一段代码:基础冷却时间查询

float UGameplayAbility::GetCooldownTimeRemaining() const
{
// 检查能力是否已实例化(是否有具体的实例对象)
// 如果已实例化,使用当前actor信息查询剩余冷却时间
// 如果没有实例化(如在CDO上调用),返回0表示没有冷却
return IsInstantiated() ? GetCooldownTimeRemaining(CurrentActorInfo) : 0.f;
}

作用:提供一个便捷的接口来获取当前能力实例的剩余冷却时间

例子

// 在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

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.SilencedActivationBlockedTags 匹配
  • 返回值:false - 能力被阻塞

例子2:治疗能力

假设治疗能力定义:

  • 源必需标签: Alignment.Good (施法者必须是善良阵营)
  • 目标必需标签: Status.Wounded (目标必须是受伤状态)
  • 目标阻塞标签: Status.Undead (不能治疗亡灵)

场景

  • 施法者标签:Alignment.Good, Class.Cleric
  • 目标标签:Status.Wounded, Race.Human

检查过程:

  1. 源标签检查:Alignment.Good 匹配源必需标签 ✓
  2. 目标标签检查:Status.Wounded 匹配目标必需标签 ✓
  3. 目标阻塞检查:没有 Status.Undead 标签 ✓
  4. 返回值:true - 可以施放治疗

例子3:OptionalRelevantTags 的使用

当能力检查失败时,OptionalRelevantTags 会包含失败原因:

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

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)

// 非实例化能力总是返回 true
return true;

为什么总是阻塞?

  • 非实例化能力没有独立的状态跟踪
  • 它们通常是简单、瞬时、无状态的能力
  • 为了安全起见,默认阻塞其他能力

对于实例化能力

// 返回 bIsBlockingOtherAbilities 标志的值
return bIsBlockingOtherAbilities;

这个标志可以在能力蓝图中设置,或者在 C++ 中通过 bIsBlockingOtherAbilities = true/false; 控制。

具体例子说明:

例子1:非实例化能力 - 简单攻击

// 假设有一个简单的近战攻击能力
UCLASS()
class UMeleeAttackAbility : public UGameplayAbility
{
// 在构造函数中设置为非实例化
UMeleeAttackAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::NonInstanced;
}
};
// 使用
UMeleeAttackAbility* AttackAbility = ...;
bool bIsBlocking = AttackAbility->IsBlockingOtherAbilities(); // 总是返回 true

效果:当这个攻击能力激活时,它会阻塞所有其他能力的执行。

例子2:实例化能力 - 持续施法

// 假设有一个持续施法的火球术
UCLASS()
class UChargedFireballAbility : public UGameplayAbility
{
// 在构造函数中设置
UChargedFireballAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerExecution;
bIsBlockingOtherAbilities = true; // 显式设置为阻塞
}
};
// 使用
UChargedFireballAbility* FireballAbility = ...;
bool bIsBlocking = FireballAbility->IsBlockingOtherAbilities(); // 返回 true

效果:蓄力期间阻塞其他能力,但蓄力结束后可以取消阻塞。

例子3:实例化能力 - 被动光环

// 假设有一个被动光环能力
UCLASS()
class UPassiveAuraAbility : public UGameplayAbility
{
// 在构造函数中设置
UPassiveAuraAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
bIsBlockingOtherAbilities = false; // 不阻塞其他能力
}
};
// 使用
UPassiveAuraAbility* AuraAbility = ...;
bool bIsBlocking = AuraAbility->IsBlockingOtherAbilities(); // 返回 false

效果:这个被动光环可以与其他能力同时运行。

实际应用场景:

场景1:角色移动和技能系统

// 当玩家尝试使用新能力时,系统会检查:
void UAbilitySystemComponent::TryActivateAbility(FGameplayAbilitySpecHandle Handle)
{
UGameplayAbility* Ability = GetAbilityFromHandle(Handle);
// 检查当前是否有阻塞能力在运行
for (UGameplayAbility* ActiveAbility : GetActiveAbilities())
{
if (ActiveAbility->IsBlockingOtherAbilities())
{
// 有阻塞能力在运行,不能激活新能力
return;
}
}
// 激活新能力...
}

场景2:翻滚动作

// 翻滚能力应该阻塞其他能力
class UDodgeAbility : public UGameplayAbility
{
UDodgeAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerExecution;
bIsBlockingOtherAbilities = true;
}
};
// 在翻滚期间,玩家不能:
// - 攻击
// - 使用技能  
// - 交互
// - 等等...

场景3:死亡状态

// 死亡能力阻塞一切其他能力
class UDeathAbility : public UGameplayAbility
{
UDeathAbility()
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
bIsBlockingOtherAbilities = true;
}
};
// 角色死亡后,所有其他能力都被阻塞

这个机制确保了能力之间的优先级和互斥性,是构建复杂能力交互系统的基础。

五、SetShouldBlockOtherAbilities

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. 条件检查

if (bIsActive &&
GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced &&
bShouldBlockAbilities != bIsBlockingOtherAbilities)

三个条件必须同时满足

  • bIsActive: 能力必须处于激活状态
  • 不是非实例化能力(非实例化能力总是阻塞,不能动态改变)
  • 阻塞状态确实发生了变化

2. 核心功能

调用 ApplyAbilityBlockAndCancelTags 来:

  • 添加/移除阻塞标签:影响其他能力是否能激活
  • 取消特定能力:强制中断某些正在运行的能力

具体例子说明:

例子1:蓄力射击能力

class UChargedShotAbility : public UGameplayAbility
{
// 蓄力阶段 - 开始阻塞其他能力
void StartCharging()
{
// 开始蓄力时阻塞其他能力
SetShouldBlockOtherAbilities(true);
// 这意味着:
// - 玩家不能使用其他能力
// - 根据 BlockAbilitiesWithTag 设置,特定标签的能力被阻塞
}
// 射击完成 - 停止阻塞
void FireShot()
{
// 射击后停止阻塞其他能力
SetShouldBlockOtherAbilities(false);
// 现在玩家可以使用其他能力了
}
// 取消蓄力
void CancelCharging()
{
// 取消时也停止阻塞
SetShouldBlockOtherAbilities(false);
}
};

例子2:可切换阻塞状态的格挡能力

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:状态依赖的阻塞

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 的使用

// 在能力构造函数中设置
UMyAbility::UMyAbility()
{
// 只阻塞带有这些标签的能力
BlockAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.Movement"));
BlockAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.Attack"));
// 不阻塞带有这些标签的能力
// BlockAbilitiesWithTag 为空表示阻塞所有能力
}

CancelAbilitiesWithTag 的使用

// 设置要取消的能力标签
CancelAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.Channeling"));

实际应用场景:

场景1:精准瞄准模式

void UPrecisionAimAbility::OnAimStart()
{
// 进入瞄准模式时
SetShouldBlockOtherAbilities(true);
// 阻塞移动和普通攻击,但允许切换武器等操作
BlockAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.Movement"));
BlockAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.BasicAttack"));
}
void UPrecisionAimAbility::OnAimEnd()
{
// 退出瞄准模式
SetShouldBlockOtherAbilities(false);
}

场景2:终极技能吟唱

void UUltimateAbility::StartChanneling()
{
// 开始吟唱时阻塞并取消其他能力
SetShouldBlockOtherAbilities(true);
// 取消所有正在进行的普通能力
CancelAbilitiesWithTag.AddTag(FGameplayTag::RequestGameplayTag("Ability.Normal"));
// 但不取消被动能力
CancelAbilitiesWithTag.RemoveTag(FGameplayTag::RequestGameplayTag("Ability.Passive"));
}

场景3:状态机式的能力管理

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()

const FGameplayTagContainer* UGameplayAbility::GetCooldownTags() const
{
// 获取冷却相关的GameplayEffect
UGameplayEffect* CDGE = GetCooldownGameplayEffect();
// 如果存在冷却GE,返回其授予的标签;否则返回空指针
return CDGE ? &CDGE->GetGrantedTags() : nullptr;
}

详细说明:

1. 冷却系统的工作原理

在 UE 的游戏能力系统中,冷却机制是通过 GameplayEffect 实现的:

  • 当能力激活时,会应用一个特殊的冷却 GameplayEffect
  • 这个 GE 包含冷却时间和相关的标签
  • 标签用于标识能力处于冷却状态

2. 方法解析

  • GetCooldownGameplayEffect(): 获取为这个能力定义的冷却 GameplayEffect
  • GetGrantedTags(): 获取该 GE 授予的标签(即冷却标签)

具体例子说明:

例子1:基础火球术能力

// 火球术能力的冷却GE定义
UGameplayEffect* UFireballAbility::GetCooldownGameplayEffect() const
{
// 返回火球术的冷却GE
return FireballCooldownGE;
}
// 冷却GE的设置(通常在数据资产中配置)
/*
GameplayEffect: Fireball_Cooldown
- Duration: 3.0f (3秒冷却)
- Granted Tags:
- "Cooldown.Fireball"
- "Ability.OnCooldown"
*/

使用场景:

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:共享冷却的能力组

// 多个冰系技能共享冷却
class UIceSpikeAbility : public UGameplayAbility
{
// 使用共享的冷却GE
};
class UIceNovaAbility : public UGameplayAbility
{
// 使用相同的共享冷却GE
};
// 共享冷却GE配置:
/*
GameplayEffect: IceSpells_SharedCooldown
- Duration: 5.0f
- Granted Tags:
- "Cooldown.IceSpells"
- "Ability.OnCooldown"
*/

使用场景:

// 当使用冰刺能力后
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:分层冷却系统

// 能力有不同的冷却层级
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"
*/
};

使用场景:

// 检查攻击类能力的通用冷却状态
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 冷却显示

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:条件性能力激活

bool UCombatManager::CanUseAbility(UGameplayAbility* Ability)
{
// 检查冷却状态
const FGameplayTagContainer* CooldownTags = Ability->GetCooldownTags();
if (CooldownTags && !CooldownTags->IsEmpty())
{
// 能力在冷却中
return false;
}
// 检查其他条件(法力、状态等)
return HasEnoughMana(Ability) && !IsStunned();
}

场景3:冷却减少效果

void UCooldownReductionEffect::ApplyCooldownReduction()
{
// 查找所有带有冷却标签的能力
for (UGameplayAbility* Ability : AffectedAbilities)
{
const FGameplayTagContainer* CooldownTags = Ability->GetCooldownTags();
if (CooldownTags && CooldownTags->HasTag(CooldownReductionTag))
{
// 减少这个能力的冷却时间
ReduceCooldownDuration(Ability, ReductionAmount);
}
}
}

场景4:冷却状态同步(多人游戏)

void UAbilityReplicationComponent::ReplicateCooldownState()
{
for (UGameplayAbility* Ability : ReplicatedAbilities)
{
const FGameplayTagContainer* CooldownTags = Ability->GetCooldownTags();
// 将冷却标签同步到客户端
if (CooldownTags)
{
ReplicateCooldownTagsToClient(Ability->GetAbilityID(), *CooldownTags);
}
}
}

这个函数是能力冷却系统的核心组成部分,它使得:

  1. 冷却状态查询:可以检查能力是否在冷却中
  2. 冷却分组:通过共享标签实现能力组冷却
  3. UI反馈:为玩家显示冷却状态
  4. 游戏逻辑:基于冷却状态做出决策
  5. 系统集成:与其他游戏系统(如buff、装备效果)交互

通过冷却标签,开发者可以构建复杂而灵活的冷却机制,满足各种游戏设计需求。

posted @ 2025-11-09 08:57  ycfenxi  阅读(20)  评论(0)    收藏  举报