1. Gameplay Ability System 추가
프로젝트를 새로 생성합니다. 저의 경우 TopDownTemplate로 프로젝트를 생성했으며, 몇 가지 수정을 기초적으로 작업했습니다.
언리얼 엔진의 게임플레이 어빌리티 시스템 | 언리얼 엔진 5.4 문서 | Epic Developer Community
게임플레이 어빌리티 시스템 개요
dev.epicgames.com
위 링크의 내용처럼 Gameplay Ability 플러그인을 활성화시켜야 합니다.

플러그인 Enable을 시켜 주고, 모듈도 추가해 줍니다.

2. Gameplay Ability System 구성 요소 설명
구성요소를 간단히 설명하고, 더 자세한 부분은 시스템 개발을 진행하면서 적도록 하겠습니다.
- 2.1 GamePlay Ability
간단하게 설명하면 폰에 능력과 상태를 부여합니다. 롤의 캐릭터 아리를 예로 들어보겠습니다.

위의 그림에서 Q, W, E, R의 스킬들과 패시브 능력등이 Ability라고 생각하시면 간단합니다. 여기에 추가적으로 각각의 스킬에 추가적으로 부여돼 있는 특정 상태들(E 스킬을 맞으면 상대방 캐릭터는 조종불가 상태에서 E스킬을 발사한 캐릭터 쪽으로 걸어옵니다)도 Ability로 볼 수 있으며, 몇몇 아이템에 부여돼 있는 능력들도 Ability로 볼 수 있습니다.
GameplayAbility 클래스에는 GameplayAbility를 실행하는데 필요한 함수들과 변수들을 제공합니다.
- 2.2 AttributeSet
간단하게 캐릭터에 부여된 스탯 정보라고 보면 됩니다.

리그오브레전드에서 사용하는 캐릭터 스탯입니다. 왼쪽 상단부터 아래로 몇 가지를 보면 물리공격력, 물리방어력, 일반 공격속도, 치명타확률등 스탯 정보를 보여줍니다. 이 스탯 정보들을 Attribute라고 보시면 되고, AttributeSet은 이런 스탯 정보들을 지정해 주고, 레벨업이나 아이템 획득, 버프를 통해 추가적으로 값을 변경해 줄 수 있습니다.
- 2.3 Gameplay Effect
AttributeSet에 선언했던 Attribute들을 적용시키는 역할을 합니다.
위의 리그오브레전드 스탯 정보 이미지에서 보면 방패 표시의 물리방어력을 보면 45로 설정 돼 있습니다. Attribute에 Armor라는 변수가 있고 이 변수가 물리 방어력을 표현한다면 각각의 캐릭터마다 이 값이 다를 것입니다. 각 캐릭터마다 GameplayEffect를 만들거나, 캐릭터 타입별로 GameplayEffect를 만들어서 Attribute 값을 변경시키는 역할을 주로 한다고 보시면 됩니다.
- 2.4 Gameplay Cue
GameplayAbility를 실행하면서 나타나는 이펙트나 사운드들을 재생하는 역할을 담당합니다. GameplayAbility에서 이펙트나 사운드 등을 실행할 수도 있으나 이럴 경우 클라이언트에 복제되지 않기 때문에 GameplayCue를 이용해서 모든 클라이언트들에게 이펙트와 사운드들이 나타날 수 있게 해 주는 역할을 합니다.
3. GameplayAbility 코드 세팅
언리얼 엔진의 게임플레이 어빌리티 시스템 컴포넌트 및 게임플레이 어트리뷰트를 살펴봅니다.
게임플레이 어트리뷰트 및 어트리뷰트 세트와 함께 어빌리티 시스템 컴포넌트를 사용하는 방법을 살펴봅니다.
dev.epicgames.com
위 링크에 GameplayAbilitySystem 코드 세팅이 자세히 설명 돼 있습니다.
GameplayAbility를 사용하기 위해서는 해당 어빌리티를 사용하는 클래스에 IAbilitySystemInterface를 상속해야 합니다.
실제 GameplayAbilty와 AttributeSet은 Ability와 스탯정보를 폰에서 사용되기 때문에 보통 Pawn에 설정됩니다. 한 번 죽으면 게임이 끝나는 게임은 유저가 플레이하는 폰에 GameplayAbility와 AttributeSet 변수를 가지고 있어도 되지만 한 번 죽었더라도 리스폰 시간이 지난 후에 다시 생성되는 게임의 경우에는 폰이 죽으면 그 게임 내에서 적용되면서 진행 됐던 AttributeSet와 Ability들이 사라지게 되므로 이 정보를 다른 곳에 저장해야 할 필요가 있습니다. 그렇기 때문에 GameplayAbility와 AttributeSet을 PlayerState에 선언하고, 이 값을 플레이어가 컨트롤하는 폰이 특정 시점에 포인팅 하여 사용하도록 구성했습니다.

