본문 바로가기
Unreal 게임 개발/Unreal 강의 개인 정리

GAS 및 GA - GAS

by daisy0461 2025. 8. 18.

어빌리티 시스템 컴포넌트 (Ability System Component)

  • 줄여서 ASC라고 불린다.
  • 게임 어빌리티 시스템을 관리하는 핵심 컴포넌트이다.
  • 게임플레이 어빌리티 및 다양한 작업을 관리 및 처리하는 중앙 처리장치이다.
  • 액터에 단 하나만 부착가능하며 부착된 ASC를 통해 게임플레이 어빌리티를 발동시킬 수 있다.
  • ASC를 부착한 액터 사이에 GAS 시스템의 상호 작용이 가능하다.

 

게임플레이 어빌리티 (Gameplay Ability)

  • 줄여서 GA라고 불린다.
  • ASC에 등록되어 발동시킬 수 있는 액션 명령이다.
    - 공격, 마법, 특수 공격 등등 간단한 액션 및 복잡한 액션 수행 가능.

  • GA 발동과정
    - ASC에 어빌리티 등록 : ASC의 GiveAbility함수에 발동할 GA의 클래스 타입을 전달
      발동할 GA 타입 정보를 게임플레이 어빌리티 스펙(GameplayAbilitySpec)이라고 한다.
    - ASC에게 어빌리티를 발동하라고 명령 : ASC의 TryActivateAbility함수에 발동할 GA의 클래스 타입을 전달.
      ASC에 등록된 타입이면 GA인스턴스가 생성된다.
    - 발동된 GA에는 발동한 액터와 실행 정보가 기록된다.
      SpecHandle : 발동된 어빌리티에 대한 핸들
      ActorInfo : 어빌리티 소유자와 아바타 정보
      ActivationInfo : 발동 방식에 대한 정보

  • GA의 주요 함수
    - CanActivateAbility ; 어빌리티가 발동될 수 있는지 파악.
    - ActivateAbility : 어빌리티가 발동될 때 호출
    - CancelAbiltiy : 어빌리티가 취소될 때 호출
    - EndAbility : 스스로 어빌리티를 마무리할 때 호출

 

GAS 구현 방법

GAS를 Actor가 사용하게 하기 위해서는

#include "AbilitySystemInterface.h"

class PROJECTNAME_API ClassName : public ParentClass, public IAbilitySystemInterface

다음과 같이 헤더파일에 AbilitySystemInterface를 추가하고

public IAbilitySystemInterface를 상속한다고 표시하여 인터페이스를 구현하는 방법이 일반적인 구현 방법이다.

 

IAbilitySystemInterface는 다음과 같이 되어있다.

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Interface.h"
#include "AbilitySystemInterface.generated.h"

#define UE_API GAMEPLAYABILITIES_API

class UAbilitySystemComponent;

/** Interface for actors that expose access to an ability system component */
UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint))
class UAbilitySystemInterface : public UInterface
{
	GENERATED_UINTERFACE_BODY()
};

class IAbilitySystemInterface
{
	GENERATED_IINTERFACE_BODY()

	/** Returns the ability system component to use for this actor. It may live on another actor, such as a Pawn using the PlayerState's component */
	virtual UAbilitySystemComponent* GetAbilitySystemComponent() const = 0;
};

#undef UE_API

결국 AbilitySystemComponent를 반환해주는 GetAbilitySystemComponent() 함수만 구현해주면 된다.

 

	UPROPERTY(EditAnywhere, Category = "GAS")
	TObjectPtr<UAbilitySystemComponent> ASC;

 

반환하기 위해선 UAbilitySystemComponent가 있어야하기에 헤더파일에 추가한다.

 

cpp에서는

#include "AbilitySystemComponent.h"

UAbilitySystemComponent* ClassName::GetAbilitySystemComponent() const
{
	return ASC;
}

헤당 헤더를 추가해주고 다음과 같이 UAbilitySystemComponent를 return하면 된다.

