728x90
반응형
- 정리
- 언리얼 게임플레이 어빌리티 시스템의 시작
- 회전하는 분수대 기획을 세 가지 방법으로 구현
- 액터를 확장해 구현 : 익숙한 방법으로 빠르게 구현. 새로운 액터 기능이 추가됨 (액터의 규모가 커지면서 굉장히 관리하기 어려워질 수 있다.)
- GAS를 사용해 구현 : C++만 사용해 모든 기능 구현 가능. 액터로부터 기능 분리
- 게임플레이 태그를 추가 활용해 구현 : 의존성 없는 설계가 가능. 어빌리티 교체 가능
- 액터의 역할을 최소화시키는데 주력
- 게임플레이 태그를 활용해 의존성을 분리
- C++ 프로그래밍 없이도 게임플레이 태그 규약만 잘 지키면 시스템을 유연하게 확장할 수 있음
- 회전하는 분수대 기획을 세 가지 방법으로 구현
- 언리얼 게임플레이 어빌리티 시스템의 시작
- 게임플레이 어빌리티 시스템의 기본 익히기
- 분수대 액터의 기획
- 3초마다 회전과 정지를 무한 반복하면서 동작하는 분수대
- 회전기능은 RotatingMovement 컴포넌트를 사용함
- 이를 어떻게 구현?
- 액터에 해당 기능을 구현
- 게임플레이 어빌리티 시스템으로 구현
- 게임플레이 어빌리티 시스템에 게임플레이 태그를 부여해 구현
- 3가지 구현 방법의 장단점 비교
- GAS 시스템을 적용할 액터에다가 ASC를 부착한 후에 ASC로 하여금 지정한 게임플레이 어빌리티를 발동시키도록 지정할 예정
- 게임플레이 태그를 활용해 보다 효과적으로 해당 기능들을 구현
- 어빌리티 시스템 컴포넌트
- 줄여서 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 : 스스로 어빌리티를 마무리할 때 호출
// ----------------------------------------------------------------------------------------------------------------
//
// 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.
//
// ----------------------------------------------------------------------------------------------------------------
- GameplayAbility.h
- GA의 주요 함수들
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "ABGA_Rotate.generated.h"
UCLASS()
class ARENABATTLEGAS_API UABGA_Rotate : public UGameplayAbility
{
GENERATED_BODY()
public:
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
virtual void CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility) override;
};
- ABGA_Rotate.h
#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);
// AvatarActor - 주로 보여지는 액터!
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);
// AvatarActor - 주로 보여지는 액터!
AActor* AvatarActor = ActorInfo->AvatarActor.Get();
if (AvatarActor)
{
// 컴포넌트 가져오기
URotatingMovementComponent* RotatingMovement = Cast<URotatingMovementComponent>(AvatarActor->GetComponentByClass(URotatingMovementComponent::StaticClass()));
if (RotatingMovement)
{
RotatingMovement->Deactivate();
}
}
}
- ABGA_Rotate.cpp
- GameplayAbility를 상속받은 클래스를 생성해주고, 주요 함수중 ActivateAbility와 CancelAbility를 override하여 구현해준다.
#pragma once
#include "CoreMinimal.h"
#include "Prop/ABFountain.h"
#include "AbilitySystemInterface.h" // GAS를 추가하기 위해서 이것을 소유하고 있는 액터는 AbilitySystemInterface라고 하는 인터페이스를 구현해주는 것이 좋다.
#include "ABGASFountain.generated.h"
UCLASS()
class ARENABATTLEGAS_API AABGASFountain : public AABFountain, public IAbilitySystemInterface
{
GENERATED_BODY()
public :
AABGASFountain();
// IAbilitySystemInterface 구현부
virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
protected:
virtual void PostInitializeComponents() override;
virtual void BeginPlay() override;
// 주기적으로 반복할 타이머 함수
virtual void TimerAction();
protected:
UPROPERTY(VisibleAnywhere, Category = Movement)
TObjectPtr<class URotatingMovementComponent> RotatingMovement;
UPROPERTY(EditAnywhere, Category = Timer)
float ActionPeriod;
// 실제 액터가 ASC를 가지고 있어야 함
UPROPERTY(EditAnywhere, Category = GAS)
TObjectPtr<class UAbilitySystemComponent> ASC;
FTimerHandle ActionTimer;
};
- ABGASFountain.h
#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도 같이 초기화해줘야 함
// 인자 - InOwnerActor, InAvatarActor 정보 두가지가 들어감.
// InOwnerActor - 실제 ASC를 구동하고 데이터를 관리하는 실제 작업이 일어나고 있는 액터 정보를 의미
// InAvatarActor - 실제로 데이터를 처리하지 않지만 비주얼만 수행해주는 액터를 지정
// InOwnerActor == InAvatarActor의 경우 데이터도 관리, 실제 동작도 관리하는 형태로 작업을 초기화한다라는 의미
// ASC 초기화
ASC->InitAbilityActorInfo(this, this);
//ASC에 GameplayAbilitySpec 넘겨주기
FGameplayAbilitySpec RotateSkillSpec(UABGA_Rotate::StaticClass());
ASC->GiveAbility(RotateSkillSpec);
}
void AABGASFountain::BeginPlay()
{
Super::BeginPlay();
// SetTimer, 핸들은 ActionTimer, 클래스의 TimerAction을 바인드, 루프 주기는 ActionPeriod, 반복유무 true, 바로 시작하도록 0초
GetWorld()->GetTimerManager().SetTimer(ActionTimer, this, &AABGASFountain::TimerAction, ActionPeriod, true, 0.0f);
}
void AABGASFountain::TimerAction()
{
ABGAS_LOG(LogABGAS, Log, TEXT("Begin"));
#pragma region GA 사용
FGameplayAbilitySpec* RotateGASpec = ASC->FindAbilitySpecFromClass(UABGA_Rotate::StaticClass());
if (!RotateGASpec)
{
ABGAS_LOG(LogABGAS, Error, TEXT("No Rotate Spec Found!"));
return;
}
// FGameplayAbilitySpec이 활성화 되있는지 확인하고, 활성화 안되어 있으면 Active 되어 있으면 Cancel
if (!RotateGASpec->IsActive())
{
ASC->TryActivateAbility(RotateGASpec->Handle);
}
else
{
ASC->CancelAbilityHandle(RotateGASpec->Handle);
}
#pragma endregion
}
- ABGASFountain.cpp
- 동일한 기능을 GA를 통해 구현.
- IAbilitySystemInterface를 상속받아 구현부(GetAbilitySystemComponent)를 작성해주고, 실제 액터가 ASC를 가지고 있어야하기 때문에 UAbilitySystemComponent의 ASC를 선언해주고, PostInitializeComponents에서 초기화 해주고 GameplayAbilitySpec을 GiveAbility를 통해 넘겨준다.
- 게임플레이 태그
- FName으로 관리되는 경량의 표식 데이터
- 액터나 컴포넌트에 지정했던 태그와 다른 데이터
- 프로젝트 설정에서 별도로 게임플레이 태그를 생성하고 관리할 수 있음
- 결과는 DefaultGameplayTags.ini 파일에 저장됨
- 계층 구조로 구성되어 있어 체계적인 관리 가능
- Actor.Action.Rotate : 행동에 대한 태그
- Actor.State.IsRotating : 상태에 대한 태그
- 게임플레이 태그들의 저장소 : GameplayTagContainer
- 계층 구조를 지원하는 검색 기능 제공
- HasTagExact : 컨테이너에 A.1태그가 있는 상황에서 A와 B로 찾으면 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
- 게임플레이 어빌리티 시스템과 독립적으로 사용 가능
- FName으로 관리되는 경량의 표식 데이터
- 게임플레이 어빌리티와 게임플레이 태그
- 게임플레이 어빌리티에 부착한 태그
- 어빌리티에 지정한 태그 (AbilityTags 태그 컨테이너)
- 게임플레이 어빌리티에 대해 다양한 실행 조건의 설정
- 태그로 어빌리티 취소 (CancelAbilitiesWithTag 태그 컨테이너)
- 태그로 어빌리티 차단 (BlockAbilitesWithTag 태그 컨테이너)
- 어빌리티 실행시 태그 설정 (ActivationOwnedTags 태그 컨테이너)
- 태그가 있어야만 어빌리티 실행 (ActivationRequiredTags 태그 컨테이너)
- 태그가 있으면 어빌리티 실행 차단 (ActivationBlockedTags 태그 컨테이너)
- 시전자가 태그가 있어야 어빌리티 실행 (SourceRequiredTags 태그 컨테이너)
- 시전자에 태그가 있으면 어빌리티 차단 (SourceBlockedTags 태그 컨테이너)
- 시전 대상에 태그가 있어야 어빌리티 실행 (TargetRequiredTags 태그 컨테이너)
- 시전 대상에 태그가 있으면 어빌리티 차단 (TargetBlockedTags 트개 컨테이너)
- 게임플레이 어빌리티에 부착한 태그
- 블루프린트와 조합하여 특정 게임 플레이 어빌리티에 대한 의존성을 없애고 게임플레이 태그를 중심으로 게임 로직을 전개 가능
[/Script/GameplayTags.GameplayTagsSettings]
ImportTagsFromConfig=True
WarnOnInvalidTags=True
ClearInvalidTags=False
AllowEditorTagUnloading=True
AllowGameTagUnloading=False
FastReplication=False
InvalidTagCharacters="\"\',"
NumBitsForContainerSize=6
NetIndexFirstBitSegment=16
+GameplayTagList=(Tag="Actor.Action.Rotate",DevComment="")
+GameplayTagList=(Tag="Actor.State.IsRotating",DevComment="")
- DefaultGameplayTags.ini
- 게임플레이 태그를 생성하고, Refesh visual studio를 하면 DefaultGameplayTags.ini에 생성한 태그가 나오는걸 확인할 수 있다.
#pragma once
#include "GameplayTagContainer.h" // GameplayTag를 활용하기 위한
// 실제 태그 넣기
// 명령 하나가 하나의 변수처럼 사용됨
#define ABTAG_ACTOR_ROTATE FGameplayTag::RequestGameplayTag(FName("Actor.Action.Rotate"))
#define ABTAG_ACTOR_ISROTATING FGameplayTag::RequestGameplayTag(FName("Actor.State.IsRotating"))
- ABGameplayTag.h
- txt파일을 만들어 다음과 같이 파일 내용을 채워준다.
#pragma once
#include "CoreMinimal.h"
#include "Prop/ABFountain.h"
#include "AbilitySystemInterface.h" // GAS를 추가하기 위해서 이것을 소유하고 있는 액터는 AbilitySystemInterface라고 하는 인터페이스를 구현해주는 것이 좋다.
#include "ABGASFountain.generated.h"
UCLASS()
class ARENABATTLEGAS_API AABGASFountain : public AABFountain, public IAbilitySystemInterface
{
GENERATED_BODY()
// ...
UPROPERTY(EditAnywhere, Category = GAS)
TArray<TSubclassOf<class UGameplayAbility>> StartAbilities;
FTimerHandle ActionTimer;
};
- ABGASFountain.h
#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"
//#include "GA/ABGA_Rotate.h"
// ...
void AABGASFountain::PostInitializeComponents()
{
Super::PostInitializeComponents();
RotatingMovement->bAutoActivate = false;
RotatingMovement->Deactivate();
// 초기화할때 ASC도 같이 초기화해줘야 함
// 인자 - InOwnerActor, InAvatarActor 정보 두가지가 들어감.
// InOwnerActor - 실제 ASC를 구동하고 데이터를 관리하는 실제 작업이 일어나고 있는 액터 정보를 의미
// InAvatarActor - 실제로 데이터를 처리하지 않지만 비주얼만 수행해주는 액터를 지정
// InOwnerActor == InAvatarActor의 경우 데이터도 관리, 실제 동작도 관리하는 형태로 작업을 초기화한다라는 의미
// ASC 초기화
ASC->InitAbilityActorInfo(this, this);
for (const auto& StartAbility : StartAbilities)
{
// ASC에 GameplayAbilitySpec 넘겨주기
FGameplayAbilitySpec StartSpec(StartAbility);
ASC->GiveAbility(StartSpec);
}
}
void AABGASFountain::BeginPlay()
{
Super::BeginPlay();
// SetTimer, 핸들은 ActionTimer, 클래스의 TimerAction을 바인드, 루프 주기는 ActionPeriod, 반복유무 true, 바로 시작하도록 0초
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);
}
}
- ABGASFountain.cpp
- TArray를 통해 GameplayAbility를 담아주고, PostInitializeComponents에서 GameplayAbilitySpec을 넘겨주도록 한다.
- TimerAction에서 게임플레이 태그를 사용하여 동일한 동작을 하도록 구현해준다.
해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템>
강의를 수강하고 정리한 내용입니다.
이득우의 언리얼 프로그래밍 Part4 - 게임플레이 어빌리티 시스템 강의 | 이득우 - 인프런
이득우 | 언리얼 엔진5의 게임플레이 어빌리티 시스템을 활용해 RPG게임의 각종 스킬 시스템을 효과적으로 구현하는 방법을 학습합니다., 게임 제작의 혁신, 언리얼 게임플레이 어빌리티 시스템
www.inflearn.com
728x90
'공부 > 이득우의 언리얼 프로그래밍' 카테고리의 다른 글
[Study] Part 4 - 캐릭터의 입력 처리 (3/10) (0) | 2024.11.16 |
---|---|
[Study] Part 4 - 언리얼 게임플레이 어빌리티 시스템 개요 (1/10) (1) | 2024.11.11 |
[Study] Part 3 - 게임의 완성 (15/15) (3) | 2024.11.10 |
[Study] Part 3 - 게임플로우 다듬기 (14/15) (0) | 2024.11.09 |
[Study] Part 3 - 캐릭터 무브먼트의 확장 (13/15) (2) | 2024.09.10 |