GAS는 멀티플레이어 동기화(Replication), 복잡한 스킬 상호작용, 스탯 관리를 처리할 수 있는 프레임워크이다. "포트나이트"나 "파라곤" 같은 게임들이 이 시스템 위에서 돌아가고 있다.
1. 개념 및 설계: GAS의 3대장
GAS를 이해하려면 다음 세 가지 핵심 요소를 알아야 한다.
- Ability System Component (ASC): 모든 GAS 기능의 중추. 캐릭터(Actor)에 부착되어 스킬 사용, 스탯 변경 등을 총괄.
- Attribute Set: 체력(HP), 마나(MP), 공격력 등 수치 데이터를 관리.
- Gameplay Ability (GA) & Effect (GE): 실제로 실행되는 스킬 로직(GA)과 스탯을 변경하는 효과(GE).

2. 모듈 설정 (Build.cs)
GAS는 기본적으로 활성화되어 있지 않다. C++ 프로젝트라면 Build.cs 파일에 필수 모듈을 추가해야 함.
[ProjectName].Build.cs
public class MyProject : ModuleRules
{
public MyProject(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore",
"EnhancedInput",
// GAS 필수 모듈 3가지
"GameplayAbilities",
"GameplayTags",
"GameplayTasks"
});
}
}- 코드를 수정 후 라이더(Rider)나 에디터에서 'Refresh Visual Studio Project'를 실행하거나 빌드를 다시 해야 인텔리센스가 정상 작동.
3. Attribute Set 구현 (스탯 관리)
언리얼은 Attribute 관리를 위해 복잡한 매크로를 사용한다.
MyAttributeSet.h
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "MyAttributeSet.generated.h"
// 보일러플레이트 매크로: Getter, Setter, Init 함수 자동 생성
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
UCLASS()
class MYPROJECT_API UMyAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
UMyAttributeSet();
// 리플리케이션(네트워크 동기화)을 위한 필수 오버라이드
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// 값이 변경되기 전/후 처리 (예: HP < 0 방지)
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
// --- 체력 속성 정의 ---
UPROPERTY(BlueprintReadOnly, Category = "Attributes", ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UMyAttributeSet, Health);
UPROPERTY(BlueprintReadOnly, Category = "Attributes", ReplicatedUsing = OnRep_MaxHealth)
FGameplayAttributeData MaxHealth;
ATTRIBUTE_ACCESSORS(UMyAttributeSet, MaxHealth);
protected:
// 값이 서버에서 변경되어 클라이언트로 왔을 때 호출됨
UFUNCTION()
virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);
UFUNCTION()
virtual void OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth);
};MyAttributeSet.cpp
#include "MyAttributeSet.h"
#include "Net/UnrealNetwork.h"
#include "GameplayEffectExtension.h"
#include "Logging/StructuredLog.h" // UE_LOGFMT 사용
UMyAttributeSet::UMyAttributeSet()
{
// 초기값 설정 (나중에 데이터 테이블로 관리하는 것이 정석)
InitHealth(100.0f);
InitMaxHealth(100.0f);
}
void UMyAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// DOREPLLIFETIME_CONDITION_NOTIFY: 값이 바뀔 때만 전송하고 알림을 보냄
DOREPLLIFETIME_CONDITION_NOTIFY(UMyAttributeSet, Health, COND_None, REPNOTIFY_Always);
DOREPLLIFETIME_CONDITION_NOTIFY(UMyAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
}
void UMyAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetHealthAttribute())
{
// 체력은 0과 MaxHealth 사이여야 함
SetHealth(FMath::Clamp(GetHealth(), 0.0f, GetMaxHealth()));
// 로그 출력 (구조화된 로그 사용)
UE_LOGFMT(LogTemp, Log, "Health Changed: {0}", GetHealth());
}
}
void UMyAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
// 예측(Prediction) 시스템을 위해 필수
GAMEPLAYATTRIBUTE_REPNOTIFY(UMyAttributeSet, Health, OldHealth);
}
void UMyAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UMyAttributeSet, MaxHealth, OldMaxHealth);
}4. 캐릭터에 GAS 탑재 (Source)
이제 캐릭터가 위에서 만든 AttributeSet과 AbilitySystemComponent를 가지도록 설정. 여기서 중요한 것은 IAbilitySystemInterface를 상속받는 것이다.
MyCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystemInterface.h" // 필수 인터페이스
#include "MyCharacter.generated.h"
class UAbilitySystemComponent;
class UMyAttributeSet;
UCLASS()
class MYPROJECT_API AMyCharacter : public ACharacter, public IAbilitySystemInterface // 반드시 상속 받아야 함
{
GENERATED_BODY()
public:
AMyCharacter();
// IAbilitySystemInterface 구현: 이 액터의 ASC를 반환
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
protected:
virtual void BeginPlay() override;
// 서버에서 빙의되었을 때 ASC 초기화
virtual void PossessedBy(AController* NewController) override;
// 클라이언트에서 PlayerState 등이 복제되었을 때 ASC 초기화
virtual void OnRep_PlayerState() override;
private:
// ASC 컴포넌트 선언
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "GAS", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
// AttributeSet 선언
UPROPERTY()
TObjectPtr<UMyAttributeSet> AttributeSet;
// ASC 초기화 함수
void InitAbilitySystem();
};MyCharacter.cpp
#include "MyCharacter.h"
#include "AbilitySystemComponent.h"
#include "MyAttributeSet.h"
AMyCharacter::AMyCharacter()
{
// ASC 생성
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
// 중요: 캐릭터 상태가 복제되도록 설정 (멀티플레이)
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
// AttributeSet 생성
AttributeSet = CreateDefaultSubobject<UMyAttributeSet>(TEXT("AttributeSet"));
}
UAbilitySystemComponent* AMyCharacter::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
void AMyCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
// 서버 측 초기화
InitAbilitySystem();
}
void AMyCharacter::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
// 클라이언트 측 초기화
InitAbilitySystem();
}
void AMyCharacter::InitAbilitySystem()
{
if (AbilitySystemComponent)
{
// Owner와 Avatar 설정 (여기서는 캐릭터가 둘 다 역할 수행)
// 만약 PlayerState를 쓴다면 InitAbilityActorInfo(PlayerState, this)가 됨
AbilitySystemComponent->InitAbilityActorInfo(this, this);
UE_LOGFMT(LogTemp, Log, "GAS Initialized for {0}", GetName());
}
}5. JetBrains Rider & 최적화 팁
- 매크로 코드 생성:
ATTRIBUTE_ACCESSORS매크로를 작성할 때, Rider는 이를 인식하지 못해 빨간 줄을 띄울 수 있다. 하지만 컴파일은 된다. Rider 설정에서 Unreal Engine 매크로 지원을 켜둬야 함. - Generate Definition:
.h에서 함수 선언 후Alt + Enter->Generate definition을 사용하면.cpp로 즉시 이동하여 구현부를 만들어준다. - Live Templates:
UE_LOGFMT나DOREPLLIFETIME같은 반복 코드는 라이더의 'Live Templates'에 등록해두고 단축키로 불러쓰면 타이핑 실수를 줄여줍니다.
주의사항 :
Tick사용 자제: 체력 회복 등을 구현할 때Tick함수에서 매 프레임 값을 더하면 안된다. GAS의 GameplayEffect (Duration/Infinite) 를 사용하여 'Period' 설정을 통해 1초마다 회복되게 처리하는 것이 훨씬 성능에 좋다.- Nullptr 체크:
GetAbilitySystemComponent()를 호출할 때 항상 유효성을 검사.
'프로그래밍 > Unreal Engine 5' 카테고리의 다른 글
| [Unreal Engine 5] Unreal Engine 5 VS Schmetterling Engine 2.0 (0) | 2026.02.05 |
|---|---|
| [Unreal Engine 5] UFUNCTION()와 지정자(Specfier)들 (0) | 2026.01.29 |
| [Unreal Engine 5] 충돌처리를 위해 바인딩(Binding)을 하는 이유 (0) | 2026.01.23 |
| [Unreal Engine 5] TSubclassOf<T> (0) | 2026.01.16 |
| [Unreal Engine 5] UPROPERTY()와 지정자(Specifier)들 (0) | 2026.01.15 |