Today I Learned |
Unreal Engine 5|GAS|Animation Blueprint|World Partition|C++
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)
// 생성자에서 등록 (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는 UFUNCTION 직접 지정자가 아님)
UFUNCTION(BlueprintPure, BlueprintThreadSafe, Category="Animation")
bool IsDeadForAnim() const;
// ✅ 올바른 작성 (meta 안에 넣어야 함)
UFUNCTION(BlueprintPure, Category="Retrieve|Enemy|Animation", meta=(BlueprintThreadSafe))
bool IsDeadForAnim() const { return bCachedIsDead; }
| 항목 | ❌ 잘못된 이해 | ✅ 올바른 동작 |
|---|---|---|
| 바인딩 위치 | 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에서 직접 읽음
World Partition 레벨에서 Enemy를 풀링(비활성화)한 뒤
지형이 미로딩된 구역에 스폰하면 무한 낙하가 발생한다.
SetActorHiddenInGame + SetActorEnableCollision(false)만으로는
CharacterMovementComponent의 중력·이동 모드가 살아있어 물리가 계속 작동한다.
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();
}
}
하드코딩(1.f) 대신 BeginPlay에서 초기값을 캡처해 원복한다.
// BeginPlay: 초기값 저장
if (UCharacterMovementComponent* MoveComp = ...)
{
DefaultGravityScale = MoveComp->GravityScale;
DefaultMovementMode = MoveComp->MovementMode;
}
// ActivateEnemy: 원복
MoveComp->GravityScale = DefaultGravityScale;
MoveComp->SetMovementMode(DefaultMovementMode);
| 호출 위치 | 변경 전 | 변경 후 |
|---|---|---|
HandleDeathEnded | inline 3단계 | DeactivateEnemy() |
SpawnerBase::DespawnAll | inline 3단계 | Enemy->DeactivateEnemy() |
RegisterGameplayTagEvent는 생성자에서 등록해도 안전하다 (ASC 막 생성 직후).BlueprintThreadSafe는meta=(BlueprintThreadSafe)— UFUNCTION 직접 지정자가 아니다.- Property Access는 핀에 연결한다.
Update Animation은 비워야 한다.SetActorHiddenInGame만으로는 물리가 멈추지 않는다 —GravityScale=0+MOVE_None필수.- 비활성화 로직은
DeactivateEnemy()하나로 통합해 호출 측 중복을 막는다.
Retrieve Project — ABP_EnemyBase 구현 + World Partition 낙하 버그(A4-d) 수정 중 습득 (2026-05-28)