내일배움캠프/TIL

TIL260611 - Unreal - GAS4

옆집히드라 2026. 6. 11. 21:04

1. 핵심 개념


  • GE링 ASC에 태그 부여하는 방식 고민

2. 상세 내용


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


Q1

언리얼 GAS에서 ASC에 태그를 부여할 때
SetAndApplyTargetTagChanges
ApplyGameplayEffectSpecToTarget는 각각 무슨 차이가 있어?

후자는
// GameplayEffectAggregator.cpp
FActiveGameplayEffectHandle FActiveGameplayEffectsContainer::ApplyGameplayEffectSpec(
    const FGameplayEffectSpec& Spec)
{
    // ① 태그 조건 검사
    // Owner(Target)가 Blocked 태그 갖고 있으면 차단
    if (Owner->AreAbilityTagsBlocked(Spec.Def->InheritableGrantedTagsContainer.CombinedTags))
        return FActiveGameplayEffectHandle();

    // ② Required 태그 검사
    // Application Required Tags 없으면 차단
    if (!Spec.Def->ApplicationTagRequirements.RequirementsMet(
        Owner->GetOwnedGameplayTags()))
        return FActiveGameplayEffectHandle();

    // ③ FActiveGameplayEffect 생성 (런타임 인스턴스)
    FActiveGameplayEffect NewEffect(
        GenerateNewActiveEffectHandle(), Spec, GetWorldTime());

    // ④ 실제 컨테이너에 등록
    GameplayEffects_Internal.Add(NewEffect);

    // ⑤ Modifier 적용 (어트리뷰트 수치 변경)
    ApplyModifiersToAttribute(Spec);

    // ⑥ 태그 부여
    Owner->AddLooseGameplayTags(
        Spec.Def->InheritableGrantedTagsContainer.CombinedTags);

    // ⑦ Duration 타이머 등록
    if (Spec.Def->DurationPolicy == EGameplayEffectDurationType::HasDuration)
    {
        FTimerDelegate Delegate;
        Delegate.BindUObject(this, 
            &FActiveGameplayEffectsContainer::OnActiveGameplayEffectExpired,
            NewEffect.Handle);
        Owner->GetWorld()->GetTimerManager().SetTimer(
            NewEffect.DurationHandle, Delegate, Spec.Duration, false);
    }

    // ⑧ 복제 (멀티플레이)
    Owner->MarkArrayDirty();

    return NewEffect.Handle;
}
와 같이 위에 명시적인 과정을 거치고 나서 AddLooseGameplayTags를 붙여서 ASC에 태그가 들어오는데

전자는 
FInheritedTagContainer TagContainer;
TagContainer.Added.AddTag(FGameplayTag::RequestGameplayTag(FName("State.Stun")));
TagsComp->SetAndApplyTargetTagChanges(TagContainer);
와 같이 직접 태그 컨테이너를 넣어서 조정이 가능하거든? 공식적인 루트는 후자인 ApplyGameplayEffectSpecToTarget를 사용하는 것 같은데
SetAndApplyTargetTagChanges로 태그를 넣는 방식도 존재해서 둘이 무슨 차이인지 알려줘

전자 방식인 SetAndApplyTargetTagChanges는 내부 소스코드를 봐도

#endif // WITH_EDITOR

void UTargetTagsGameplayEffectComponent::SetAndApplyTargetTagChanges(const FInheritedTagContainer& TagContainerMods)
{
    InheritableGrantedTagsContainer = TagContainerMods;

    // Try to find the parent and update the inherited tags
    const UTargetTagsGameplayEffectComponent* Parent = FindParentComponent(*this);
    InheritableGrantedTagsContainer.UpdateInheritedTagProperties(Parent ? &Parent->InheritableGrantedTagsContainer : nullptr);

    // Apply to the owning Gameplay Effect Component
    ApplyTargetTagChanges();
}

void UTargetTagsGameplayEffectComponent::ApplyTargetTagChanges() const
{
    UGameplayEffect* Owner = GetOwner();
    InheritableGrantedTagsContainer.ApplyTo(Owner->CachedGrantedTags);
}

#undef LOCTEXT_NAMESPACE

어디서 어떻게 태그가 ASC로 들어가는지 알기 힘들어.

최종적으로 다음의 2개의 질문을 답해
1. 왜 ASC에 태그를 붙이는 방식이 2가지인가? 무엇을 사용해야하고, 전자 방식을 사용한다면 무슨 이점을 얻을 수 있는가?
2. 전자 방식을 사용한다면 어느 코드에서 구체적으로 ASC에 태그를 넣는가? 이렇게 넣은 태그는 영구적인가? 아니면 후자의 방식 같이 GE 적용이 끝나면 사라지는가?

A1

핵심 구분

먼저 두 함수가 작동하는 레이어 자체가 다르다.

  SetAndApplyTargetTagChanges ApplyGameplayEffectSpecToTarget
