[UE-GAS] UAbilityTask에서 APlayerController에 접근하는 방법에 대해

ChangJin·2024년 12월 4일

Unreal Engine - GAS

목록 보기
6/6
post-thumbnail

언리얼 엔진의 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를 가져오는 것이 좋습니다.


1. 코드 구현

2.1 UAbilityTask 기본 구조

우선, 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();
};

CPP 파일

#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);
	}
}

2 두 가지 접근 방법

방법 1: GetAvatarActor()를 활용한 접근

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);

방법 2: GAS ActorInfo를 활용한 접근

APlayerController* Controller = Ability->GetCurrentActorInfo()->PlayerController.Get();
FHitResult HitResult;
Controller->GetHitResultUnderCursor(ECollisionChannel::ECC_Visibility, true, HitResult);

3. 두 방법의 비교

방법 1: GetAvatarActor() 사용

  • 장점:

    • GAS 외부에서도 사용 가능.
    • 코드가 명시적이며 직관적.
  • 단점:

    • ACharacterAPlayerController를 전제로 하므로, 다른 Actor 유형에선 작동하지 않을 수 있음.
    • GAS 구조와 연계되지 않아 확장성이 낮음.

방법 2: Ability->GetCurrentActorInfo() 사용

  • 장점:

    • GAS와 완벽히 호환되고, 멀티플레이어 환경에서 잘 작동함
    • Ability->GetCurrentActorInfo()를 통해 필요한 모든 정보를 가져올 수 있어 다양한 정보에 접근 가능.
  • 단점:

    • GAS에 의존적이므로, GAS를 사용하지 않는 프로젝트에서는 적용하기 어려움.

5. 결론

GAS의 구조를 준수하는 방식으로 코드를 작성하면 다양한 정보를 가져올 수 있어서 편하지만, 간단한 작업에서는 직관적 접근 방식을 사용하는 것도 괜찮은 선택일 수 있습니다.

profile
게임 프로그래머

0개의 댓글