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/에셋 |
ASC의 FActiveGameplayEffectsContainer |
| 시점 | 에셋 로드 후, 적용 전 | 실제 전투/게임플레이 중 |
| 영속성 | 에셋에 반영 → 이후 모든 적용에 영향 | Duration/Instant/Infinite 정책에 따라 결정 |
Q1. 왜 2가지인가? 무엇을 써야 하는가?
SetAndApplyTargetTagChanges는 GE 에셋의 "부여할 태그 목록"을 런타임에 변경하는 함수다.
ASC에 태그를 직접 넣는 게 아니라, GE가 "나중에 적용될 때 어떤 태그를 줄지"를 결정하는 GE 에셋 자체를 수정한다.
소스 코드를 다시 보면:
cpp
void UTargetTagsGameplayEffectComponent::ApplyTargetTagChanges() const {
UGameplayEffect* Owner = GetOwner();
InheritableGrantedTagsContainer.ApplyTo(Owner->CachedGrantedTags); // ← GE 에셋의 CachedGrantedTags 갱신
}
CachedGrantedTags는 GE 에셋이 들고 있는 "줄 태그 목록" 캐시다.
이 시점에선 ASC에 아무것도 들어가지 않는다.
이 방식을 쓰는 이유와 이점:
- 데이터 드리븐 GE 커스터마이징: 같은 GE 블루프린트를 재사용하면서 부여 태그만 C++/BP로 런타임 교체
- GE 파생(Inheritance) 시스템 활용:
UpdateInheritedTagProperties로 부모 GE Component의 태그를 상속받아 오버라이드 가능 — 에셋 중복 없이 태그 변형 - 에디터 워크플로우:
WITH_EDITOR분기가 있는 이유가 이것 — 에디터에서 GE를 편집할 때 태그 미리보기/검증용으로도 동작
결론: 일반적인 게임플레이 태그 부여는 ApplyGameplayEffectSpecToTarget가 공식 루트.SetAndApplyTargetTagChanges는 "이 GE가 앞으로 줄 태그를 프로그래밍적으로 바꾸고 싶을 때" — 즉 GE 에셋을 동적으로 구성하는 도구다.
Q2. 전자 방식에서 ASC에 실제로 태그가 들어가는 시점과 영속성
SetAndApplyTargetTagChanges → ApplyTargetTagChanges → Owner->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)
- [(연구) SDF 텍스처는 직접 그려서 쓸 수 있을까? - 3DCG 채널](https://arca.live/b/3d3d/141290850
'내일배움캠프 > 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 |