Gameplay Ability System in Lyra

Jangmanbo·2025년 1월 8일

언리얼 라이라 프로젝트와 엮어서 Gameplay Ability Sytem을 전체적으로 정리해봤다.

Gameplay Ability System (GAS)

  • 어빌리티 및 어트리뷰트 유형을 구축하기 위한 프레임워크.
  • 캐릭터의 액션 그리고 이러한 액션의 결과로 다양한 어트리뷰트를 높이거나 낮추는 상태 이펙트를 만들고, 파티클 및 사운드 이펙트를 활성화하는 등의 작업을 할 수 있다.

UAbilitySystemComponent

  • 게임 플레이 어빌리티 시스템과 상호작용하는 액터는 Ability System Component를 가진다.

UGameplayAbility

  • 특정 어빌리티가 어떤 역할을 하는지 등을 정의
  • ex. 무기 사용, 점프, 질주,사망 후 리스폰 트리거
// UAbilitySystemComponent class

/** Full list of all instance-per-execution gameplay abilities associated with this component */
UE_DEPRECATED(5.1, "This array will be made private. Use GetReplicatedInstancedAbilities, AddReplicatedInstancedAbility or RemoveReplicatedInstancedAbility instead.")
UPROPERTY()
TArray<TObjectPtr<UGameplayAbility>>	AllReplicatedInstancedAbilities;

UAbilityTask

  • 작업 완료할 때 해당 어빌리티 역할에 맞는 델리게이트를 호출

UAttributeSet

  • 어트리뷰트 컬렉션으로, 게임 메커니즘 내에서 특정 의미를 지닌 숫자 값 (ex. 체력, 기본 공격력)
  • 하나 이상의 어트리뷰트 프로퍼티를 정의 및 관리
// UAbilitySystemComponent class

/** List of attribute sets */
UPROPERTY(Replicated, ReplicatedUsing = OnRep_SpawnedAttributes, Transient)
TArray<TObjectPtr<UAttributeSet>>	SpawnedAttributes;

FGameplayAttribute

  • 게임플레이 어트리뷰트 변수들을 가지는 구조체. 게임플레이 이펙트 사용 시 이 어트리뷰트를 변경할 수 있다.

FGameplayEffectContext

  • 게임 액션의 결과로 어트리뷰트를 일시적 또는 영구적으로 수정한다.

게임 플레이 어빌리티 시스템은 플러그인으로 제공된다.

PublicDependencyModuleNames.AddRange(new string[] { "GameplayAbilities", "GameplayTags", "GameplayTasks", "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay" });

플러그인 활성화 후 Build.cs > PublicDependencyModuleNames에 'GameplayAbilities', 'GameplayTags', 'GameplayTasks'를 추가한다.


Lyra Example

ULyraAbilitySystemComponent

ALyraPlayerState

UCLASS(Config = Game)
class LYRAGAME_API ALyraPlayerState : public AModularPlayerState, public IAbilitySystemInterface, public ILyraTeamAgentInterface
{
	// ...
    
	// The ability system component sub-object used by player characters.
	UPROPERTY(VisibleAnywhere, Category = "Lyra|PlayerState")
	TObjectPtr<ULyraAbilitySystemComponent> AbilitySystemComponent;
    
    // ...
};

ULyraAbilitySystemComponent를 소유함으로써 기본 폰 데이터와 GAS 스테이트 로직을 분리할 수 있다.

ALyraGameState

UCLASS(Config = Game)
class LYRAGAME_API ALyraGameState : public AModularGameStateBase, public IAbilitySystemInterface
{
	// The ability system component subobject for game-wide things (primarily gameplay cues)
	UPROPERTY(VisibleAnywhere, Category = "Lyra|GameState")
	TObjectPtr<ULyraAbilitySystemComponent> AbilitySystemComponent;
};

마찬가지로 ALyraGameState도 ULyraAbilitySystemComponent를 통해 게임페이즈를 관리힌다.

ULyraGlobalAbilitySystem

ULyraAbilitySystemComponent는 초기화 중에 자동으로 ULyraGlobalAbilitySystem 등록된다.
따라서 레벨의 모든 ULyraAbilitySystemComponent를 추적하고 상호작용할 수 있습니다.

등록된 어빌리티 시스템 컴포넌트들에 어빌리티와 게임플레이 이펙트를 제거하거나 부여하는 블루프린트 호출 가능 함수를 제공한다.

