Last active
January 3, 2026 23:00
-
-
Save MajorTomAW/3cb303ddc2e443a70601de5b39d067dd to your computer and use it in GitHub Desktop.
Implementing a Targeting Ability Task using the Targeting System Plugin. URL: https://dev.epicgames.com/community/learning/tutorials/bEJV/unreal-engine-creating-a-targeting-ability-task-using-epic-s-targetingsystem-plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //@see https://dev.epicgames.com/community/learning/tutorials/bEJV/unreal-engine-creating-a-targeting-ability-task-using-epic-s-targetingsystem-plugin | |
| #include "AbilitySystem/Tasks/AbilityTask_WaitPerformTargeting.h" | |
| #include "AbilitySystemComponent.h" | |
| #include "AbilitySystemLog.h" | |
| #include "TargetingSystem/TargetingSubsystem.h" | |
| #include UE_INLINE_GENERATED_CPP_BY_NAME(AbilityTask_WaitPerformTargeting) | |
| UAbilityTask_WaitPerformTargeting::UAbilityTask_WaitPerformTargeting() | |
| : bForceTargetingOnServer(false) | |
| , bSkipReplicatingDataToServer(false) | |
| , bUseAsyncTargeting(false) | |
| { | |
| } | |
| UAbilityTask_WaitPerformTargeting* UAbilityTask_WaitPerformTargeting::WaitPerformTargeting( | |
| UGameplayAbility* OwningAbility, | |
| FName TaskInstanceName, | |
| UTargetingPreset* TargetingPreset, | |
| bool bForceTargetingOnServer, | |
| bool bSkipReplicatingDataToServer, | |
| bool bUseAsyncTargeting, | |
| AActor* OverrideSourceActor) | |
| { | |
| if (!IsValid(TargetingPreset)) | |
| { | |
| return nullptr; | |
| } | |
| UAbilityTask_WaitPerformTargeting* NewTask = NewAbilityTask<UAbilityTask_WaitPerformTargeting>(OwningAbility, TaskInstanceName); | |
| NewTask->TargetingPreset = TargetingPreset; | |
| NewTask->bForceTargetingOnServer = bForceTargetingOnServer; | |
| NewTask->bSkipReplicatingDataToServer = bSkipReplicatingDataToServer; | |
| NewTask->bUseAsyncTargeting = bUseAsyncTargeting; | |
| NewTask->OverrideSourceActor = OverrideSourceActor; | |
| return NewTask; | |
| } | |
| UAbilityTask_WaitPerformTargeting* UAbilityTask_WaitPerformTargeting::WaitPerformTargetFiltering( | |
| UGameplayAbility* OwningAbility, | |
| FName TaskInstanceName, | |
| UTargetingPreset* TargetingPreset, | |
| const TArray<AActor*> InTargets, | |
| bool bForceTargetingOnServer, | |
| bool bSkipReplicatingDataToServer, | |
| bool bUseAsyncTargeting, | |
| AActor* OverrideSourceActor) | |
| { | |
| if (!IsValid(TargetingPreset)) | |
| { | |
| return nullptr; | |
| } | |
| UAbilityTask_WaitPerformTargeting* NewTask = NewAbilityTask<UAbilityTask_WaitPerformTargeting>(OwningAbility, TaskInstanceName); | |
| NewTask->TargetingPreset = TargetingPreset; | |
| NewTask->bForceTargetingOnServer = bForceTargetingOnServer; | |
| NewTask->bSkipReplicatingDataToServer = bSkipReplicatingDataToServer; | |
| NewTask->bUseAsyncTargeting = bUseAsyncTargeting; | |
| NewTask->OverrideSourceActor = OverrideSourceActor; | |
| NewTask->InitialTargets = InTargets; | |
| return NewTask; | |
| } | |
| void UAbilityTask_WaitPerformTargeting::Activate() | |
| { | |
| // Sanity checks | |
| if (!IsValid(Ability) || !IsValid(TargetingPreset)) | |
| { | |
| return; | |
| } | |
| UAbilitySystemComponent* ASC = AbilitySystemComponent.Get(); | |
| if (!ensure(IsValid(ASC))) | |
| { | |
| return; | |
| } | |
| // We could have provided a different source actor | |
| AActor* SourceActor = IsValid(OverrideSourceActor) | |
| ? OverrideSourceActor.Get() | |
| : GetAvatarActor(); | |
| if (!ensure(IsValid(SourceActor))) | |
| { | |
| return; | |
| } | |
| UTargetingSubsystem* TargetingSub = UTargetingSubsystem::Get(SourceActor->GetWorld()); | |
| if (!ensure(TargetingSub)) | |
| { | |
| return; | |
| } | |
| const bool bIsLocallyControlled = Ability->GetCurrentActorInfo()->IsLocallyControlled(); | |
| // Locally Controlled: Okay | |
| // Authority: Okay | |
| // Not Locally Controlled and not force on server: Not Okay! | |
| const bool bShouldProduceTargetDataNow = bForceTargetingOnServer || bIsLocallyControlled; | |
| // If not locally controlled (server for remote client), see if TargetData was already sent | |
| // Otherwise, register callback for when it does get there. | |
| if (!bIsLocallyControlled) | |
| { | |
| // Register with the TargetData callbacks if we are expecting client to send them | |
| if (!bForceTargetingOnServer) | |
| { | |
| const FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle(); | |
| const FPredictionKey ActivationPredictionKey = GetActivationPredictionKey(); | |
| // Since multi-fire is supported, we still need to hook up the callbacks | |
| ASC->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey) | |
| .AddUObject(this, &ThisClass::OnTargetDataReadyCallback); | |
| ASC->AbilityTargetDataCancelledDelegate(SpecHandle, ActivationPredictionKey) | |
| .AddUObject(this, &ThisClass::OnTargetDataCancelledCallback); | |
| ASC->CallReplicatedTargetDataDelegatesIfSet(SpecHandle, ActivationPredictionKey); | |
| SetWaitingOnRemotePlayerData(); | |
| } | |
| } | |
| if (bShouldProduceTargetDataNow) | |
| { | |
| FTargetingSourceContext SourceContext; | |
| SourceContext.SourceActor = SourceActor; | |
| // Attempt to fill in any additional targets (for filtering only) | |
| FTargetingRequestHandle TargetingHandle = UTargetingSubsystem::MakeTargetRequestHandle(TargetingPreset, SourceContext); | |
| SetupInitialTargetsForRequest(TargetingHandle); | |
| FTargetingRequestDelegate Delegate = FTargetingRequestDelegate::CreateUObject(this, &ThisClass::OnTargetingRequested); | |
| // Now either start an async or immediate targeting request | |
| if (bUseAsyncTargeting) | |
| { | |
| UE_LOG(LogAbilitySystem, Verbose, TEXT("Starting async ability targeting task...")) | |
| FTargetingAsyncTaskData& AsyncTask = FTargetingAsyncTaskData::FindOrAdd(TargetingHandle); | |
| AsyncTask.bReleaseOnCompletion = true; | |
| TargetingSub->StartAsyncTargetingRequestWithHandle(TargetingHandle, Delegate); | |
| } | |
| else | |
| { | |
| UE_LOG(LogAbilitySystem, Verbose, TEXT("Starting immediate ability targeting task...")) | |
| FTargetingImmediateTaskData& ImmediateTask = FTargetingImmediateTaskData::FindOrAdd(TargetingHandle); | |
| ImmediateTask.bReleaseOnCompletion = true; | |
| TargetingSub->ExecuteTargetingRequestWithHandle(TargetingHandle, Delegate); | |
| } | |
| } | |
| } | |
| void UAbilityTask_WaitPerformTargeting::ExternalCancel() | |
| { | |
| if (ShouldBroadcastAbilityTaskDelegates()) | |
| { | |
| OnCancelled.Broadcast(FGameplayAbilityTargetDataHandle()); | |
| } | |
| Super::ExternalCancel(); | |
| } | |
| void UAbilityTask_WaitPerformTargeting::OnDestroy(bool bInOwnerFinished) | |
| { | |
| Super::OnDestroy(bInOwnerFinished); | |
| } | |
| void UAbilityTask_WaitPerformTargeting::OnTargetDataCancelledCallback() | |
| { | |
| // Client cancelled this Targeting Task (we're the server) | |
| if (ShouldBroadcastAbilityTaskDelegates()) | |
| { | |
| OnCancelled.Broadcast(FGameplayAbilityTargetDataHandle()); | |
| } | |
| EndTask(); | |
| } | |
| void UAbilityTask_WaitPerformTargeting::OnTargetDataReadyCallback( | |
| const FGameplayAbilityTargetDataHandle& TargetData, | |
| FGameplayTag ApplicationTag) | |
| { | |
| // Valid Target Data was replicated to use (we are server, was sent from client) | |
| FGameplayAbilityTargetDataHandle MutableData = TargetData; | |
| if (UAbilitySystemComponent* ASC = AbilitySystemComponent.Get()) | |
| { | |
| ASC->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey()); | |
| } | |
| if (ShouldBroadcastAbilityTaskDelegates()) | |
| { | |
| OnTargeted.Broadcast(MutableData); | |
| } | |
| EndTask(); | |
| } | |
| void UAbilityTask_WaitPerformTargeting::SetupInitialTargetsForRequest(FTargetingRequestHandle RequestHandle) const | |
| { | |
| if (InitialTargets.IsEmpty() ||!RequestHandle.IsValid()) | |
| { | |
| return; | |
| } | |
| // Fill in initial targetin results for filtering task | |
| FTargetingDefaultResultsSet& TargetingResults = FTargetingDefaultResultsSet::FindOrAdd(RequestHandle); | |
| for (AActor* Target : InitialTargets) | |
| { | |
| if (!IsValid(Target)) | |
| { | |
| continue; | |
| } | |
| // Make sure to not add targets twice | |
| const bool bAddResult = !TargetingResults.TargetResults.ContainsByPredicate( | |
| [Target] (const FTargetingDefaultResultData& Data) -> bool | |
| { | |
| return Data.HitResult.GetActor() == Target; | |
| }); | |
| if (!bAddResult) | |
| { | |
| continue; | |
| } | |
| FTargetingDefaultResultData* ResultData = new(TargetingResults.TargetResults) FTargetingDefaultResultData(); | |
| ResultData->HitResult.HitObjectHandle = FActorInstanceHandle(Target); | |
| ResultData->HitResult.Location = Target->GetActorLocation(); | |
| } | |
| } | |
| void UAbilityTask_WaitPerformTargeting::OnTargetingRequested(FTargetingRequestHandle TargetingHandle) | |
| { | |
| UAbilitySystemComponent* ASC = AbilitySystemComponent.Get(); | |
| if (!ensure(IsValid(ASC))) | |
| { | |
| EndTask(); | |
| return; | |
| } | |
| // Fill in the target data with the hit results from the targeting task | |
| FGameplayAbilityTargetDataHandle TargetData; | |
| TArray<FHitResult> HitResults; | |
| if (TargetingHandle.IsValid()) | |
| { | |
| if (FTargetingDefaultResultsSet* ResultData = FTargetingDefaultResultsSet::Find(TargetingHandle)) | |
| { | |
| for (const FTargetingDefaultResultData& TargetingResult : ResultData->TargetResults) | |
| { | |
| // Converting Targeting Result into Target Data | |
| FGameplayAbilityTargetData_SingleTargetHit* Data = new FGameplayAbilityTargetData_SingleTargetHit(); | |
| Data->HitResult = TargetingResult.HitResult; | |
| TargetData.Add(Data); | |
| } | |
| } | |
| } | |
| FScopedPredictionWindow ScopedPrediction(ASC, ShouldReplicateDataToServer()); | |
| if (IsPredictingClient()) | |
| { | |
| // If the server didn't target, we need to send the targeting data now! | |
| if (!bForceTargetingOnServer && !bSkipReplicatingDataToServer) | |
| { | |
| FGameplayTag ApplicationTag; | |
| ASC->CallServerSetReplicatedTargetData( | |
| GetAbilitySpecHandle(), | |
| GetActivationPredictionKey(), | |
| TargetData, | |
| ApplicationTag, | |
| ASC->ScopedPredictionKey); | |
| } | |
| } | |
| if (ShouldBroadcastAbilityTaskDelegates()) | |
| { | |
| OnTargeted.Broadcast(TargetData); | |
| } | |
| EndTask(); | |
| } | |
| bool UAbilityTask_WaitPerformTargeting::ShouldReplicateDataToServer() const | |
| { | |
| if (!IsValid(Ability)) | |
| { | |
| return false; | |
| } | |
| /* Send TargetData to server IF: | |
| * - we are the client. | |
| * - We don't want to skip replication to server. | |
| * - This task can't produce target data on server. */ | |
| const FGameplayAbilityActorInfo* Info = Ability->GetCurrentActorInfo(); | |
| if (!Info->IsNetAuthority() && | |
| !bSkipReplicatingDataToServer && | |
| !bForceTargetingOnServer) | |
| { | |
| return true; | |
| } | |
| return false; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //@see https://dev.epicgames.com/community/learning/tutorials/bEJV/unreal-engine-creating-a-targeting-ability-task-using-epic-s-targetingsystem-plugin | |
| #pragma once | |
| #include "CoreMinimal.h" | |
| #include "Abilities/Tasks/AbilityTask.h" | |
| // We need those headers as well | |
| #include "Abilities/Tasks/AbilityTask_WaitTargetData.h" | |
| #include "TargetingSystem/TargetingPreset.h" | |
| #include "AbilityTask_WaitPerformTargeting.generated.h" | |
| /** Ability task to both perform targeting and filtering. */ | |
| UCLASS() | |
| class UAbilityTask_WaitPerformTargeting : public UAbilityTask | |
| { | |
| GENERATED_BODY() | |
| public: | |
| UAbilityTask_WaitPerformTargeting(); | |
| /** Called when the targeting request has been completed and target data is valid. */ | |
| UPROPERTY(BlueprintAssignable) | |
| FWaitTargetDataDelegate OnTargeted; | |
| /** Called when targeting request was cancelled, target data will NOT be valid. */ | |
| UPROPERTY(BlueprintAssignable) | |
| FWaitTargetDataDelegate OnCancelled; | |
| /** Performs a targeting request based on the given Targeting Preset. | |
| * @param bForceTargetingOnServer If true, the server will also perform targeting instead of relying on the data the client sent | |
| * @param bSkipReplicatingDataToServer If true, server won't receive generated target data produced by the client. | |
| */ | |
| UFUNCTION(BlueprintCallable, Category="Ability|Tasks", DisplayName="Wait Perform Targeting Ability Task", meta=(HidePin=OwningAbility, DefaultToSelf=OwningAbility, BlueprintInternalUseOnly=true)) | |
| static UAbilityTask_WaitPerformTargeting* WaitPerformTargeting(UGameplayAbility* OwningAbility, FName TaskInstanceName, UTargetingPreset* TargetingPreset, bool bForceTargetingOnServer, bool bSkipReplicatingDataToServer, bool bUseAsyncTargeting, AActor* OverrideSourceActor = nullptr); | |
| /** Performs a target filtering request based on the given Targeting Preset. | |
| * @param bForceTargetingOnServer If true, the server will also perform targeting instead of relying on the data the client sent | |
| * @param bSkipReplicatingDataToServer If true, server won't receive generated target data produced by the client. | |
| */ | |
| UFUNCTION(BlueprintCallable, Category="Ability|Tasks", DisplayName="Wait Perform Target Filtering Ability Task", meta=(HidePin=OwningAbility, DefaultToSelf=OwningAbility, BlueprintInternalUseOnly=true)) | |
| static UAbilityTask_WaitPerformTargeting* WaitPerformTargetFiltering(UGameplayAbility* OwningAbility, FName TaskInstanceName, UTargetingPreset* TargetingPreset, const TArray<AActor*> InTargets, bool bForceTargetingOnServer, bool bSkipReplicatingDataToServer, bool bUseAsyncTargeting, AActor* OverrideSourceActor = nullptr); | |
| protected: | |
| //~ Begin UAbilityTask Interface | |
| virtual void Activate() override; | |
| virtual void ExternalCancel() override; | |
| virtual void OnDestroy(bool bInOwnerFinished) override; | |
| //~ End UAbilityTask Interface | |
| UFUNCTION() | |
| void OnTargetDataCancelledCallback(); | |
| UFUNCTION() | |
| void OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& TargetData, FGameplayTag ApplicationTag); | |
| /** Helper function to push the InitialTargets array to the DataStore for Filtering requests. */ | |
| void SetupInitialTargetsForRequest(FTargetingRequestHandle RequestHandle) const; | |
| void OnTargetingRequested(FTargetingRequestHandle TargetingHandle); | |
| virtual bool ShouldReplicateDataToServer() const; | |
| private: | |
| /** Targeting preset to request. */ | |
| UPROPERTY() | |
| TObjectPtr<UTargetingPreset> TargetingPreset; | |
| /** Initial targets to pass into the request (only populated for filtering requests). */ | |
| UPROPERTY() | |
| TArray<TObjectPtr<AActor>> InitialTargets; | |
| /** Whether the server should be forced to always generate target data too. */ | |
| uint8 bForceTargetingOnServer:1; | |
| /** Whether to skip replicating the generated target data to the server. */ | |
| uint8 bSkipReplicatingDataToServer:1; | |
| /** Whether to perform this request immediately or async. */ | |
| uint8 bUseAsyncTargeting:1; | |
| UPROPERTY() | |
| TObjectPtr<AActor> OverrideSourceActor; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment