UE5 GAS GameAbility源码解析 CanActivateAbility

文章目录


一、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;

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

总结

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

  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

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

具体例子:

  1. 模拟代理情况
cpp 复制代码
// 玩家A在客户端控制角色,玩家B在另一个客户端看到玩家A的角色
// 玩家B看到的玩家A角色是ROLE_SimulatedProxy
// 这个角色不能激活任何能力,只能接收网络同步的动画和效果
  1. 服务器权限能力:
cpp 复制代码
// 一个重要的管理型能力,如GM命令
// 设置NetSecurityPolicy = ServerOnly
// 只有服务器角色(ROLE_Authority)可以激活,客户端无法激活
  1. 客户端预测能力:
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 可以通过事件来触发能力
  • 这个函数在事件触发时被调用,决定能力是否响应该事件

蓝图自定义逻辑

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

具体例子

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

2.特定武器触发技能:

cpp 复制代码
// 事件:玩家按下技能键
// 能力:武器特殊技能
// 蓝图逻辑:检查当前装备的武器类型,只有装备特定武器时才响应
  1. 环境依赖能力:
cpp 复制代码
// 事件:进入特定区域
// 能力:区域增益效果
// 蓝图逻辑:检查玩家所在区域ID,只有特定区域才激活能力
  1. 组合技触发:
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.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 会包含失败原因:

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(): 获取为这个能力定义的冷却 GameplayEffect
  • GetGrantedTags(): 获取该 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);
        }
    }
}

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

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

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

相关推荐
远离UE44 小时前
UE5 GPU内部异步
ue5
1204157137 肖哥4 小时前
UE5 PCG PCGBlueprintElement简介
ue5
dong1326975 小时前
UE5FPS游戏开发教程(四)
ue5
dong1326971 天前
UE5FPS游戏开发教程(三)
ue5
日月云棠1 天前
UE5源码分析之Editor——AnimationBlueprintLibrary模块全面分析
ue5
远离UE41 天前
UE5 ECS 原理
ue5
mengzhi啊3 天前
UE5 把剑绑定到骨骼-给要播放的动画及动画蒙太奇添加武器
ue5
directx3d_beginner3 天前
32,每帧更新怪物朝向改为c++
ue5
日月云棠3 天前
UE5源码分析之Editor——AnimationEditor模块全面分析
ue5
吴梓穆4 天前
UE5 播放透明视频
ue5