유저가 컨트롤하는 캐릭터(GOPlayerCharacter)의 경우 PlayerState에 원본 AbilitySystemComponent와 AttributeSet을 가지고 있고, 캐릭터가 빙의되는 순간에 원본 요소들을 참조하여 사용하는 방식으로 구성했습니다.
몬스터의 경우 PlayerState가 없기 때문에 해당 몬스터가 원본 AbilitySystemComponent와 AttributeSet을 가지고 있고, Monster의 생성자에서 AbilitySystemComponent와 AttributeSet을 생성하고, Beginplay에서 플레이어 캐릭터가 참조하듯이 몬스터도 구성 시스템을 참조해 줍니다.
이렇게 구성함으로써 몬스터와 캐릭터가 동일하게 AbilitySystemComponent와 AttributeSet을 변경할 때는 ProjectGOCharacter에서 작업을 진행하고, 개별 적용을 할 때는 각각의 하위 클래스에서 적용을 할 수 있도록 클래스 구성해 줍니다.
캐릭터의 경우 PlayerState에 AbilitySystemComponent와 AttributeSet을 설정하는 방법과 Character에 설정하는 방법이 있는데, 캐릭터가 죽고 다시 부활하여 게임을 진행해야 하기 때문에 PlayerState에 설정하는 방법을 통해서 캐릭터가 다시 부활했을 때 이전에 진행됐던 값을 캐릭터가 다시 참조하는 방식으로 구현할 수 있습니다.
원본은 TObjectPtr타입으로 변수를 선언하여 언리얼의 리플렉션 시스템에 적용되도록 세팅했고, 다른 객체에서 참조할 때는 TWeakObjectPtr타입 변수로 선언했습니다. TWeakObjectPtr타입은 UClass가 아닌 클래스에서 UObject객체를 참조하여 객체가 유효한지를 알 수 있고, 가비지 컬렉터에 수집되지 않도록 하기 위해 TWeakObjectPtr로 설정했습니다.

AbilitySystemComponent를 사용하기 위해서는 사용처에 위 그림과 같이 인터페이스 상속을 진행해 주어야 합니다.
저의 경우 AGOPlayerCharacter와 AProjectGOMonster는 AGOProjectCharacter를 상속받았기 때문에 AProjectGOCharacter와 AGOPlayerState에만 상속해 주고 인터페이스 함수를 오버라이드 해 주면 됩니다.
AbilitySystemComponent의 경우 ReplicationMode를 세팅 해 주는데 아래의 표와 같습니다.

멀티플레이어 형식에서 몬스터는 Minimal로 세팅 해 주고, 플레이어는 Mixed로 세팅 해 줍니다.
우선 AbilitySystem과 AttributeSet을 내가 원하는 방식으로 만들 수 있게 클래스를 생성 해 줍니다.