UCLASS()
class ULyraGlobalAbilitySystem : public UWorldSubsystem
{
	GENERATED_BODY()

public:
	ULyraGlobalAbilitySystem();

	UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="Lyra")
	void ApplyAbilityToAll(TSubclassOf<UGameplayAbility> Ability);

	UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="Lyra")
	void ApplyEffectToAll(TSubclassOf<UGameplayEffect> Effect);

	UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Lyra")
	void RemoveAbilityFromAll(TSubclassOf<UGameplayAbility> Ability);

	UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Lyra")
	void RemoveEffectFromAll(TSubclassOf<UGameplayEffect> Effect);

	/** Register an ASC with global system and apply any active global effects/abilities. */
	void RegisterASC(ULyraAbilitySystemComponent* ASC);

	/** Removes an ASC from the global system, along with any active global effects/abilities. */
	void UnregisterASC(ULyraAbilitySystemComponent* ASC);

private:
	UPROPERTY()
	TMap<TSubclassOf<UGameplayAbility>, FGlobalAppliedAbilityList> AppliedAbilities;

	UPROPERTY()
	TMap<TSubclassOf<UGameplayEffect>, FGlobalAppliedEffectList> AppliedEffects;

	UPROPERTY()
	TArray<TObjectPtr<ULyraAbilitySystemComponent>> RegisteredASCs;
};
// 등록된 어빌리티 시스템 컴포넌트들에 게임플레이 이펙트를 부여
void ULyraGlobalAbilitySystem::ApplyEffectToAll(TSubclassOf<UGameplayEffect> Effect)
{
	if ((Effect.Get() != nullptr) && (!AppliedEffects.Contains(Effect)))
	{
		FGlobalAppliedEffectList& Entry = AppliedEffects.Add(Effect);
		for (ULyraAbilitySystemComponent* ASC : RegisteredASCs)
		{
			Entry.AddToASC(Effect, ASC);
		}
	}
}
// actor 초기화할 때 ULyraGlobalAbilitySystem에 컴포넌트 등록
void ULyraAbilitySystemComponent::InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor)
{
	// ...

	// Register with the global system once we actually have a pawn avatar. We wait until this time since some globally-applied effects may require an avatar.
	if (ULyraGlobalAbilitySystem* GlobalAbilitySystem = UWorld::GetSubsystem<ULyraGlobalAbilitySystem>(GetWorld()))
	{
		GlobalAbilitySystem->RegisterASC(this);
	}

	// ...
}

// 월드에서 제거되면 ULyraGlobalAbilitySystem에서도 제거
void ULyraAbilitySystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	if (ULyraGlobalAbilitySystem* GlobalAbilitySystem = UWorld::GetSubsystem<ULyraGlobalAbilitySystem>(GetWorld()))
	{
		GlobalAbilitySystem->UnregisterASC(this);
	}

	Super::EndPlay(EndPlayReason);
}

ULyraAbilitySet

데이터 에셋 유형으로 라이라 캐릭터에게 부여할 게임플레이 어빌리티, 게임플레이 이펙트, 어트리뷰트 세트 목록을 갖고 있다.

/**
 * ULyraAbilitySet
 *
 *	Non-mutable data asset used to grant gameplay abilities and gameplay effects.
 */
UCLASS(BlueprintType, Const)
class ULyraAbilitySet : public UPrimaryDataAsset
{
	GENERATED_BODY()

public:

