이득우 님의 강의
와 다른 개발자가 정리해놓은 문서를 보고 정리한 내용입니다.
자세하고 정확한 내용은 위 링크를 참조하세요
시리즈
GAS 기초
GAS 캐릭터 제작 기초
어트리뷰트와 게임플레이 이펙트
- 언리얼 GAS 캐릭터 어트리뷰트
- 언리얼 GAS 게임플레이 이펙트
- 언리얼 GAS 어트리뷰트와 UI 연동
GAS의 활용
- 언리얼 GAS 아이템 상자 구현
- 언리얼 GAS 광역 스킬 구현
이번 포스트에서는 아래 3가지의 방법을 통해 액터의 움직임을 구현해보면서 GAS 프레임워크의 사용 방식에 대해 알아보고,
각 방식에 따른 차이점을 살펴보겠다.
- 액터 기능 확장
- Game Ability System 사용
- Game Ability System + Gameplay Tag 사용
액터로는 분수대, 움직임은 3초간 제자리 회전/정지를 기준으로 제작하였다.
액터 기능 확장
GAS 프레임워크 없이 URotatingMovementComponent을 직접 사용하여 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
UCLASS()
class ARENABATTLEGAS_API AABGASFountain : public AABFountain
{
GENERATED_BODY()
public:
AABGASFountain();
protected:
virtual void PostInitializeComponents() override;
virtual void BeginPlay() override;
virtual void TimerAction();
protected:
UPROPERTY(VisibleAnywhere, Category=Movement)
TObjectPtr<URotatingMovementComponent> RotatingMovement;
UPROPERTY(EditAnywhere, Category=Timer)
float ActionInterval;
FTimerHandle ActionTimer;
};
void AABGASFountain::BeginPlay()
{
Super::BeginPlay();
GetWorld()->GetTimerManager().SetTimer(ActionTimer, this, &AABGASFountain::TimerAction, ActionInterval, true, 0.0f);
}
void AABGASFountain::TimerAction()
{
if(!RotatingMovement->IsActive())
{
RotatingMovement->Activate();
}
else
{
RotatingMovement->Deactivate();
}
}
|
Game Ability System 사용
GAS 프레임워크를 이용하여 움직임을 구현하기 위해서는, 아래 두 개념을 먼저 이해해야 한다.
- Ability System Component
- Game Ability
Ability System Component
- 게임플레이 어빌리티 시스템을 관리하는 컴포넌트
- 액터 당 하나만 부착 가능
- 액터는 이를 통하여 Gameplay Ability를 발동시킬 수 있음
- 해당 컴포넌트를 부착한 액터 사이에 Game Ability System에 의한 상호작용이 가능해짐
Game Ability
- Ability System Component에 등록되어 발동시킬 수 있는 액션
- 발동 과정
- Ability System Component에 등록 : AbilitySystemComponent->GiveAbility()
- 액션 발동 : AbilitySystemComponent->TryActivateAbility()
- 발동된 ability 내부에서는 SpecHandle, ActorInfo, ActivationInfo를 활용하여 요구사항 구현
- 주요 메서드
- CanActivateAbility
- ActivateAbility
- CancelAbility
- EndAbility
위 두 개념을 이용하여 맨 처음과 동일한 움직임을 구현하면 아래와 같다.
- GameplayAbility를 상속하여 어빌리티 클래스 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
UCLASS()
class ARENABATTLEGAS_API UABGA_Rotate : public UGameplayAbility
{
GENERATED_BODY()
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;
};
void UABGA_Rotate::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
if(auto Avatar = ActorInfo->AvatarActor.Get())
{
if(auto RotatingMovement = Avatar->GetComponentByClass(URotatingMovementComponent::StaticClass()))
{
RotatingMovement->Activate();
}
}
}
void UABGA_Rotate::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility)
{
Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility);
// 생략
}
|
- 액터에 컴포넌트 부착 및 인터페이스 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
UCLASS()
class ARENABATTLEGAS_API AABGASFountain : public AABFountain, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
AABGASFountain();
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override; // pure virtual
//...
UPROPERTY(VisibleAnywhere, Category=Ability)
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
UPROPERTY(VisibleAnywhere, Category=Ability)
TObjectPtr<URotatingMovementComponent> RotatingMovement;
UPROPERTY(EditAnywhere, Category=Timer)
float ActionInterval;
FTimerHandle ActionTimer;
};
UAbilitySystemComponent* AABGASFountain::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
|
- 부착된 컴포넌트에 어빌리티 등록 및 발동
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
void AABGASFountain::PostInitializeComponents()
{
Super::PostInitializeComponents();
AbilitySystemComponent->InitAbilityActorInfo(this, this);
FGameplayAbilitySpec RotateSpec(UABGA_Rotate::StaticClass());
AbilitySystemComponent->GiveAbility(RotateSpec);
}
void AABGASFountain::TimerAction()
{
if(auto RotateSpec = AbilitySystemComponent->FindAbilitySpecFromClass(UABGA_Rotate::StaticClass()))
{
if(!RotateSpec->IsActive())
{
AbilitySystemComponent->TryActivateAbility(RotateSpec->Handle);
}
else
{
AbilitySystemComponent->CancelAbilityHandle(RotateSpec->Handle);
}
}
}
|
Game Ability System + Gameplay Tag 사용
게임플레이 태그 없이 Game Ability System만을 사용하여 구현하는 경우, GAS 프레임워크의 장점을 최대로 활용할 수 없다.
Gameplay Tag가 무엇인지 먼저 알아보자
FGameplayTag란 Parent.Child.Grandchild...
와 같이 계층적 형태로 등록/관리되는 이름을 의미한다. GameplayTagManager에 의해 등록된다.
이러한 태그들을 이용하여 클래스들을 분류하고, 상태를 효과적으로 추적할 수 있다.
상태 추적을 위해 bool값이나 enum을 두고 해당 값을 기준으로 조건문을 통해 상태를 확인하던 방식들은,
해당 오브젝트가 Gameplay Tag를 가지고있는지 여부로 대체할 수 있다.
한편 여러개의 Tag들은 Tarray보다는 FGameplayTagContainer를 이용하여 관리하는 것이 좋다. 언제든 배열 형태로
태그들을 받아볼 수 있을 뿐만 아니라, 여러 메서드를 통해 태그 관리를 쉽게 할 수 있기 때문이다.
GAS 프레임워크는 Gameplay Tag와 매우 친화적이다. UAbilitySystemComponent가 IGameplayTagAssetInterface를 구현하기 때문에 Ability System Component에 직접 태그를 지정할 수 있을 뿐만 아니라,
아래와 같이 GameplayAbility의 여러 상태들을 태그 컨테이너로 관리할 수 있도록 구현해두었기 때문이다.
1
2
3
4
5
6
7
8
9
10
11
12
|
// GameplayAbility.h
/** Abilities with these tags are cancelled when this ability is executed */
UPROPERTY(EditDefaultsOnly, Category = Tags, meta=(Categories="AbilityTagCategory"))
FGameplayTagContainer CancelAbilitiesWithTag;
/** Abilities with these tags are blocked while this ability is active */
UPROPERTY(EditDefaultsOnly, Category = Tags, meta=(Categories="AbilityTagCategory"))
FGameplayTagContainer BlockAbilitiesWithTag;
/** Tags to apply to activating owner while this ability is active. These are replicated if ReplicateActivationOwnedTags is enabled in AbilitySystemGlobals. */
UPROPERTY(EditDefaultsOnly, Category = Tags, meta=(Categories="OwnedTagsCategory"))
FGameplayTagContainer ActivationOwnedTags;
|
또한 게임플레이 태그를 활용하면, 액터 클래스에서 Gameplay Ability 클래스에 대한 정보가 없어도 동일한 요구사항을 구현할 수 있기 때문에
코드 간 낮은 의존성을 유지시킬 수 있다는 장점이 있다.
게임플레이 태그를 활용하면 동일한 요구 사항을 아래와 같이 구현할 수 있다.
- 태그 생성
1
2
3
4
5
|
// DefaultGameplayTags.ini
[/Script/GameplayTags.GameplayTagsSettings]
//...
+GameplayTagList=(Tag="Actor.Action.Rotate",DevComment="")
+GameplayTagList=(Tag="Actor.State.IsRotating",DevComment="")
|
- 태그를 쉽게 얻어오기 위한 매크로 작성
1
2
3
|
// ABGameplayTag.h
#define ABTAG_ACTOR_ROTATE FGameplayTag::RequestGameplayTag(FName("Actor.Action.Rotate"))
#define ABTAG_ACTOR_ISROTATING FGameplayTag::RequestGameplayTag(FName("Actor.State.IsRotating"))
|
- Gameplay Ability에 태그 등록
1
2
3
4
5
6
|
UABGA_Rotate::UABGA_Rotate()
{
AbilityTags.AddTag(ABTAG_ACTOR_ROTATE);
// 활성화될 때 아래 태그가 심어짐
ActivationOwnedTags.AddTag(ABTAG_ACTOR_ISROTATING);
}
|
- 태그를 활용한 어빌리티 등록 및 활성화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
// ABGASFountain.h
UPROPERTY(EditAnywhere, Category=GAS)
TArray<TSubclassOf<UGameplayAbility>> Abilities;
// 구현부
void AABGASFountain::PostInitializeComponents()
{
Super::PostInitializeComponents();
AbilitySystemComponent->InitAbilityActorInfo(this, this);
for (auto Element : Abilities)
{
FGameplayAbilitySpec Spec(Element);
AbilitySystemComponent->GiveAbility(Spec);
}
}
void AABGASFountain::TimerAction()
{
FGameplayTagContainer TargetTag(ABTAG_ACTOR_ROTATE);
if(!AbilitySystemComponent->HasMatchingGameplayTag(ABTAG_ACTOR_ISROTATING))
{
AbilitySystemComponent->TryActivateAbilitiesByTag(TargetTag);
}
else
{
AbilitySystemComponent->CancelAbilities(&TargetTag);
}
}
|
이후 AABGASFountain cpp 클래스를 상속하는 블루프린트 클래스를 만든 후, Details 패널에서 알맞은 액티비티를 Abilities에 넣어주면 된다.
정리
회전하는 분수대를 세 가지의 방법으로 구현
- 액터의 기능 확장
- GAS : 새로운 클래스(ability)를 만들어 액터로부터 기능 분리
- GAS + Gameplay Tags : 태그를 활용해 액터와 ability 간 의존성 제거
GAS와 Gameplay Tags를 같이 사용했을 때의 좋은 점
- 액터의 역할 최소화
- 의존성 제거. 유지보수가 쉬워지고, 재사용성이 높아짐
- 확장성 + 타직군 과의 협업 용이
