Sparta Unreal 부트캠프 123일차

정찬호·2026년 5월 28일

[TIL] Enemy 상태 관리 — GAS 태그 캐싱 · ABP Property Access · World Partition 대응

Today I Learned | Unreal Engine 5 | GAS | Animation Blueprint | World Partition | C++


GAS 태그 이벤트 → 캐시 bool → ABP 읽기 흐름

AnimBlueprint Worker Thread는 GAS ASC에 직접 접근할 수 없다.
C++에서 태그 변경을 감지 → bool 캐싱 → ThreadSafe getter 노출 순서로 연결한다.

[GAS Tag 부여/해제]
    ↓
RegisterGameplayTagEvent → OnXxxTagChanged (game thread)
    ↓
bCachedXxx = (Count > 0)
    ↓
BlueprintThreadSafe getter → ABP Property Access (worker thread)

RegisterGameplayTagEvent

// 생성자에서 등록 (ASC가 막 생성된 직후 — 안전)
if (IsValid(OwnedASC))
{
    OwnedASC->RegisterGameplayTagEvent(
        RetrieveGameplayTags::State_Enemy_Dead,
        EGameplayTagEventType::NewOrRemoved)
    .AddUObject(this, &ARetrieveEnemyCharacter::OnDeadTagChanged);
}

// 콜백: Count > 0이면 태그 보유
void ARetrieveEnemyCharacter::OnDeadTagChanged(const FGameplayTag Tag, int32 Count)
{
    bCachedIsDead = Count > 0;
}
  • 반환값은 FOnGameplayTagCountChangedSignature& (델리게이트 레퍼런스)
  • AddUObject로 멤버 함수 바인딩 → GC 안전
  • Count는 태그를 부여한 GE 누적 개수 — bool로 변환해 캐싱한다

BlueprintThreadSafe getter

// ❌ 잘못된 작성 (BlueprintThreadSafe는 UFUNCTION 직접 지정자가 아님)
UFUNCTION(BlueprintPure, BlueprintThreadSafe, Category="Animation")
bool IsDeadForAnim() const;

// ✅ 올바른 작성 (meta 안에 넣어야 함)
UFUNCTION(BlueprintPure, Category="Retrieve|Enemy|Animation", meta=(BlueprintThreadSafe))
bool IsDeadForAnim() const { return bCachedIsDead; }

ABP Property Access — 핀 레벨 바인딩

항목❌ 잘못된 이해✅ 올바른 동작
바인딩 위치Details 패널에서 변수에 함수 지정AnimGraph 노드의 입력 핀에 직접 연결
중간 bool 변수노드마다 bool 변수 추가 필요불필요 — 핀에 직접 Property Access 연결
Update Animation매 틱 값 갱신 로직 작성비워둬야 함
캐릭터 참조여러 변수에 분산Initialize Animation에서 단 하나만 캐싱
Event Blueprint Initialize Animation
    └→ TryGetPawnOwner → Cast → Set OwnerEnemy  (1회, game thread)

Event Blueprint Update Animation
    └→ (비워둠)  ← Property Access가 worker thread에서 직접 읽음

Enemy 비활성화 패턴 — World Partition 대응

문제

World Partition 레벨에서 Enemy를 풀링(비활성화)한 뒤
지형이 미로딩된 구역에 스폰하면 무한 낙하가 발생한다.

SetActorHiddenInGame + SetActorEnableCollision(false)만으로는
CharacterMovementComponent의 중력·이동 모드가 살아있어 물리가 계속 작동한다.

DeactivateEnemy() — 비활성화

void ARetrieveEnemyCharacter::DeactivateEnemy()
{
    SetActorHiddenInGame(true);
    SetActorEnableCollision(false);

    if (UCharacterMovementComponent* MoveComp =
        Cast<UCharacterMovementComponent>(GetMovementComponent()))
    {
        MoveComp->GravityScale = 0.0f;
        MoveComp->SetMovementMode(EMovementMode::MOVE_None);
    }

    if (AEnemyAIController* AI = Cast<AEnemyAIController>(GetController()))
    {
        AI->Deactivate();
    }
}

ActivateEnemy() — 복구

하드코딩(1.f) 대신 BeginPlay에서 초기값을 캡처해 원복한다.

// BeginPlay: 초기값 저장
if (UCharacterMovementComponent* MoveComp = ...)
{
    DefaultGravityScale = MoveComp->GravityScale;
    DefaultMovementMode = MoveComp->MovementMode;
}

// ActivateEnemy: 원복
MoveComp->GravityScale = DefaultGravityScale;
MoveComp->SetMovementMode(DefaultMovementMode);

호출 일원화

호출 위치변경 전변경 후
HandleDeathEndedinline 3단계DeactivateEnemy()
SpawnerBase::DespawnAllinline 3단계Enemy->DeactivateEnemy()

핵심 요약

  • RegisterGameplayTagEvent는 생성자에서 등록해도 안전하다 (ASC 막 생성 직후).
  • BlueprintThreadSafemeta=(BlueprintThreadSafe) — UFUNCTION 직접 지정자가 아니다.
  • Property Access는 에 연결한다. Update Animation은 비워야 한다.
  • SetActorHiddenInGame만으로는 물리가 멈추지 않는다 — GravityScale=0 + MOVE_None 필수.
  • 비활성화 로직은 DeactivateEnemy() 하나로 통합해 호출 측 중복을 막는다.

Retrieve Project — ABP_EnemyBase 구현 + World Partition 낙하 버그(A4-d) 수정 중 습득 (2026-05-28)

profile
게임 개발 지망생입니다.

0개의 댓글