당연히 생성자에서 CreateDefaultSubobject로 생성해줘야한다.

 

GAS 초기화

void AABGASFountain::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	//시작 시 동작하지 않도록 자동활성화 및 비활성화
	RotatingMovement->bAutoActivate = false;
	RotatingMovement->Deactivate();

	ASC->InitAbilityActorInfo(this, this);
}

ASC->InitAbilityActorInfo(Owner, Avatar) 이 함수를 꼭 지정해줘야한다.

Owner : ASC를 구동하고 데이터를 관리하는 실제 작업이 일어나는 Actor의 정보

Avatar Actor : 비주얼만 수행해주는 Actor

위처럼 (this, this)는 해당 Actor가 데이터도 관리하고 실제 동작도 관리하는 형태로 초기화한다는 의미이다.

 

GA 생성

해당 Class를 상속받아서 생성하면 된다.

 

UCLASS(Blueprintable, MinimalAPI)
class UGameplayAbility : public UObject, public IGameplayTaskOwnerInterface
{
	GENERATED_UCLASS_BODY()
	REPLICATED_BASE_CLASS(UGameplayAbility)

	friend class UAbilitySystemComponent;
	friend class UGameplayAbilitySet;
	friend struct FScopedTargetListLock;

public:

	// ----------------------------------------------------------------------------------------------------------------
	//
	//	The important functions:
	//	
	//		CanActivateAbility()	- const function to see if ability is activatable. Callable by UI etc
	//
	//		TryActivateAbility()	- Attempts to activate the ability. Calls CanActivateAbility(). Input events can call this directly.
	//								- Also handles instancing-per-execution logic and replication/prediction calls.
	//		
	//		CallActivateAbility()	- Protected, non virtual function. Does some boilerplate 'pre activate' stuff, then calls ActivateAbility()
	//
	//		ActivateAbility()		- What the abilities *does*. This is what child classes want to override.
	//	
	//		CommitAbility()			- Commits reources/cooldowns etc. ActivateAbility() must call this!
	//		
	//		CancelAbility()			- Interrupts the ability (from an outside source).
	//
	//		EndAbility()			- The ability has ended. This is intended to be called by the ability to end itself.
	//	
	// ----------------------------------------------------------------------------------------------------------------

UGameplayAbility에 가보면 다음과 같이 주요 함수에 대한 정보들이 있다.

주요 함수는 순수 가상함수는 아니기에 꼭 재정의해줄 필요는 없다.

검색할 때 위 Important fuctions처럼 ()를 다 복사해서 하면 잘 검색되지 않는다.

 

/** Actually activate ability, do not call this directly */
UE_API virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData);

/** Destroys instanced-per-execution abilities. Instance-per-actor abilities should 'reset'. Any active ability state tasks receive the 'OnAbilityStateInterrupted' event. Non instance abilities - what can we do? */
UE_API virtual void CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility);

다음과 같이 인자가 많아서 ()보단 (하나로 검색하는게 더 찾기가 쉽다.

 

virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;

ActivateAbility의 파라미터들을 살펴보자.

FGameplayAbilitySpecHandle Handle : 현재 발동한 어빌리티를 직접 처리할 수 있는 Handle 정보

FGameplayAbilityActorInfo* ActorInfo : 설정한 OwnerInfo와 AvatarInfo 등 다양한 정보를 담은 구조체

FGameplayAbilityActivationInfo ActivationInfo : 어떻게 발동했는지에 대한 정보를 담은 구조체

FGameplayEventData* TriggerEventData : 외부의 조건에 의해서 발동하면 외부에서 어떻게 발생시켰는지 정보에 대한 구조체

이후 해당 인자들과 관련해서 더 자세하게 다룬다.

 

이제 생성한 Ability를 발동하면 ActiavateAbility함수가 호출된다.

// Fill out your copyright notice in the Description page of Project Settings.


#include "GA/ABGA_Rotate.h"
#include "GameFramework/RotatingMovementComponent.h"

void UABGA_Rotate::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

	//ActorInfo에서 AvatarActor를 들고온다.
	AActor* AvatarActor = ActorInfo->AvatarActor.Get();
	if (AvatarActor)
	{
		//AvatorActor에서 Component 정보를 런타임에 들고온다.
		URotatingMovementComponent* RotatingMovement = Cast<URotatingMovementComponent>(AvatarActor->GetComponentByClass(URotatingMovementComponent::StaticClass()));
		if (RotatingMovement)
		{
			RotatingMovement->Activate(true);
		}
	}
}


