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、装备效果)交互

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

相关推荐
危险库6 小时前
【UE4/UE5】在虚幻引擎中创建控制台指令的几种方法
c++·ue5·游戏引擎·ue4·虚幻
m0_552200821 天前
《UE5_C++多人TPS完整教程》学习笔记60 ——《P61 开火蒙太奇(Fire Montage)》
c++·游戏·ue5
avi91112 天前
Unreal虚幻粒子系统二三事
ue5·虚幻引擎·unreal
一梦、んんん3 天前
UE 雷达干扰效果模拟
ue5
玉龙20253 天前
虚幻引擎|UE5制作DeepSeek插件并打包发布
ue5·游戏引擎·虚幻·虚幻引擎基础入门·=学习·虚幻引擎插件
努力的小钟3 天前
Unreal Engine GameplayTag匹配功能详解
ue5
司徒法克3 天前
ue编辑器视口鼠标消失的问题
ue5
CandyU23 天前
UE5 小知识点 —— 09 - 旋转小问题
ue5
AA陈超4 天前
虚幻引擎UE5专用服务器游戏开发-21 连招技能动画蒙太奇播放
c++·游戏·ue5·游戏引擎·虚幻