내일배움캠프/TIL

TIL260610 - Unreal - GAS3

옆집히드라 2026. 6. 10. 23:28

1. 핵심 개념


  • Gameplay Cue

2. 상세 내용


2.1. Gameplay Cue

 

Gameplay Cue는 게임플레이 로직에 영향을 주지 않는 부가 효과(FX) 전용 기능이다. 하나의 게임플레이 태그가 하나의 게임플레이 큐에 대응되며 독립된 GameplayCueManager가 태그를 기반으로 FX 효과를 재생한다.

GameplayCue는 게임플레이 이펙트에서 태그를 지정해서 자동 발동할 수 있으며, 태그를 사용하여 원하는 타이밍에 직접 호출도 가능하다. 또한 Gameplay Cue에 사용되는 태그는 특정 폴더 내에 블루프린트 이름과 자동으로 매칭하는 기능을 지원한다.(GC_DAMAGE_EFFECT 블루 프린트를 GameplayCue.Damage.Effect 태그에 대응하는 식)

 

 

GameplayCue의 종류

  • Static: 일순간(Burst) -> Instant 타입의 GE와 연계
  • Actor: 살아있는 동안 지속 -> Duration 타입의 GE와 연계

Event Functions

  • GameplayCue Static: 발동 시 OnExecute 함수를 실행함
  • GameplayCue Actor: 반복되는 주기(Period)마다 Executed 이벤트를 발생시킴

GAS에서의 데이터

수치 데이터

AttributeSetFGameplayAttributeData를 통해 관리한다. GAS의 코어 시스템(클라이언트 예측, 네트워크 롤백, 수치 중첩 및 계수 곱연산 파이프라인 등)은 성능 최적화와 연산의 통일성을 위해 내부적으로 오직 float으로만 구현되어 있다.

// 1. 선언은 무조건 float 기반으로 진행
UPROPERTY(BlueprintReadOnly, Category = "Ammo")
FGameplayAttributeData CurrentAmmo;
ATTRIBUTE_ACCESSORS(UMyAttributeSet, CurrentAmmo)

// 2. 로직에서 꺼내 쓸 때 형변환 (C++)
int32 DisplayAmmo = FMath::RoundToInt(GetCurrentAmmo());

논리 데이터

Gameplay Tag로 관리한다.

// ASC에서 특정 상태(Tag)를 가지고 있는지 검사하여 bool처럼 활용
if (TargetASC->HasMatchingGameplayTag(FGameplayTag::RequestGameplayTag(FName("Status.Debuff.Stun"))))
{
    // 기절 상태일 때의 처리 로직
    return; 
}

그외 데이터

Gameplay Effect Context에 상속해 Pre(Post)GameplayEffectExecute에서 사용한다.

// 1. 커스텀 Context 구조체 선언
USTRUCT()
struct FMyCustomEffectContext : public FGameplayEffectContext
{
    GENERATED_BODY()

    // 원하는 데이터를 자유롭게 선언
    UPROPERTY()
    FVector HitImpactLocation;

    UPROPERTY()
    FString UsedWeaponName;
};

// 2. 타겟의 AttributeSet(PostGameplayEffectExecute)에서 데이터 추출
void UMyAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
    Super::PostGameplayEffectExecute(Data);

    // 전달받은 봉투(Context)를 열어서 커스텀 타입으로 캐스팅
    if (const FMyCustomEffectContext* CustomContext = static_cast<const FMyCustomEffectContext*>(Data.EffectSpec.GetContext().Get()))
    {
        FVector ImpactPoint = CustomContext->HitImpactLocation;
        UE_LOG(LogTemp, Log, TEXT("타격당한 위치: %s"), *ImpactPoint.ToString());
    }
}

info, handle, spec

1. Info (정보 / 신분증)

대표 구조체: FGameplayAbilityActorInfo

비유: 스킬을 사용하는 주체의 "종합 신분증명서"

[역할과 특징]

스킬(GA)이 실행될 때, *"내 주인이 누구지? 내 애니메이션은 어디서 틀어야 하지? 내 컨트롤러는 뭐지?"*를 알기 위해 매번 Cast<ACharacter>를 하는 것은 성능상 매우 비효율적입니다.

그래서 GAS는 InitAbilityActorInfo()가 호출되는 순간, 이 스킬을 쓸 액터의 필수 컴포넌트(포인터)들을 미리 싹 다 캐싱(저장)해 둡니다. 이 캐싱된 데이터 묶음이 바로 ActorInfo입니다.

[주요 포함 데이터]

  • OwnerActor: ASC를 소유한 논리적 액터 (예: PlayerState)
  • AvatarActor: 월드에 존재하는 물리적 액터 (예: Character)
  • PlayerController, SkeletalMeshComponent, MovementComponent

[실제 사용 예시]

스킬(GA) 내부에서 주인의 캐릭터 무브먼트에 접근하고 싶을 때 사용합니다.

C++