#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
class PROJECTGO_API UGOAttributeSetBase : public UAttributeSet
{
GENERATED_BODY()
public:
};
3-1 캐릭터 세팅
//AProjectGOCharacter.h
UCLASS(Blueprintable, BlueprintType)
class AProjectGOCharacter : public ACharacter, public IAbilitySystemInterface, public ICombatInterface
{
GENERATED_BODY()
...
protected:
UPROPERTY(VisibleAnywhere)
TWeakObjectPtr<class UGOAbilitySystemComponent> AbilitySystemComponent;
UPROPERTY(VisibleAnywhere)
TWeakObjectPtr<class UGOAttributeSetBase> AttributeSetBase;
...
public:
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
};
//AProjectGOCharacter.cpp
...
UAbilitySystemComponent* AProjectGOCharacter::GetAbilitySystemComponent() const
{
return AbilitySystemComponent.Get();
}
...
//AGOPlayerState.h
UCLASS()
class PROJECTGO_API AGOPlayerState : public APlayerState, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
protected:
UPROPERTY(VisibleAnywhere)
TObjectPtr<class UGOAbilitySystemComponent> AbilitySystemComponent;
UPROPERTY(VisibleAnywhere)
TObjectPtr<class UGOAttributeSetBase> AttributeSetBase;
};
//AGOPlayerState.cpp
...
AGOPlayerState::AGOPlayerState()
{
AbilitySystemComponent = CreateDefaultSubobject<UGOAbilitySystemComponent>(TEXT("GOAbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
AttributeSetBase = CreateDefaultSubobject<UGOAttributeSetBase>(TEXT("GOAttributeSetBase"));
NetUpdateFrequency = 100.0f;
}
UAbilitySystemComponent* AGOPlayerState::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
...
위 코드와 같이 PlayerState에 AbilitySystemComponent와 AttributeSet을 생성합니다.
다음 캐릭터에서 PlayerState가 세팅될 때 캐릭터의 AbilitySystemComponent와 AttributeSet에 PlayerState에 있던 컴포넌트들을 설정해 줍니다.
캐릭터에 PlayerState가 세팅될 때는 Controller가 폰에 빙의할 때입니다. 폰은 캐릭터의 부모 클래스이고, 빙의가 진행되면 PossessedBy라는 함수가 호출됩니다.(서버)

PossessedBy함수에서 아래 보시면 여러 개의 함수를 호출시켰습니다 그중에서도 InitializeAbilityValue함수를 보겠습니다.

위 빨간색으로 네모 친 부분이 캐릭터에서 PlayerState에 있는 AbilitySystemComponent와 AttributeSet을 참조하는 부분입니다. 위 코드에서 컴포넌트들을 세팅했지만 서버에서 세팅했기 때문에 클라이언트에서의 세팅도 해 주어야 합니다.
Pawn에 보시면 PlayerState가 Replicate 변수로 설정 돼 있고, 이 값이 서버에서 바뀌면 클라이언트로 함수가 호출됩니다.

클라이언트로 OnRep_PlayerState함수가 호출되기 때문에 위 함수를 AGOPlayerCharacter에서 재정의 하여 이 함수에서 클라이언트사이드에 AbilitySystemComponent와 AttributeSet의 참조를 진행해 줍니다.

PlayerCharacter에서 위 작업을 진행해 주고, 몬스터와 플레이어의 공통 초기화를 공통 부모인 AProjectGOCharacter에서 해 주면 됩니다.
3-2 몬스터 세팅
몬스터의 경우 PlayerState가 없기 때문에 생성자에서 AbilitySystemComponent와 AttributeSet을 생성합니다.

//AProjectGOMonster.cpp
AProjectGOMonster::AProjectGOMonster(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
MonsterAbilitySystemComponent = CreateDefaultSubobject<UGOAbilitySystemComponent>(TEXT("MonsterAbilitySystemComponent"));
MonsterAbilitySystemComponent->SetIsReplicated(true);
MonsterAbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Minimal);
MonsterAttributeSetBase = CreateDefaultSubobject<UGOAttributeSetBase>(TEXT("MonsterAttributeSetBase"));
bUseControllerRotationPitch = false;
bUseControllerRotationRoll = false;
bUseControllerRotationYaw = false;
AbilitySystemComponent = MonsterAbilitySystemComponent;
AttributeSetBase = MonsterAttributeSetBase;
GetCharacterMovement()->bUseControllerDesiredRotation = true;
}
...
//AProjectGOMonster.h
UCLASS()
class PROJECTGO_API AProjectGOMonster : public AProjectGOCharacter, public IMonsterInterface
{
GENERATED_BODY()
private:
protected:
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<class UGOAbilitySystemComponent> MonsterAbilitySystemComponent;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<class UGOAttributeSetBase> MonsterAttributeSetBase
};
몬스터의 AbilitySystem 초기화의 경우 BeginPlay에서 진행합니다. 몬스터는 PlayerState가 없기 때문에 위에서 초기화를 진행했던 함수에 nullptr을 전달해 주고, 플레이어 캐릭터와 같은 초기화를 진행해 줍니다.


BeginPlay는 서버/클라 둘 다 들어오기 때문에 InitializeAbilityValue함수를 따로 실행할 필요가 없습니다.
플레이어 캐릭터와 똑같이 몬스터에서 생성한 AbilitySystem과 AttributeSet을 참조시켜 줍니다.
이러한 구조를 통해서 몬스터와 캐릭터 공통작업은 부모 클래스인 ProjectGOCharacter에서 작업을 진행하고, 개별 작업은 각각의 캐릭터 클래스에서 진행하면 됩니다.
초기 AbilitySystemComponent과 AttributeSet 생성과 지정을 해 봤습니다.
다음은 AttributeSet 컴포넌트에 캐릭터의 스탯정보들을 추가하는 방식을 알아보겠습니다.
4. 당부 말씀
현재까지 진행된 부분에서 설명이 빠진 부분이 있을 수 있습니다. 배웠던 내용을 다시 한번 복기하면서 GAS시스템에 대한 이해도를 더 올리고, 이 시스템을 이용하려 하는 분들에게 도움이 되기 위해 작성하는 포스팅입니다. 전반적인 시스템에 대한 설명과 코드 구현을 작성하기 때문에 빈 부분이 많을 수도 있습니다. 댓글로 부족한 부분에 대해 적어 주시면 감사하겠습니다. 제공된 깃 허브 주소에 전체 코드가 있으니 전체 코드를 참고해 주시면 되겠습니다.
언리얼에서 제공되는 파라곤 에셋을 사용하지만 파일은 많은데 정작 쓸만한 파일은 별로 없습니다. 포스팅을 진행하면서 기초적인 부분만 파라곤 에셋을 이용하고 다른 게임의 스킬이나 아이템 효과등을 따라 해 볼 때는 다른 에셋을 사용하려고 합니다. 때문에 포스팅 중간중간의 텀이 길어질 수 있을 것 같습니다.
깃 허브 주소는 포스팅 마지막에 넣겠습니다.
https://github.com/ChoDuckHwan/ProjectGO
GitHub - ChoDuckHwan/ProjectGO: Individual Practice Project
Individual Practice Project. Contribute to ChoDuckHwan/ProjectGO development by creating an account on GitHub.
github.com
'UnrealEngine > GAS(Gameplay Ability System)' 카테고리의 다른 글
| GamePlay Ability System(GAS) (0) (0) | 2024.05.10 |
|---|