-
Notifications
You must be signed in to change notification settings - Fork 861
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Composing Ability Tasks #138
Comments
I had a similar situation and decided to just make a new ability task for the specific scenario I needed. For example, wait for a confirm or cancel and also retrieve the target data under the cursor when that happens rather than trying to reuse existing tasks within other tasks. #pragma once
#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "WaitConfirmOrCancelWithData.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FWaitConfirmCancelWithDataSignature, const FGameplayAbilityTargetDataHandle&, DataHandle);
/**
* Ability Task to wait for confirm or cancel input, collecting and sending target data under the cursor in response.
*/
UCLASS()
class AURORASVEIL_API UWaitConfirmOrCancelWithData : public UAbilityTask
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FWaitConfirmCancelWithDataSignature OnConfirm;
UPROPERTY(BlueprintAssignable)
FWaitConfirmCancelWithDataSignature OnCancel;
UPROPERTY(BlueprintReadWrite, Category = "Ability|Tasks")
bool bIgnoreSelf;
UPROPERTY(BlueprintReadWrite, Category = "Ability|Tasks")
TEnumAsByte<ECollisionChannel> TraceChannel = ECC_Visibility;
UFUNCTION(BlueprintCallable, meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true"), Category = "Veil|AbilityTasks")
static UWaitConfirmOrCancelWithData* WaitConfirmOrCancelWithData(UGameplayAbility* OwningAbility, bool bInIgnoreSelf, ECollisionChannel InTraceChannel);
virtual void Activate() override;
protected:
virtual void OnDestroy(bool AbilityEnding) override;
private:
bool bRegisteredCallbacks;
void SendMouseCursorData(bool bCancelled) const;
void OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& DataHandle, FGameplayTag ActivationTag) const;
// Local Confirm/Cancel callbacks
UFUNCTION() void OnLocalConfirmCallback();
UFUNCTION() void OnLocalCancelCallback();
}; #include "AbilitySystem/AbilityTask/WaitConfirmOrCancelWithData.h"
#include "AbilitySystemComponent.h"
#include "VeilGameplayTags.h"
UWaitConfirmOrCancelWithData* UWaitConfirmOrCancelWithData::WaitConfirmOrCancelWithData(UGameplayAbility* OwningAbility, bool bInIgnoreSelf, ECollisionChannel InTraceChannel)
{
UWaitConfirmOrCancelWithData* Task = NewAbilityTask<UWaitConfirmOrCancelWithData>(OwningAbility);
Task->bIgnoreSelf = bInIgnoreSelf;
Task->TraceChannel = InTraceChannel;
return Task;
}
void UWaitConfirmOrCancelWithData::Activate()
{
if (AbilitySystemComponent.IsValid() && Ability)
{
const FGameplayAbilityActorInfo* Info = Ability->GetCurrentActorInfo();
if (Info->IsLocallyControlled())
{
AbilitySystemComponent->GenericLocalConfirmCallbacks.AddDynamic(this, &UWaitConfirmOrCancelWithData::OnLocalConfirmCallback);
AbilitySystemComponent->GenericLocalCancelCallbacks.AddDynamic(this, &UWaitConfirmOrCancelWithData::OnLocalCancelCallback);
bRegisteredCallbacks = true;
}
else
{
const FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle();
const FPredictionKey ActivationPredictionKey = GetActivationPredictionKey();
AbilitySystemComponent.Get()->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey).AddUObject(this, &UWaitConfirmOrCancelWithData::OnTargetDataReplicatedCallback);
const bool bCalledDelegate = AbilitySystemComponent.Get()->CallReplicatedTargetDataDelegatesIfSet(SpecHandle, ActivationPredictionKey);
if (!bCalledDelegate)
{
SetWaitingOnRemotePlayerData();
}
}
}
else
{
EndTask();
}
}
// Callbacks that when triggered will get the target data under the cursor and broadcast it
void UWaitConfirmOrCancelWithData::OnLocalConfirmCallback() { SendMouseCursorData(false); }
void UWaitConfirmOrCancelWithData::OnLocalCancelCallback() { SendMouseCursorData(true); }
// Finds the target data under the cursor, locally, and then replicates it to the server and broadcasts it to the client
void UWaitConfirmOrCancelWithData::SendMouseCursorData(bool bCancelled) const
{
FScopedPredictionWindow ScopedPrediction(AbilitySystemComponent.Get(), IsPredictingClient());
const APlayerController* PC = Ability->GetCurrentActorInfo()->PlayerController.Get();
const APawn* PlayerPawn = PC->GetPawn();
FCollisionQueryParams QueryParams;
if (bIgnoreSelf)
{
QueryParams.AddIgnoredActor(PlayerPawn);
}
FVector WorldLocation, WorldDirection;
float X, Y = 0.f;
PC->GetMousePosition(X, Y);
PC->DeprojectScreenPositionToWorld(X, Y, WorldLocation, WorldDirection);
const FVector TraceStart = WorldLocation;
const FVector TraceEnd = WorldLocation + WorldDirection * 10000.0f;
FHitResult CursorHit;
PC->GetWorld()->LineTraceSingleByChannel(CursorHit, TraceStart, TraceEnd, TraceChannel, QueryParams);
FGameplayAbilityTargetDataHandle DataHandle;
FGameplayAbilityTargetData_SingleTargetHit* Data = new FGameplayAbilityTargetData_SingleTargetHit();
Data->HitResult = CursorHit;
DataHandle.Add(Data);
AbilitySystemComponent->ServerSetReplicatedTargetData(
GetAbilitySpecHandle(),
GetActivationPredictionKey(),
DataHandle,
bCancelled ? FVeilGameplayTags::Input_CancelAbility : FVeilGameplayTags::Input_ConfirmAbility,
AbilitySystemComponent->ScopedPredictionKey);
// Broadcasting to the server or clients
if (ShouldBroadcastAbilityTaskDelegates())
{
if (bCancelled)
{
OnCancel.Broadcast(DataHandle);
}
else
{
OnConfirm.Broadcast(DataHandle);
}
}
}
// When the server receives the target data, broadcast it out
void UWaitConfirmOrCancelWithData::OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& DataHandle, const FGameplayTag ActivationTag) const
{
AbilitySystemComponent->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());
if (ShouldBroadcastAbilityTaskDelegates())
{
if (ActivationTag.MatchesTagExact(FVeilGameplayTags::Input_ConfirmAbility))
{
OnConfirm.Broadcast(DataHandle);
}
else
{
OnCancel.Broadcast(DataHandle);
}
}
}
// Cleanup
void UWaitConfirmOrCancelWithData::OnDestroy(const bool AbilityEnding)
{
if (bRegisteredCallbacks && AbilitySystemComponent.IsValid())
{
AbilitySystemComponent->GenericLocalConfirmCallbacks.RemoveDynamic(this, &UWaitConfirmOrCancelWithData::OnLocalConfirmCallback);
AbilitySystemComponent->GenericLocalCancelCallbacks.RemoveDynamic(this, &UWaitConfirmOrCancelWithData::OnLocalCancelCallback);
}
Super::OnDestroy(AbilityEnding);
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I often find myself using very similar Ability Tasks in different abilities. For example I have to play a montage and wait for input release, or play a montage and wait for target data, etc, so I was thinking of creating ability tasks that are actually a composition of other ability tasks. Yes, it's less flexible, but I've had enough use cases where I think it's worth creating something reusable.
That said, are there caveats of doing it that I should be aware of? For example, are there downsides of manually creating ability tasks inside of other ability tasks, manually cancelling and managing them, etc?
The text was updated successfully, but these errors were encountered: