Udemy Unreal - Gameplay Ability System - Top Down RPG (Section 1)

๊น€์ข…๋ฏผยท2025๋…„ 3์›” 12์ผ

Udemy Unreal - GAS

๋ชฉ๋ก ๋ณด๊ธฐ
1/7

๐Ÿ“– ์œ ๋ฐ๋ฏธ GAS ๊ฐ•์˜ ์ค‘ ๊ธฐ๋กํ•ด๋‘๊ณ  ์‹ถ์€ ๋‚ด์šฉ์„ ์ ์–ด๋‘” ํฌ์ŠคํŠธ

Section 1 : ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ๊ณผ ์บ๋ฆญํ„ฐ, ์ธํ’‹ ๋“ฑ ๊ธฐ๋ณธ ์…‹์—…์„ ๋‹ค๋ฃจ๊ณ  ์žˆ์Œ


Enhanced Input

{ํ”„๋กœ์ ํŠธ๋ช…}.bulid.cs

  • EnhancedInput ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ชจ๋“ˆ ๋“ฑ๋ก ํ•„์š”
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" , "EnhancedInput" });

Input Mapping Context , Input Action ํ•„์š”

// AuraPlayerController ํด๋ž˜์Šค์˜ ๋ฉค๋ฒ„
	UPROPERTY(EditAnywhere, Category="Input")
	TObjectPtr<UInputMappingContext> AuraContext;
	UPROPERTY(EditAnywhere, Category="Input")
	TObjectPtr<UInputAction> MoveAction;

EnhancedInput Subsystem์— ๋“ฑ๋ก

  • EnhancedInputLocalPlayerSubsystem์— InputMappingContext ์ถ”๊ฐ€ํ•˜๊ธฐ
  • Subsystem์€ ์‹ฑ๊ธ€ํ†ค ํด๋ž˜์Šค
  • check()์œผ๋กœ ValidCheck : Throw ASSERT
void AAuraPlayerController::BeginPlay()
{
	Super::BeginPlay();
	// Assert , ์ธํ’‹์ด ์ œ๋Œ€๋กœ ์•ˆ ๋˜๋ฉด ์–ด์ฐจํ”ผ ๊ฒŒ์ž„ ๋ชปํ•จ
	check(AuraContext);

	UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer());
	check(Subsystem);
	Subsystem->AddMappingContext(AuraContext,0);
}

SetupInputComponent์—์„œ InputAction๊ณผ ๋™์ž‘ ํ•จ์ˆ˜ ๋ฐ”์ธ๋”ฉํ•˜๊ธฐ

  • InputComponent๋Š” ์ปจํŠธ๋กค๋Ÿฌ์— ์›๋ž˜ ์žˆ๋Š” ๋ฉค๋ฒ„๊ณ  ํ”„๋กœ์ ํŠธ ์„ค์ •์—์„œ ์ธํ•ธ์Šค๋“œ์ธํ’‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋””ํดํŠธ ํด๋ž˜์Šค๋กœ ์„ค์ •๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์บ์ŠคํŒ…์ด ๊ฐ€๋Šฅํ•œ ๊ฒƒ.(๋””ํดํŠธ ํด๋ž˜์Šค ๋ณ€๊ฒฝํ•˜๋ฉด ์•ˆ ๋  ์ˆ˜ ์žˆ์Œ)
  • CastChecked<>() : ์บ์ŠคํŒ… + Valid Check (๋ฐ˜๋“œ์‹œ ์„ฑ๊ณตํ•ด์•ผํ•˜๋Š” ์ƒํ™ฉ์— ์‚ฌ์šฉ)
void AAuraPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();

	// ํ”„๋กœ์ ํŠธ ๋””ํดํŠธ ์„ค์ •์— ์ธํ•ธ์Šค๋“œ์ธํ’‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ๊ธฐ๋•Œ๋ฌธ์— ์บ์ŠคํŒ… ๊ฐ€๋Šฅ
	UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);
	EnhancedInputComponent->BindAction(MoveAction,ETriggerEvent::Triggered,this,&AAuraPlayerController::Move);

}

์ž…๋ ฅ ๋ฐ์ดํ„ฐ ๋ฐ›์•„์˜ค๊ธฐ

  • FInputActionValue ๋ฅผ ์ ์ ˆํ•œ ๋ฐ์ดํ„ฐ ์œ ํ˜•์œผ๋กœ ์บ์ŠคํŒ…ํ•ด์„œ ์‚ฌ์šฉ (InputAction์— ์ด๋ฏธ ์„ค์ •๋œ ์œ ํ˜•์ด ์žˆ์Œ)
void AAuraPlayerController::Move(const FInputActionValue& InputActionValue)
{
	const FVector2d InputAxisVector = InputActionValue.Get<FVector2D>();
...
}

์บ๋ฆญํ„ฐ

bOrientRotationToMovement : ์ž…๋ ฅ ๋ฐฉํ–ฅ์— ๋”ฐ๋ผ ์บ๋ฆญํ„ฐ ํšŒ์ „

AAuraCharacter::AAuraCharacter()
{
	// ํƒ‘๋‹ค์šด ๋ทฐ๋กœ ์ง„ํ–‰๋˜๋Š” ๊ฒŒ์ž„์ด๋ฏ€๋กœ ์นด๋ฉ”๋ผ๋Š” ๊ณ ์ •๋˜์–ด์žˆ๊ณ  ์ž…๋ ฅ ๋ฐฉํ–ฅ์— ๋”ฐ๋ผ ์บ๋ฆญํ„ฐ๊ฐ€ ํšŒ์ „ํ•˜๋„๋ก ์„ค์ •
	GetCharacterMovement()->bOrientRotationToMovement = true;
	GetCharacterMovement()->RotationRate = FRotator(0.f, 400.f, 0.f);
    // ์บ๋ฆญํ„ฐ๊ฐ€ ๋ฐ”๋‹ฅ์— ๋ถ™์–ด์žˆ๋„๋ก ์„ค์ •
	GetCharacterMovement()->bConstrainToPlane = true;
	GetCharacterMovement()->bSnapToPlaneAtStart = true;

	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;
}

๋ž˜ํผ(Wrapper)

TObjectPrt<T> : ์Šค๋งˆํŠธ ํฌ์ธํ„ฐ

  • ํŒจํ‚ค์ง€ ๋ฒ„์ „์—์„œ๋Š” ๊ทธ๋ƒฅ ํฌ์ธํ„ฐ(Raw Pointer)์™€ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•˜์ง€๋งŒ ์—๋””ํ„ฐ์—์„œ๋Š” ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•œ๋‹ค.
  • ํด๋ž˜์Šค ๋ฉค๋ฒ„์— ํฌ์ธํ„ฐ๋ฅผ ์“ธ ๋•Œ TObjectPrt<T> ์‚ฌ์šฉ ๊ถŒ์žฅ
  • Access Tracking , Lazy Loading ... ์ง€์›
UPROPERTY(EditAnywhere,Category="Combat")
	TObjectPtr<USkeletalMeshComponent> Weapon;

TScriptInterface<T> : ์ธํ„ฐํŽ˜์ด์Šค ํฌ์ธํ„ฐ ๋ž˜ํผ

  • ์ธํ„ฐํŽ˜์ด์Šค ํฌ์ธํ„ฐ๋ฅผ ๋ฉค๋ฒ„๋กœ ์ง์ ‘ ๋‘๋Š” ๋Œ€์‹  ํฌ์ธํ„ฐ ์‚ฌ์šฉ ๊ถŒ์žฅ
TScriptInterface<IEnemyInterface> LastActor;

FName

FName ๊ตฌ์กฐ์ฒด๋Š” ๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ณ„์ด ์—†๋Š” ๊ฐ„์†Œํ™”๋œ ๋ฌธ์ž์—ด์ด๋ฏ€๋กœ ๊ตณ์ด ํ…์ŠคํŠธ ๋งคํฌ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋ฐ”๋กœ ๋„˜๊ฒจ์ค˜๋„ ๊ดœ์ฐฎ๋‹ค.

AAuraCharacterBase::AAuraCharacterBase()
{
	PrimaryActorTick.bCanEverTick = false;

	// FName์—๋Š” ํ…์ŠคํŠธ ๋งคํฌ๋กœ ์“ฐ์ง€ ์•Š์•„๋„ ๋จ , FString์— ์‚ฌ์šฉ 
	Weapon = CreateDefaultSubobject<USkeletalMeshComponent>("Weapon");
	Weapon->SetupAttachment(GetMesh(),"WeaponHandSocket");
}

์บ๋ฆญํ„ฐ ์™ธ๊ณฝ์„  ํšจ๊ณผ - Custom Depth Stencil Pass

  1. ํ”„๋กœ์ ํŠธ ์„ธํŒ… - ์—”์ง„ ๋ Œ๋”๋ง ์„ธํŒ… - Custom Depth Stencil Pass ๋ถ€๋ถ„ - Enable with Stencil๋กœ ์„ค์ •

  2. ํฌ์ŠคํŠธ ํ”„๋กœ์„ธ์Šค ๋ณผ๋ฅจ์— ๋จธํ‹ฐ๋ฆฌ์–ผ ์ถ”๊ฐ€ ํ•„์š”
    (๋จธํ‹ฐ๋ฆฌ์–ผ์€ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์ค˜์•ผ ํ•จ. ์ด ๊ฐ•์˜์—์„œ๋Š” ๊ฐ™์ด ์ œ๊ณตํ•ด์ฃผ๋Š” ์™ธ๊ณฝ์„  ๋จธํ‹ฐ๋ฆฌ์–ผ์„ ์‚ฌ์šฉํ–ˆ์Œ)

  3. ์•กํ„ฐ์—์„œ ๋ Œ๋” ์˜ต์…˜ ์ผœ์ฃผ๊ธฐ
    SetCustomDepthStencilValue(CUSTOM_DEPTH_RED) : ๋จธํ‹ฐ๋ฆฌ์–ผ์—์„œ ์‚ฌ์šฉํ•œ ์ปค์Šคํ…€ ๋ฐธ๋ฅ˜์™€ ์ผ์น˜์‹œ์ผœ์ฃผ๋ฉด ๋œ๋‹ค.
    SetRenderCustomDepth(true)

AAuraEnemy::AAuraEnemy()
{
	GetMesh()->SetCollisionResponseToChannel(ECC_Visibility,ECR_Block);
	// #define ๋งคํฌ๋กœ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ƒ์ˆ˜ ์‚ฌ์šฉ ์ตœ์†Œํ™”
	GetMesh()->SetCustomDepthStencilValue(CUSTOM_DEPTH_RED);
	if (Weapon)
	{
		Weapon->SetCustomDepthStencilValue(CUSTOM_DEPTH_RED);
	}
}

void AAuraEnemy::HighlightActor()
{
	GetMesh()->SetRenderCustomDepth(true);
	if (Weapon)
	{
		Weapon->SetRenderCustomDepth(true);
	}
}

LastActor์™€ ThisActor๋ฅผ ํ™œ์šฉํ•œ ์•กํ„ฐ ์„ ํƒ ๊ฒฝ์šฐ์˜ ์ˆ˜

/**
 	 * Line trace from cursor. There are several scenarios:
 	 *  A. LastActor is null && ThisActor is null
 	 *		- Do nothing
 	 *	B. LastActor is null && ThisActor is valid
 	 *		- Highlight ThisActor
 	 *	C. LastActor is valid && ThisActor is null
 	 *		- UnHighlight LastActor
 	 *	D. Both actors are valid, but LastActor != ThisActor
 	 *		- UnHighlight LastActor, and Highlight ThisActor
 	 *	E. Both actors are valid, and are the same actor
 	 *		- Do nothing
 	 */
 
 	if (LastActor == nullptr)
 	{
 		if (ThisActor != nullptr)
 		{
 			// Case B
 			ThisActor->HighlightActor();
 		}
 		else
 		{
 			// Case A - both are null, do nothing
 		}
 	}
 	else // LastActor is valid
 	{
 		if (ThisActor == nullptr)
 		{
 			// Case C
 			LastActor->UnHighlightActor();
 		}
 		else // both actors are valid
 		{
 			if (LastActor != ThisActor)
 			{
 				// Case D
 				LastActor->UnHighlightActor();
 				ThisActor->HighlightActor();
 			}
 			else
 			{
 				// Case E - do nothing
 			}
 		}
 	}

์ธํ„ฐํŽ˜์ด์Šค


๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ ธ์„ ๋•Œ ์›ํ•˜๋Š” ์•กํ„ฐ์— ๋‹ค์–‘ํ•˜๊ฒŒ ์œ ๋™์ ์œผ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

Unreal Interface์˜ ์ž์‹ ํด๋ž˜์Šค

UINTERFACE(MinimalAPI)
class UEnemyInterface : public UInterface
{
	GENERATED_BODY()
};
class AURA_API IEnemyInterface
{
	GENERATED_BODY()

public:
	// ์ˆœ์ˆ˜ ๊ฐ€์ƒ ํ•จ์ˆ˜
	virtual void HighlightActor() = 0;
	virtual void UnHighlightActor() = 0;
};

์ธํ„ฐํŽ˜์ด์Šค ์ƒ์† ๋ฐ›๊ธฐ

UCLASS()
class AURA_API AAuraEnemy : public AAuraCharacterBase, public  IEnemyInterface // ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์†
{
	GENERATED_BODY()

public:
	AAuraEnemy();
	
	virtual void HighlightActor() override;
	virtual void UnHighlightActor() override;
	
};

์ „๋ฐฉ์„ ์–ธ

ํฌ์ธํ„ฐ๋‚˜ ์ฐธ์กฐํ˜• ๋ฉค๋ฒ„์ธ ๊ฒฝ์šฐ ํ—ค๋”์— ํ•ด๋‹น ํด๋ž˜์Šค๋‚˜ ๊ตฌ์กฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค๋ฅธ ํ—ค๋”๋ฅผ include ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

  • BasePawn ํด๋ž˜์Šค ๋ฉค๋ฒ„์— ์บก์А ์ปดํฌ๋„ŒํŠธ ํฌ์ธํ„ฐ๊ฐ€ ๋“ค์–ด๊ฐ€๋Š” ์ƒํ™ฉ
  • ์ „๋ฐฉ ์„ ์–ธ ์—†์ด BasePawn.h ํŒŒ์ผ์— Capsule~.h๋ฅผ ์ธํด๋ฃจ๋“œํ•˜๋ฉด BasePawn ํ—ค๋”๋ฅผ ํฌํ•จํ•˜๋Š” ๋‹ค๋ฅธ ํด๋ž˜์Šค๋“ค์ด ๋‚˜์˜ฌ ๋•Œ๋งˆ๋‹ค ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ์บก์А ์ปดํฌ๋„ŒํŠธ ์ •๋ณด๊นŒ์ง€ ๊ฐ™์ด ํฌํ•จํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ ํŒŒ์ผ์ด ๋ฌด์˜๋ฏธํ•˜๊ฒŒ ์ปค์ง€๊ฒŒ ๋จ
  • ๋ฐ˜๋ฉด ์ „๋ฐฉ ์„ ์–ธ์„ ํ†ตํ•ด ํด๋ž˜์Šค ํฌ์ธํ„ฐ๋ผ๋Š” ๊ฒƒ๋งŒ ์•Œ๋ ค์ฃผ๊ณ  ์‹ค์ œ ๊ตฌํ˜„ ๋ถ€๋ถ„์ธ cpp ํŒŒ์ผ์— ์บก์А ์ปดํฌ๋„ŒํŠธ ํ—ค๋” ํŒŒ์ผ์„ ํฌํ•จํ•ด ์ปดํŒŒ์ผํ•œ๋‹ค๋ฉด ๋‚˜์ค‘์— BasePawn ํ—ค๋”๋ฅผ ํฌํ•จํ•˜๋”๋ผ๋„ ์บก์А ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋”ธ๋ ค๊ฐ€์ง€ ์•Š์•„ ํšจ์œจ์ ์ž„
    • ex) class UCapsuleComponent* CapsuleComp;

AuraPlayerController ์˜ˆ์‹œ

// ์ „๋ฐฉ์„ ์–ธ
class IEnemyInterface;
struct FInputActionValue; // Ftype ๊ตฌ์กฐ์ฒด๋Š” struct๋กœ ์ „๋ฐฉ์„ ์–ธ
class UInputAction;
class UInputMappingContext;


UCLASS()
class AURA_API AAuraPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	AAuraPlayerController();
	virtual void PlayerTick(float DeltaTime) override;

protected:
	virtual void BeginPlay() override;
	virtual void SetupInputComponent() override;
	

private:
	UPROPERTY(EditAnywhere, Category="Input")
	TObjectPtr<UInputMappingContext> AuraContext; // ์ „๋ฐฉ์„ ์–ธ
	UPROPERTY(EditAnywhere, Category="Input")
	TObjectPtr<UInputAction> MoveAction; // ์ „๋ฐฉ์„ ์–ธ
	
	void Move(const FInputActionValue& InputActionValue); // ์ „๋ฐฉ์„ ์–ธ
	void CursorTrace();
	TScriptInterface<IEnemyInterface> LastActor; // ์ „๋ฐฉ์„ ์–ธ
	TScriptInterface<IEnemyInterface> ThisActor; // ์ „๋ฐฉ์„ ์–ธ
};

์ปค๋ฐ‹

https://github.com/jongmin7759/Unreal_GAS/commit/78bec3f628f2db222f3c95159b420ca135ac8360

profile
์ •๋ฆฌ์™€ ๊ธฐ๋ก

0๊ฐœ์˜ ๋Œ“๊ธ€