-
[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 : 발동 방식에 대한 정보
- ASC에 어빌리티를 등록 : ASC의 GiveAbility 함수에 발동할 GA의 타입을 전달
- 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++만 사용해 모든 기능 구현 가능. 액터로부터 기능 분리
- 게임플레이 태그를 추가 활용해 구현 : 의존성 없는 설계가 가능. 어빌리티 교체 가능
액터의 역할을 최소화시키는데 주력하고, 게임플레이 태그를 활용해 의존성을 분리할 수 있다!
'UE5' 카테고리의 다른 글
[UE5] GAS를 활용한 캐릭터 공격 (0) 2025.01.03 [UE5] GAS를 활용한 캐릭터의 입력 처리 (0) 2025.01.02 [UE5] 네트워크 멀티플레이어 최적화 (0) 2024.12.26 [UE5] 움직임 리플리케이션 (1) 2024.12.20 [UE5] 전역 네임스페이스와 ::의 활용 (0) 2024.12.19