UE5 GAS GameAbility源码解析 EndAbility
文章目录
- 一、EndAbility
- 具体例子说明
- 二、IsEndAbilityValid
- 逐行详细注释:
- 1. 重复调用保护
- 2. 能力系统组件有效性检查
- 3. 能力规格状态检查
- 具体例子说明:
- 例子1:角色冲刺能力
- 例子2:火球法术能力
- 例子3:被动光环能力
- 返回值说明:
- 三、K2_EndAbility、K2_EndAbilityLocally
- 关键参数说明
- 具体例子说明
- 例子1:多人游戏中的治疗技能
- 例子2:本地视觉特效能力
- 例子3:客户端预测的移动能力
- 网络同步场景对比
- 使用 `K2_EndAbility()` 的情况:
- 使用 `K2_EndAbilityLocally()` 的情况:
- 实际应用建议
- 使用 `K2_EndAbility()` 当:
- 使用 `K2_EndAbilityLocally()` 当:
- 总结
一、EndAbility
void UGameplayAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{// 检查是否可以结束这个能力if (IsEndAbilityValid(Handle, ActorInfo)){// 检查作用域锁计数,如果大于0表示能力正在被锁定,不能立即结束if (ScopeLockCount > 0){UE_LOG(LogAbilitySystem, Verbose, TEXT("Attempting to end Ability %s but ScopeLockCount was greater than 0, adding end to the WaitingToExecute Array"), *GetName());// 将结束操作添加到等待执行队列,等锁释放后执行WaitingToExecute.Add(FPostLockDelegate::CreateUObject(this, &UGameplayAbility::EndAbility, Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled));return;}// 对于非实例化策略的能力,设置能力结束标志if (GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced){bIsAbilityEnding = true;}// 调用蓝图的结束事件,给蓝图机会处理结束逻辑K2_OnEndAbility(bWasCancelled);// 检查蓝图是否已经结束了这个能力if (bIsActive == false && GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced){return;}// 停止所有与这个能力相关的计时器和延迟操作UWorld* MyWorld = GetWorld();if (MyWorld){MyWorld->GetLatentActionManager().RemoveActionsForObject(this);if (FAbilitySystemTweaks::ClearAbilityTimers){MyWorld->GetTimerManager().ClearAllTimersForObject(this);}}// 广播能力结束事件并清除委托绑定OnGameplayAbilityEnded.Broadcast(this);OnGameplayAbilityEnded.Clear();// 广播带数据的结束事件并清除委托绑定OnGameplayAbilityEndedWithData.Broadcast(FAbilityEndedData(this, Handle, bReplicateEndAbility, bWasCancelled));OnGameplayabilityEndedWithData.Clear();// 对于非实例化策略的能力,重置活动状态if (GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced){bIsActive = false;bIsAbilityEnding = false;}// 清理所有活跃的任务for (int32 TaskIdx = ActiveTasks.Num() - 1; TaskIdx >= 0 && ActiveTasks.Num() > 0; --TaskIdx){UGameplayTask* Task = ActiveTasks[TaskIdx];if (Task){Task->TaskOwnerEnded(); // 通知任务所有者已结束}}ActiveTasks.Reset(); // 清空任务数组但不释放内存,因为对象可能很快被销毁// 获取能力系统组件并进行清理if (UAbilitySystemComponent* const AbilitySystemComponent = ActorInfo->AbilitySystemComponent.Get()){// 如果需要复制,通知网络结束能力if (bReplicateEndAbility){AbilitySystemComponent->ReplicateEndOrCancelAbility(Handle, ActivationInfo, this, false);}// 移除能力添加的标签AbilitySystemComponent->RemoveLooseGameplayTags(ActivationOwnedTags);// 根据网络设置处理标签复制if (UAbilitySystemGlobals::Get().ShouldReplicateActivationOwnedTags()){if (GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalPredicted || GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::ServerInitiated){// 对于客户端也执行的能力,使用最小复制AbilitySystemComponent->RemoveMinimalReplicationGameplayTags(ActivationOwnedTags);}else{// 其他情况使用完全复制AbilitySystemComponent->RemoveReplicatedLooseGameplayTags(ActivationOwnedTags);}}// 移除跟踪的GameplayCuesfor (FGameplayTag& GameplayCueTag : TrackedGameplayCues){AbilitySystemComponent->RemoveGameplayCue(GameplayCueTag);}TrackedGameplayCues.Empty();// 处理能力取消相关逻辑if (CanBeCanceled()){AbilitySystemComponent->HandleChangeAbilityCanBeCanceled(GetAssetTags(), this, false);}// 处理能力阻塞相关逻辑if (IsBlockingOtherAbilities()){AbilitySystemComponent->ApplyAbilityBlockAndCancelTags(GetAssetTags(), this, false, BlockAbilitiesWithTag, false, CancelAbilitiesWithTag);}// 清除复制数据缓存AbilitySystemComponent->ClearAbilityReplicatedDataCache(Handle, CurrentActivationInfo);// 通知能力系统组件能力已结束AbilitySystemComponent->NotifyAbilityEnded(Handle, this, bWasCancelled);}// 对于实例化能力,重置当前事件数据if (IsInstantiated()){CurrentEventData = FGameplayEventData{};}}
}
具体例子说明
假设我们有一个火球术能力:
- 能力激活时:
- 添加标签
Gameplay.Activating.Fireball
(激活标签) - 启动GameplayCue显示施法特效
- 创建任务来处理火球飞行逻辑
- 当调用
EndAbility
时:
// 假设玩家在施法过程中被打断,bWasCancelled = true
FireballAbility->EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
执行流程:
- 作用域锁检查:如果没有锁定,继续执行
- 蓝图事件:调用
K2_OnEndAbility(true)
,蓝图可以播放打断动画 - 清理任务:停止火球飞行任务和任何相关的计时器
- 移除标签:移除
Gameplay.Activating.Fireball
标签 - 移除特效:移除施法
GameplayCue
- 网络同步:如果是服务器,复制结束事件到客户端
- 取消阻塞:如果火球术阻塞了其他能力,现在解除阻塞
- 通知系统:告诉
AbilitySystemComponent
火球术已结束
- 如果是正常结束(火球命中目标):
bWasCancelled = false
- 可能会在
K2_OnEndAbility
中播放命中特效 - 其他清理步骤相同
这个函数确保了能力结束时所有相关资源都被正确清理,状态被重置,系统回到稳定状态。
二、IsEndAbilityValid
bool UGameplayAbility::IsEndAbilityValid(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo) const
{// 保护机制:防止EndAbility被多次调用// 结束AbilityState可能会导致此函数被再次调用if ((bIsActive == false || bIsAbilityEnding == true) && GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced){UE_LOG(LogAbilitySystem, Verbose, TEXT("IsEndAbilityValid returning false on Ability %s due to EndAbility being called multiple times"), *GetName());return false;}// 检查能力是否有有效的所有者组件UAbilitySystemComponent* AbilityComp = ActorInfo ? ActorInfo->AbilitySystemComponent.Get() : nullptr;if (AbilityComp == nullptr){UE_LOG(LogAbilitySystem, Verbose, TEXT("IsEndAbilityValid returning false on Ability %s due to AbilitySystemComponent being invalid"), *GetName());return false;}// 检查这是否是NonInstanced能力或者能力是否处于激活状态const FGameplayAbilitySpec* Spec = AbilityComp->FindAbilitySpecFromHandle(Handle);const bool bIsSpecActive = Spec ? Spec->IsActive() : IsActive();if (!bIsSpecActive){UE_LOG(LogAbilitySystem, Verbose, TEXT("IsEndAbilityValid returning false on Ability %s due spec not being active"), *GetName());return false;}return true;
}
逐行详细注释:
1. 重复调用保护
if ((bIsActive == false || bIsAbilityEnding == true) && GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced)
- 检查条件:如果能力已经不活跃或正在结束,且不是NonInstanced实例策略
- 目的:防止对同一个能力实例多次调用EndAbility
- 实例策略说明:
NonInstanced
:能力不创建实例,所有调用共享同一份代码InstancedPerActor
:每个Actor创建一个实例InstancedPerExecution
:每次执行创建一个新实例
2. 能力系统组件有效性检查
UAbilitySystemComponent* AbilityComp = ActorInfo ? ActorInfo->AbilitySystemComponent.Get() : nullptr;
if (AbilityComp == nullptr)
- 检查条件:确保拥有有效的AbilitySystemComponent
- 重要性:AbilitySystemComponent是管理所有游戏能力的核心组件
3. 能力规格状态检查
const FGameplayAbilitySpec* Spec = AbilityComp->FindAbilitySpecFromHandle(Handle);
const bool bIsSpecActive = Spec ? Spec->IsActive() : IsActive();
- 功能:通过句柄查找对应的能力规格,并检查其激活状态
- 回退机制:如果找不到Spec,使用基础的IsActive()方法
具体例子说明:
例子1:角色冲刺能力
// 假设有一个冲刺能力
class UDashAbility : public UGameplayAbility
{// 实现...
};// 场景:玩家按下冲刺键
void APlayerCharacter::OnDashPressed()
{// 激活冲刺能力AbilitySystemComponent->TryActivateAbility(DashAbilityHandle);// 2秒后尝试结束冲刺GetWorld()->GetTimerManager().SetTimer(DashTimer, [this]() {// 这里会调用IsEndAbilityValid进行检查AbilitySystemComponent->CancelAbility(DashAbility);}, 2.0f, false);
}
检查过程:
- 第一次调用IsEndAbilityValid:所有检查通过,可以结束
- 如果意外再次调用:bIsAbilityEnding为true,返回false,防止重复结束
例子2:火球法术能力
// 火球法术,使用InstancedPerExecution策略
class UFireballAbility : public UGameplayAbility
{EGameplayAbilityInstancingPolicy::InstancedPerExecution
};// 场景:法师连续施放火球
void AWizard::CastFireball()
{// 每次施放都创建新实例AbilitySystemComponent->TryActivateAbility(FireballAbilityHandle);// 如果在火球飞行过程中再次施放// 新的火球实例与旧的互不影响// 每个实例独立检查IsEndAbilityValid
}
例子3:被动光环能力
// 被动能力,使用NonInstanced策略
class UAuraAbility : public UGameplayAbility
{EGameplayAbilityInstancingPolicy::NonInstanced
};// 场景:角色获得治疗光环
void APlayerCharacter::AcquireAura()
{// 激活被动光环AbilitySystemComponent->TryActivateAbility(AuraAbilityHandle);// 即使光环已经激活,也可以安全地多次调用结束检查// 因为NonInstanced能力不受重复调用保护的限制
}
返回值说明:
- 返回true:能力可以安全结束
- 返回false:能力不能结束,可能因为:
- 能力已经结束或正在结束(重复调用保护)
- 缺少有效的AbilitySystemComponent
- 能力规格不处于激活状态
这个函数是GameplayAbilitySystem中重要的安全机制,确保能力生命周期管理的正确性和稳定性。
三、K2_EndAbility、K2_EndAbilityLocally
void UGameplayAbility::K2_EndAbility()
{ensure(CurrentActorInfo); // 确保当前有有效的Actor信息bool bReplicateEndAbility = true; // 需要在网络上复制bool bWasCancelled = false; // 正常结束,不是被取消EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicateEndAbility, bWasCancelled);
}void UGameplayAbility::K2_EndAbilityLocally()
{ensure(CurrentActorInfo); // 确保当前有有效的Actor信息bool bReplicateEndAbility = false; // 不在网络上复制bool bWasCancelled = false; // 正常结束,不是被取消EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicateEndAbility, bWasCancelled);
}
关键参数说明
bReplicateEndAbility
- true: 结束事件会在网络上的所有客户端同步
- false: 只在本地客户端结束,不进行网络同步
bWasCancelled
- false: 能力正常结束(完成任务)
- true: 能力被中断取消
具体例子说明
例子1:多人游戏中的治疗技能
class UHealAbility : public UGameplayAbility
{UFUNCTION(BlueprintCallable)void CompleteHealing(){// 治疗完成,需要在所有客户端同步结束K2_EndAbility(); // bReplicateEndAbility = true}UFUNCTION(BlueprintCallable) void InterruptHealing(){// 被攻击打断,本地立即结束,网络同步由其他系统处理K2_EndAbilityLocally(); // bReplicateEndAbility = false}
};
场景:
- 玩家A对玩家B使用治疗技能
- 治疗完成后调用
K2_EndAbility()
,所有玩家都能看到治疗结束的效果 - 如果治疗过程中玩家A被攻击,调用
K2_EndAbilityLocally()
立即在本地结束,避免延迟
例子2:本地视觉特效能力
class ULocalVFXAbility : public UGameplayAbility
{UFUNCTION(BlueprintCallable)void SpawnParticles(){// 生成本地粒子特效SpawnLocalParticleSystem();// 特效播放完毕后,只需要在本地结束GetWorld()->GetTimerManager().SetTimer(EndTimer, this, &ULocalVFXAbility::OnParticlesFinished, 3.0f, false);}void OnParticlesFinished(){// 纯视觉效果,不需要网络同步K2_EndAbilityLocally();}
};
例子3:客户端预测的移动能力
class UDashAbility : public UGameplayAbility
{UFUNCTION(BlueprintCallable)void StartDash(){// 客户端预测的冲刺PerformDashMovement();// 设置结束计时器GetWorld()->GetTimerManager().SetTimer(DashTimer,this,&UDashAbility::OnDashComplete,DashDuration,false);}void OnDashComplete(){if (GetOwningActorFromActorInfo()->HasAuthority()){// 服务器端:同步结束到所有客户端K2_EndAbility();}else{// 客户端:本地结束,等待服务器确认K2_EndAbilityLocally();}}
};
网络同步场景对比
使用 K2_EndAbility()
的情况:
// 服务器端
void APlayerCharacter::OnAbilityCompleted()
{// 服务器调用,所有客户端都会收到结束事件Ability->K2_EndAbility();
}// 客户端1、客户端2、客户端3都会同步结束该能力
使用 K2_EndAbilityLocally()
的情况:
// 客户端预测
void APlayerCharacter::OnLocalPrediction()
{// 只有本地客户端结束,其他客户端不受影响Ability->K2_EndAbilityLocally();
}// 只有调用者本地结束,其他客户端保持原状态
实际应用建议
使用 K2_EndAbility()
当:
- 能力结束影响游戏状态(生命值、资源等)
- 需要在所有客户端同步视觉效果
- 服务器发起的结束操作
- 重要的游戏流程状态改变
使用 K2_EndAbilityLocally()
当:
- 纯本地视觉效果
- 客户端预测操作
- 临时性的本地状态
- 避免网络延迟影响的用户体验
总结
这两个函数提供了灵活的能力结束机制:
K2_EndAbility()
:用于需要网络同步的重要能力结束K2_EndAbilityLocally()
:用于本地化的、不需要同步的能力结束
正确选择使用哪个函数对于多人游戏的网络同步和性能优化至关重要。