
언리얼 엔진의 Gameplay Ability System (GAS)에서
UAbilityTask를 활용하여 마우스 커서 위치의 타겟 데이터를 얻어오는 방법은 다양하게 구현될 수 있습니다. 이번 글에서는 마우스 커서 위치를 가져오기 위해 Controller에 접근하는 두 가지 방법을 알아보겠습니다.
들어가기에 앞서..
UAbilityTask는 UGameplayTask를 상속받고 있으니, 태스크의 Owner인 Avatar Actor를 GetAvatarActor()로 가져와 ACharacter로 캐스팅한 뒤, 이를 통해 Controller를 얻으면 되지 않을까? 라고 생각했습니다. 실제로 이렇게 하면 정상적으로 작동합니다.
그러나 이 방법에는 한계가 있습니다.
예를 들어, 탱크나 비행기와 같은 Pawn 기반 캐릭터는 APawn 클래스를 상속받아 만들기 때문에, ACharacter로의 캐스팅이 실패할 수 있습니다. 이는 GetAvatarActor()가 반환하는 Actor가 반드시 ACharacter라고 보장할 수 없기 때문입니다.
또한, NPC와 같은 컨트롤러가 없거나 다른 방식으로 조작되는 Actor의 경우에도 문제가 발생할 수 있습니다.
NPC는 APlayerController 대신 AIController와 연결될 가능성이 높으며,
경우에 따라 컨트롤러 자체를 사용하지 않는 Actor일 수도 있습니다.
그리고 다른 Actor를 마우스 클릭으로 이동시키는 작업을 하려 한다면, Avatar Actor에 직접 접근하는 것이 아니라, 해당 Task를 생성한 GameplayAbility 클래스를 통해 작업해야 합니다.
결론적으로, UAbilityTask에서 멀티플레이가 가능하게 구현을 하기 위해서는 GameplayAbility 클래스에서 GetCurrentActorInfo()를 호출하여 Controller를 가져오는 것이 좋습니다.
우선, UAbilityTask를 상속받아 타겟 데이터를 생성하는 기본 클래스를 작성합니다.
#pragma once
#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "TargetDataUnderMouse.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMouseTargetDataSignature, const FGameplayAbilityTargetDataHandle&, DataHandle);
UCLASS()
class AURA_API UTargetDataUnderMouse : public UAbilityTask
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category="Ability|Task", meta=(DisplayName = "TargetDataUnderMouse", HidePin="OwingAbility", DefaultToSelf = "OwingAbility", BlueprintInternalUseOnly = true))
static UTargetDataUnderMouse* CreateTargetDataUnderMouse(UGameplayAbility* OwingAbility);
UPROPERTY(BlueprintAssignable, Category="Ability|Task")
FMouseTargetDataSignature ValidData;
private:
virtual void Activate() override;
void SendMouseCursorData();
};
#include "TargetDataUnderMouse.h"
#include "GameFramework/Character.h"
#include "AbilitySystemComponent.h"
UTargetDataUnderMouse* UTargetDataUnderMouse::CreateTargetDataUnderMouse(UGameplayAbility* OwingAbility)
{
return NewAbilityTask<UTargetDataUnderMouse>(OwingAbility);
}
void UTargetDataUnderMouse::Activate()
{
if (Ability->GetCurrentActorInfo()->IsLocallyControlled())
{
SendMouseCursorData();
}
}
void UTargetDataUnderMouse::SendMouseCursorData()
{
APlayerController* Controller = Ability->GetCurrentActorInfo()->PlayerController.Get();
FHitResult HitResult;
Controller->GetHitResultUnderCursor(ECollisionChannel::ECC_Visibility, true, HitResult);
FGameplayAbilityTargetDataHandle DataHandle;
FGameplayAbilityTargetData_SingleTargetHit* Data = new FGameplayAbilityTargetData_SingleTargetHit();
Data->HitResult = HitResult;
DataHandle.Add(Data);
AbilitySystemComponent->ServerSetReplicatedTargetData(
GetAbilitySpecHandle(),
GetActivationPredictionKey(),
DataHandle,
FGameplayTag(),
AbilitySystemComponent->ScopedPredictionKey);
if (ShouldBroadcastAbilityTaskDelegates())
{
ValidData.Broadcast(DataHandle);
}
}
FHitResult HitResult;
const ACharacter* Character = Cast<ACharacter>(GetAvatarActor());
check(Character);
APlayerController* Controller = Cast<APlayerController>(Character->GetController());
check(Controller);
Controller->GetHitResultUnderCursor(ECollisionChannel::ECC_Visibility, true, HitResult);
APlayerController* Controller = Ability->GetCurrentActorInfo()->PlayerController.Get();
FHitResult HitResult;
Controller->GetHitResultUnderCursor(ECollisionChannel::ECC_Visibility, true, HitResult);
장점:
단점:
ACharacter와 APlayerController를 전제로 하므로, 다른 Actor 유형에선 작동하지 않을 수 있음.장점:
Ability->GetCurrentActorInfo()를 통해 필요한 모든 정보를 가져올 수 있어 다양한 정보에 접근 가능.단점:
GAS의 구조를 준수하는 방식으로 코드를 작성하면 다양한 정보를 가져올 수 있어서 편하지만, 간단한 작업에서는 직관적 접근 방식을 사용하는 것도 괜찮은 선택일 수 있습니다.