	ULyraAbilitySet(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

	// Grants the ability set to the specified ability system component.
	// The returned handles can be used later to take away anything that was granted.
	void GiveToAbilitySystem(ULyraAbilitySystemComponent* LyraASC, FLyraAbilitySet_GrantedHandles* OutGrantedHandles, UObject* SourceObject = nullptr) const;

protected:

	// Gameplay abilities to grant when this ability set is granted.
	UPROPERTY(EditDefaultsOnly, Category = "Gameplay Abilities", meta=(TitleProperty=Ability))
	TArray<FLyraAbilitySet_GameplayAbility> GrantedGameplayAbilities;

	// Gameplay effects to grant when this ability set is granted.
	UPROPERTY(EditDefaultsOnly, Category = "Gameplay Effects", meta=(TitleProperty=GameplayEffect))
	TArray<FLyraAbilitySet_GameplayEffect> GrantedGameplayEffects;

	// Attribute sets to grant when this ability set is granted.
	UPROPERTY(EditDefaultsOnly, Category = "Attribute Sets", meta=(TitleProperty=AttributeSet))
	TArray<FLyraAbilitySet_AttributeSet> GrantedAttributes;
};
// Actor가 가지고 있는 ULyraAbilitySystemComponent에 어빌리티 세트를 적용
void ALyraPlayerState::SetPawnData(const ULyraPawnData* InPawnData)
{
	//...
    
    for (const ULyraAbilitySet* AbilitySet : PawnData->AbilitySets)
	{
		if (AbilitySet)
		{
			AbilitySet->GiveToAbilitySystem(AbilitySystemComponent, nullptr);
		}
	}

	// ...
}

FLyraGameplayEffectContext

FGameplayEffectContext 구조체에서 확장되어 Gameplay Cue Notifies 에 전송할 추가 데이터 멤버와 함수를 정의한다.

여기서 GameplayCueNotify는 시각 이펙트나 사운드와 같은 게임 로직과 무관한 시각적, 청각적 기능을 담당하는 클래스를 말한다.
언리얼5_4_3 : 게임플레이 어빌리티 시스템의 활용 방법

ULyraAttributeSet

UAttributeSet 클래스를 상속받은 클래스로 게임플레이 어트리뷰트 프로퍼티를 자동화하기 위한 편리한 ATTRIBUTE_ACCESSORS 매크로를 제공한다.

/**
 * This macro defines a set of helper functions for accessing and initializing attributes.
 *
 * The following example of the macro:
 *		ATTRIBUTE_ACCESSORS(ULyraHealthSet, Health)
 * will create the following functions:
 *		static FGameplayAttribute GetHealthAttribute();
 *		float GetHealth() const;
 *		void SetHealth(float NewVal);
 *		void InitHealth(float NewVal);
 */
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

/** 
 * Delegate used to broadcast attribute events, some of these parameters may be null on clients: 
 * @param EffectInstigator	The original instigating actor for this event
 * @param EffectCauser		The physical actor that caused the change
 * @param EffectSpec		The full effect spec for this change
 * @param EffectMagnitude	The raw magnitude, this is before clamping
 * @param OldValue			The value of the attribute before it was changed
 * @param NewValue			The value after it was changed
*/
DECLARE_MULTICAST_DELEGATE_SixParams(FLyraAttributeEvent, AActor* /*EffectInstigator*/, AActor* /*EffectCauser*/, const FGameplayEffectSpec* /*EffectSpec*/, float /*EffectMagnitude*/, float /*OldValue*/, float /*NewValue*/);

/**
 * ULyraAttributeSet
 *
 *	Base attribute set class for the project.
 */
UCLASS()
class LYRAGAME_API ULyraAttributeSet : public UAttributeSet
{
	GENERATED_BODY()

public:

	ULyraAttributeSet();

	UWorld* GetWorld() const override;

	ULyraAbilitySystemComponent* GetLyraAbilitySystemComponent() const;
};

ULyraHealthSet

UCLASS(BlueprintType)
class ULyraHealthSet : public ULyraAttributeSet
{
	GENERATED_BODY()

public:

	ULyraHealthSet();
	
    // ULyraAttributeSet에서 정의한 매크로 사용
	ATTRIBUTE_ACCESSORS(ULyraHealthSet, Health);
	ATTRIBUTE_ACCESSORS(ULyraHealthSet, MaxHealth);
	ATTRIBUTE_ACCESSORS(ULyraHealthSet, Healing);
	ATTRIBUTE_ACCESSORS(ULyraHealthSet, Damage);
    
    // ...
};

ULyraCombatSet

UCLASS(BlueprintType)
class ULyraCombatSet : public ULyraAttributeSet
{
	GENERATED_BODY()

public:

	ULyraCombatSet();

	ATTRIBUTE_ACCESSORS(ULyraCombatSet, BaseDamage);
	ATTRIBUTE_ACCESSORS(ULyraCombatSet, BaseHeal);
	
    // ...
};

참고 문서

언리얼 엔진의 게임플레이 어빌리티 시스템 | 언리얼 엔진 5.5 문서 | Epic Developer Community | Epic Developer Community
언리얼 엔진에서 라이라의 어빌리티 | 언리얼 엔진 5.5 문서 | Epic Developer Community | Epic Developer Community

0개의 댓글