레이어 GE 에셋 정의 수정 GE 런타임 인스턴스 적용
대상 UGameplayEffect CDO/에셋 ASCFActiveGameplayEffectsContainer
시점 에셋 로드 후, 적용 전 실제 전투/게임플레이 중
영속성 에셋에 반영 → 이후 모든 적용에 영향 Duration/Instant/Infinite 정책에 따라 결정

Q1. 왜 2가지인가? 무엇을 써야 하는가?

SetAndApplyTargetTagChanges는 GE 에셋의 "부여할 태그 목록"을 런타임에 변경하는 함수다.
ASC에 태그를 직접 넣는 게 아니라, GE가 "나중에 적용될 때 어떤 태그를 줄지"를 결정하는 GE 에셋 자체를 수정한다.

소스 코드를 다시 보면:

cpp

void UTargetTagsGameplayEffectComponent::ApplyTargetTagChanges() const {
    UGameplayEffect* Owner = GetOwner();
    InheritableGrantedTagsContainer.ApplyTo(Owner->CachedGrantedTags); // ← GE 에셋의 CachedGrantedTags 갱신
}

CachedGrantedTagsGE 에셋이 들고 있는 "줄 태그 목록" 캐시다.
이 시점에선 ASC에 아무것도 들어가지 않는다.

이 방식을 쓰는 이유와 이점:

  • 데이터 드리븐 GE 커스터마이징: 같은 GE 블루프린트를 재사용하면서 부여 태그만 C++/BP로 런타임 교체
  • GE 파생(Inheritance) 시스템 활용: UpdateInheritedTagProperties로 부모 GE Component의 태그를 상속받아 오버라이드 가능 — 에셋 중복 없이 태그 변형
  • 에디터 워크플로우: WITH_EDITOR 분기가 있는 이유가 이것 — 에디터에서 GE를 편집할 때 태그 미리보기/검증용으로도 동작

결론: 일반적인 게임플레이 태그 부여는 ApplyGameplayEffectSpecToTarget가 공식 루트.
SetAndApplyTargetTagChanges는 "이 GE가 앞으로 줄 태그를 프로그래밍적으로 바꾸고 싶을 때" — 즉 GE 에셋을 동적으로 구성하는 도구다.


Q2. 전자 방식에서 ASC에 실제로 태그가 들어가는 시점과 영속성

SetAndApplyTargetTagChangesApplyTargetTagChangesOwner->CachedGrantedTags 갱신까지는 GE 에셋 레이어다.

실제 ASC 진입은 이후 GE가 적용될 때 일반 경로를 그대로 탄다:

SetAndApplyTargetTagChanges()
    └─ Owner->CachedGrantedTags 갱신 (GE 에셋)
           ↓ (이후 별도로)
ASC->ApplyGameplayEffectSpecToSelf(Spec)
    └─ FActiveGameplayEffectsContainer::ApplyGameplayEffectSpec()
           └─ Owner->AddLooseGameplayTags(
                  Spec.Def->CachedGrantedTags  ← 여기서 수정된 값이 사용됨
              )

CachedGrantedTags를 바꿔놓고, GE를 ASC에 Apply하는 순간 AddLooseGameplayTags를 통해 ASC에 들어간다.

영속성은 GE의 Duration Policy가 결정한다SetAndApplyTargetTagChanges가 아니라.

Duration Policy 태그 수명
Instant 태그 부여 자체가 의미없음 (즉시 제거)
HasDuration Duration 만료 시 RemoveLooseGameplayTags 호출로 제거
Infinite GE가 명시적으로 제거될 때까지 유지

따라서 SetAndApplyTargetTagChanges로 넣은 태그가 영구적인지 여부는 그 GE의 Duration Policy에 달려 있다. 전자 방식이라서 특별히 영구적이거나 임시적인 게 아니다.


요약

SetAndApplyTargetTagChanges
= "이 GE가 줄 태그 목록을 런타임에 바꾼다" (GE 에셋 조작)

ApplyGameplayEffectSpecToTarget  
= "이 GE를 지금 대상 ASC에 적용한다" (런타임 인스턴스 생성)

ASC에 태그가 실제로 들어가는 건 항상 후자의 경로.
전자는 후자가 참조할 CachedGrantedTags를 미리 세팅하는 것.
  • GAS는 수치/상태 변화 + 행동 중에 캔슬/금지/쿨타임 등 필요하면 검토

4. 관련 문서 (Links)


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

TIL260615 - Ch3 - 1 - 표정, 메테리얼 슬롯, 양면 렌더  (0) 2026.06.15
TIL260612 - Unreal  (0) 2026.06.12
TIL260610 - Unreal - GAS3  (0) 2026.06.10
TIL260609 - Unreal - GAS2  (0) 2026.06.09
TIL260608 - Unreal  (0) 2026.06.08