// GA 내부에서 매번 Cast 할 필요 없이 Info에서 바로 꺼내 씁니다.
UCharacterMovementComponent* MoveComp = GetCurrentActorInfo()->MovementComponent.Get();

2. Handle (핸들 / 영수증 번호)

대표 구조체: FGameplayAbilitySpecHandle, FActiveGameplayEffectHandle, FGameplayAbilityTargetDataHandle

비유: 포인터 대신 사용하는 "안전한 식별표(티켓 번호)"

[역할과 특징]

멀티플레이 게임에서는 스킬이나 이펙트가 언제 파괴되거나 지연(Lag)될지 모릅니다. 만약 C++ 원시 포인터(*)를 주고받다가 대상이 삭제되면 게임이 크래시(Crash)됩니다.

이를 막기 위해 GAS는 객체 자체를 넘기지 않고, 고유한 ID 번호가 적힌 가벼운 구조체인 Handle을 발급합니다. 시스템에 *"이 핸들 번호 가진 이펙트 좀 지워줘"*라고 요청하면, 시스템이 알아서 안전하게 찾아 지워줍니다.

[주요 사용처]

  • FGameplayAbilitySpecHandle: 장착된 스킬을 특정해서 발동(TryActivateAbility)하거나 해제할 때 사용.
  • FActiveGameplayEffectHandle: 타겟에게 걸어둔 독(Poison) 데미지를 5초 뒤에 추적해서 강제로 지울 때 사용.

[실제 사용 예시]

C++

// 1. 적에게 이펙트를 적용하고 '영수증 번호(Handle)'를 받음
FActiveGameplayEffectHandle PoisonHandle = TargetASC->ApplyGameplayEffectSpecToSelf(*Spec);

// 2. 나중에 해독제를 먹으면 그 영수증 번호로 이펙트를 지움
TargetASC->RemoveActiveGameplayEffect(PoisonHandle);

3. Spec (명세서 / 주문서)

대표 구조체: FGameplayAbilitySpec, FGameplayEffectSpec

비유: 원본 클래스(설계도)를 바탕으로 구체적인 수치를 적어 넣은 "실행 주문서"

[역할과 특징]

언리얼의 클래스(Class)나 CDO는 원본 데이터이므로 게임 중에 함부로 수정할 수 없습니다. 따라서 원본 클래스를 바탕으로 "현재 레벨", "시전자 정보", "동적 태그" 등을 조합한 인스턴스화된 데이터를 만들어야 하는데, 이것이 Spec입니다.

[주요 사용처]

  • FGameplayAbilitySpec: 캐릭터에게 스킬을 부여(GiveAbility)할 때, 단순히 '파이어볼 클래스'만 주는 게 아니라 *"파이어볼 클래스, 스킬 레벨 3, 입력 키 Q"*를 묶어서 Spec으로 만들어 줍니다.
  • FGameplayEffectSpec: 적에게 데미지를 줄 때 적용됩니다. 단순히 '데미지 GE'를 넣는 게 아니라, *"데미지 GE, 시전자의 현재 레벨, 시전자의 공격력 계수"*를 모두 담은 명세서(Spec)를 만들어서 타겟에게 날립니다.

[실제 사용 예시]

C++

// 1. GE 클래스를 기반으로 빈 명세서(Spec)를 만듭니다.
FGameplayEffectSpecHandle SpecHandle = MakeOutgoingGameplayEffectSpec(DamageEffectClass, Level);

// 2. 명세서에 방금 타겟팅한 정보를 추가로 적어 넣습니다.
SpecHandle.Data.Get()->AddDynamicAssetTags(MyTags);

// 3. 완성된 명세서를 타겟의 ASC에 제출(Apply)합니다.
TargetASC->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());

💡 3줄 요약 테이블

용어 풀네임 예시 비유 핵심 역할
Info FGameplayAbilityActorInfo 신분증 "내가 누구고 어떤 컴포넌트를 가졌는가?" (캐싱된 포인터 모음)
Handle FActiveGameplayEffectHandle 티켓 / 번호표 "포인터 대신 쓰는 안전한 ID 번호" (크래시 방지용 참조 객체)
Spec FGameplayEffectSpec 주문서 / 명세서 "클래스 + 레벨 + 동적 변수"가 결합된 실질적인 실행 데이터

3. 질문 및 해결 (Q&A)


  • ASC의 레플리케이션 정책은? 언리얼에서의 레플리케이션의 개념은 구체적으로 무엇?
  • ASC에서 AttributeSet은 어디에?
  • GA 없이 GE만으로 AttributeSet 변경 가능?
  • AbilityTask는 비동기 구현용?

4. 관련 문서 (Links)


'내일배움캠프 > TIL' 카테고리의 다른 글

TIL260612 - Unreal  (0) 2026.06.12
TIL260611 - Unreal - GAS4  (0) 2026.06.11
TIL260609 - Unreal - GAS2  (0) 2026.06.09
TIL260608 - Unreal  (0) 2026.06.08
TIL260605 - Unreal - GAS1  (1) 2026.06.05