void UABGA_Rotate::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility)
{
	Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility);
	AActor* AvatarActor = ActorInfo->AvatarActor.Get();
	if (AvatarActor)
	{
		//AvatorActor에서 Component 정보를 런타임에 들고온다.
		URotatingMovementComponent* RotatingMovement = Cast<URotatingMovementComponent>(AvatarActor->GetComponentByClass(URotatingMovementComponent::StaticClass()));
		if (RotatingMovement)
		{
			RotatingMovement->Deactivate();
		}
	}
}

코드는 다음과 같이 ActivateAbility에 실행할 동작과 CancelAbility에 실행할 동작을 작성해주면 된다.

 

이제 Actor에서 GAS에 위에서 작성한 Ability를 GiveAbility()를 통해 부여해줘야한다.

인자를 보면 Class를 넘기는 것이 아니라 FGameplayAbilitySpec이라는 GAS에서 제공하는 Ability에 대한 정보를 넘겨준다.

 

#include "GameplayAbilitySpec.h"
#include "GA/ABGA_Rotate.h"

void AABGASFountain::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	//시작 시 동작하지 않도록 자동활성화 및 비활성화
	RotatingMovement->bAutoActivate = false;
	RotatingMovement->Deactivate();

	ASC->InitAbilityActorInfo(this, this);
	FGameplayAbilitySpec RotateSkillSpec(UABGA_Rotate::StaticClass());
	ASC->GiveAbility(RotateSkillSpec);
}

GameplayAbilitySpec에 관한 헤더와 사용할 Ability에 대한 헤더를 추가하고

FGameplayAbilitySpec RotateSkillSpec(UABGA_Rotate::StaticClass());로 Class 정보를 넣어 Spec을 만들고

이 FGameplayAbilitySpec을 GiveAbility에 넣어주면 

ASC는 해당 타입에 대한 Ability를 발동한 준비를 마친다.

최초부터 인스턴스를 생성해서 갖게되면 부하가  수 있기에 발동할 기본 정보만 보유한다.

 

void AABGASFountain::BeginPlay()
{
	Super::BeginPlay();

	GetWorld()->GetTimerManager().SetTimer(ActionTimer, this, &ThisClass::TimerAction, ActionPerid, true, 0.0f);
}

void AABGASFountain::TimerAction()
{
	GAS_LOG(LogABGAS, Log, TEXT("Begin"));

	//ASC에서 스펙을 클래스 정보를 통해서 검색하여 가져온다.
	FGameplayAbilitySpec* RotateGASpec = ASC->FindAbilitySpecFromClass(UABGA_Rotate::StaticClass());
	if (!RotateGASpec) return;

	//현재 활성화가 안되어있다면
	if (!RotateGASpec->IsActive())
	{
		//TryActivateAbility에 실제 구동을 위해서 Spec의 Handle을 넣는다.
		ASC->TryActivateAbility(RotateGASpec->Handle);
	}
	else
	{
		//위와 동일하게 Handle을 넘겨줘서 취소하면 된다.
		ASC->CancelAbilityHandle(RotateGASpec->Handle);
	}
}

위의 코드와 같이 BeginPlay에서 3초마다 TimerAction을 실행해주고

FindAbilitySpecFromClass로 클래스 정보를 통해 가져온 뒤
TryActivateAbility로 실행, CancelAbilityHandle로 취소, IsActive()로 활성화 체크를 하면서 Ability를 실행하고 멈춘다.