ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [UE5] GAS(GamePlay Ability System)의 시작
    UE5 2024. 12. 30. 14:40

    게임플레이 어빌리티 시스템(GAS) 프레임 워크

    • 액터가 소유하고 발동할 수 있는 어빌리티 및 액터 간의 인터랙션 기능을 제공하는 프레임워크
    • RPG, 액션 어드벤처, MOBA 장르의 제작을 쉽게하기 위한 도구. 대부분의 게임 제작에 활용 가능
    • GAS 프레임워크의 장점
      • 유연성과 확장성 : 다양하고 복잡한 게임 제작에 대응할 수 있도록 범용적으로 설계
      • 모듈러 시스템 : 각 기능에 대한 의존성이 최소화되도록 설계
      • 네트워크 지원 : 네트워크 멀티플레이어 게임에서도 활용 가능하도록 설계
      • 데이터 기반 설계 : 데이터를 기반으로 동작하도록 설계
      • 완성도 : 포트나이트 게임 서비스를 통해 실효성 검증
    • GAS 프레임워크의 단점
      • 배우는 학습 비용 : 구성 요소가 많아서 학습하는 비용이 꽤 큼
      • 오버헤드 : 작은 규모의 프로젝트에는 복잡한 구조가 오히려 부담될 수 있음
    큰 규모의 RPG 및 네트워크 멀티플레이 겡미을 효율적으로 만드는데 적합함

     

    어빌리티 시스템 컴포넌트

    • 줄여서 ASC(Ability System Component)로 불림
    • 게임플레이 어빌리티 시스템을 관리하는 핵심 컴포넌트
    • 게임플레이 어빌리티 및 다양한 작업을 관리 및 처리하는 중앙 처리 장치
    • 액터에 단 하나만 부착할 수 있음
    • 액터는 부착된 ASC를 통해 게임플레이 어빌리티를 발동시킬 수 있음
    • ASC를 부착한 액터 사이에 GAS 시스템의 상호 작요이 가능해짐

    게임플레이 어빌리티

    • 줄여서 GA(Gameplay Ability)로 불림
    • ASC에 등록되어 발동시킬 수 있는 액션 명령
      • 공격, 마법, 특수 공격 등등
      • 간단한 액션 뿐만 아니라 상황에 따른 복잡한 액션 수행 가능
    • GA의 발동 과정
      • ASC에 어빌리티를 등록 : ASC의 GiveAbility 함수에 발동할 GA의 타입을 전달
        • 발동할 GA 타입 정보를 게임플레이 어빌리티 스펙(GameplayAbilitySpec)이라고 함
      • ASC에게 어빌리티를 발동하라고 명령 : ASC의 TryActivateAbility함수에 발동할 GA의 타입을 전달
        • ASC에 등록된 타입이면 GA의 인스턴스가 생성됨
      • 발동된 GA에는 발동한 액터와 실행 정보가 기록됨
        • SpecHandle : 발동된 어빌맅에 대한 핸들
        • ActorInfo : 어빌리티의 소유자와 아바타 정보
        • ActivationInfo : 발동 방식에 대한 정보
    • GA의 주요 함수
      • CanActivateAbility : 어빌리티가 발동될 수 있는지 파악
      • ActivateAbility : 어빌리티가 발동될 때 호출
      • CancelAbility : 어빌리티가 취소될 때 호출
      • EndAbility : 스스로 어빌리티를 마무리할 때 호출

     

    게임플레이 태그

    • FName으로 관리되는 경량의 표식 데이터
      • 액터나 컴포넌트에 지정했던 태그와 다른 데이터
    • 프로젝트 설정에서 별도로 게임플레이 태그를 생성하고 관리할 수 있음.
      • 결과는 DefaultGameplayTags.ini 파일에 저장됨
    • 계층 구조로 구성되어 있어 체계적인 관리 가능
      • Actor.Action.Rotate : 행동에 대한 태크
      • Actor.State.IsRotating : 상태에 대한 태그
    • 게임플레이 태그들의 저장소 : GameplayTagContainer
      • 계층 구조를 지원하는 검색 기능 제공
      • HasTagExact : 컨테이너에 A.1태그가 있는 상황에서 A로 찾으면 false
      • HasAny : 컨테이너에 A.1태그가 있는 상황에서 A와 B로 찾으면 true
      • HasAnyExact : 컨테이너에 A.1태그가 있는 상황에서 A와 B로 찾으면 false
      • HasAll : 컨테이너에 A.1태그와 B.1태그가 있는 상황에서 A와 B로 찾으면 true
      • HasAllExact : 컨테이너에 A.1태그와 B.1태그가 있는 상황에서 A와 B로 찾으면 false
    • 게임플레이 어빌리티 시스템과 독립적으로 사용 가능

     

    게임플레이 어빌리티와 게임플레이 태그

    • 게임플레이 어빌리티에 부착한 태그
      • 어빌리티에 지정한 태그 ( AbilityTags 태그 컨테이너 )
    • 게임플레이 어빌리티에 대해 다양한 실행 조건의 설정
      • 태그로 어빌리티 취소 ( CancelAbilitiesWithTag 태그 컨테이너 )
      • 태그로 어빌리티 차단 ( BlockAbilitiesWithTag 태그 컨테이너 )
      • 어빌리티 실행시 태그 설정 ( ActivationOwnedTags 태그 컨테이너 )
      • 태그가 있어야만 어빌리티 실행 ( ActivationRequiredTags 태그 컨테이너 )
      • 태그가 있으면 어빌리티 실행 차단 ( ActivationBlockedTags 태그 컨테이너 )
      • 시전자가 태그가 있어야 어빌리티 실행 ( SourceRequiredTags 태그 컨테이너 )
      • 시전자에 태그가 있으면 어빌리티 차단 ( SourceBlockedTags 태그 컨테이너 )
      • 시전 대상에 태그가 있어야 어빌리티 실행 ( TargetRequiredTags 태그 컨테이너 )
      • 시전 대상에 태그가 있으면 어빌리티 차단 ( TargetBlockedTags 태그 컨테이너 )
    블루프린트와 조합하여 특정 게임플레이 어빌리티에 대한 의존성을 없애고
    게임플레이 태그를 중심으로 게임 로직을 전개 가능

     

    분수대 액터의 기획

    • 3초마다 회전과 정지를 무한 반복하면서 동작하는 분수대
    • 회전기능은 RotatingMovement 컴포넌트를 사용함

    구현 방법

     

    1. 액터에 해당 기능을 구현

    #include "Prop/ABGASFountain.h"
    #include "GameFramework/RotatingMovementComponent.h"
    #include "ArenaBattleGAS.h"
    
    AABGASFountain::AABGASFountain()
    {
    	RotatingMovement = CreateDefaultSubobject<URotatingMovementComponent>(TEXT("RotateMovement"));
    	ActionPeriod = 3.0f;
    }
    
    void AABGASFountain::PostInitializeComponents()
    {
    	Super::PostInitializeComponents();
    
    	RotatingMovement->bAutoActivate = false;
    	RotatingMovement->Deactivate();
    }
    
    void AABGASFountain::BeginPlay()
    {
    	Super::BeginPlay();
    
    	GetWorld()->GetTimerManager().SetTimer(ActionTimer, this, &AABGASFountain::TimerAction, ActionPeriod, true, 0.0f);
    }
    
    void AABGASFountain::TimerAction()
    {
    	ABGAS_LOG(LogABGAS, Log, TEXT("Begin"));
    
    	if (!RotatingMovement->IsActive())
    	{
    		RotatingMovement->Activate(true);
    	}
    	else
    	{
    		RotatingMovement->Deactivate();
    	}
    }

     

     

    2. 게임플레이 어빌리티 시스템으로 구현

    #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);
    
    	// 아바타 액터가 보여지는 주체라서 가져오는 것이 일반적
    	AActor* AvatarActor = ActorInfo->AvatarActor.Get();
    	if (AvatarActor)
    	{
    		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)
    	{
    		URotatingMovementComponent* RotatingMovement = Cast<URotatingMovementComponent>(AvatarActor->GetComponentByClass(URotatingMovementComponent::StaticClass()));
    		if (RotatingMovement)
    		{
    			RotatingMovement->Deactivate();
    		}
    	}
    }
    #include "Prop/ABGASFountain.h"
    #include "GameFramework/RotatingMovementComponent.h"
    #include "ArenaBattleGAS.h"
    #include "AbilitySystemComponent.h"
    #include "GameplayAbilitySpec.h"
    #include "GA/ABGA_Rotate.h"
    
    AABGASFountain::AABGASFountain()
    {
    	ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
    	RotatingMovement = CreateDefaultSubobject<URotatingMovementComponent>(TEXT("RotateMovement"));
    	ActionPeriod = 3.0f;
    }
    
    UAbilitySystemComponent* AABGASFountain::GetAbilitySystemComponent() const
    {
    	return ASC;
    }
    
    void AABGASFountain::PostInitializeComponents()
    {
    	Super::PostInitializeComponents();
    
    	RotatingMovement->bAutoActivate = false;
    	RotatingMovement->Deactivate();
    
    	// 오너 정보, 아바타 정보
    	// 오너 정보 : 실제 ASC를 구동하고 데이터를 관리하는 실제 작업이 일어나고 있는 액터 정보
    	// 아바타 정보 : 실제로 데이터를 처리하지 않지만 비주얼만 수행하는 액터를 지정
    	// 아바타와 오너가 같으면 현재 이 분수대의 경우에는 데이터도 관리하고 실제 동작도 관리하는 형태로 작업을 초기화한다는 것
    	ASC->InitAbilityActorInfo(this, this);
    	FGameplayAbilitySpec RotateSkillSpec(UABGA_Rotate::StaticClass());
    	ASC->GiveAbility(RotateSkillSpec);
    }
    
    void AABGASFountain::BeginPlay()
    {
    	Super::BeginPlay();
    
    	GetWorld()->GetTimerManager().SetTimer(ActionTimer, this, &AABGASFountain::TimerAction, ActionPeriod, true, 0.0f);
    }
    
    void AABGASFountain::TimerAction()
    {
    	ABGAS_LOG(LogABGAS, Log, TEXT("Begin"));
    
    	FGameplayAbilitySpec* RotateGASpec = ASC->FindAbilitySpecFromClass(UABGA_Rotate::StaticClass());
    	if (!RotateGASpec)
    	{
    		ABGAS_LOG(LogABGAS, Error, TEXT("No Rotate Spec Found!"));
    		return;
    	}
    
    	if (!RotateGASpec->IsActive())
    	{
    		ASC->TryActivateAbility(RotateGASpec->Handle);
    	}
    	else
    	{
    		ASC->CancelAbilityHandle(RotateGASpec->Handle);
    	}
    }

     

    3. 게임플레이 어빌리티 시스템에 게임플레이 태그를 부여해 구현

    #pragma once
    
    #include "GameplayTagContainer.h"
    
    #define ABTAG_ACTOR_ROTATE FGameplayTag::RequestGameplayTag(FName("Actor.Action.Rotate"))
    #define ABTAG_ACTOR_ISROTATING FGameplayTag::RequestGameplayTag(FName("Actor.State.IsRotating"))
    #include "Prop/ABGASFountain.h"
    #include "GameFramework/RotatingMovementComponent.h"
    #include "ArenaBattleGAS.h"
    #include "AbilitySystemComponent.h"
    #include "GameplayAbilitySpec.h"
    #include "Tag/ABGameplayTag.h"
    #include "Abilities/GameplayAbility.h"
    
    AABGASFountain::AABGASFountain()
    {
    	ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
    	RotatingMovement = CreateDefaultSubobject<URotatingMovementComponent>(TEXT("RotateMovement"));
    	ActionPeriod = 3.0f;
    }
    
    UAbilitySystemComponent* AABGASFountain::GetAbilitySystemComponent() const
    {
    	return ASC;
    }
    
    void AABGASFountain::PostInitializeComponents()
    {
    	Super::PostInitializeComponents();
    
    	RotatingMovement->bAutoActivate = false;
    	RotatingMovement->Deactivate();
    
    	// 오너 정보, 아바타 정보
    	// 오너 정보 : 실제 ASC를 구동하고 데이터를 관리하는 실제 작업이 일어나고 있는 액터 정보
    	// 아바타 정보 : 실제로 데이터를 처리하지 않지만 비주얼만 수행하는 액터를 지정
    	// 아바타와 오너가 같으면 현재 이 분수대의 경우에는 데이터도 관리하고 실제 동작도 관리하는 형태로 작업을 초기화한다는 것
    	ASC->InitAbilityActorInfo(this, this);
    
    	for (const auto& StartAbility : StartAbilities)
    	{
    		FGameplayAbilitySpec StartSpec(StartAbility);
    		ASC->GiveAbility(StartSpec);
    	}
    }
    
    void AABGASFountain::BeginPlay()
    {
    	Super::BeginPlay();
    
    	GetWorld()->GetTimerManager().SetTimer(ActionTimer, this, &AABGASFountain::TimerAction, ActionPeriod, true, 0.0f);
    }
    
    void AABGASFountain::TimerAction()
    {
    	ABGAS_LOG(LogABGAS, Log, TEXT("Begin"));
    	
    	FGameplayTagContainer TargetTag(ABTAG_ACTOR_ROTATE);
    
    	if (!ASC->HasMatchingGameplayTag(ABTAG_ACTOR_ISROTATING))
    	{
    		ASC->TryActivateAbilitiesByTag(TargetTag);
    	}
    	else
    	{
    		ASC->CancelAbilities(&TargetTag);
    	}
    }

     

     

    결론적으로,

    • 액터를 확장해 구현 : 익숙한 방법으로 빠르게 구현. 새로운 액터 기능이 추가됨
    • GAS를 사용해 구현 : C++만 사용해 모든 기능 구현 가능. 액터로부터 기능 분리
    • 게임플레이 태그를 추가 활용해 구현 : 의존성 없는 설계가 가능. 어빌리티 교체 가능

    액터의 역할을 최소화시키는데 주력하고, 게임플레이 태그를 활용해 의존성을 분리할 수 있다!

Designed by Tistory.