[트러블슈팅] Gameplay Cue OnRemove에서 CastChecked 크래시

김세희·2026년 4월 7일

DungeonZero

목록 보기
2/3

분류: Unreal Engine 5.7 / GAS / Gameplay Cue
발생 시점: 액터 파괴(Destroy) 시
증상: ApplyOriginalMaterial 내부에서 크래시 발생, 로그상 "MyTarget is null"처럼 보이지만 실제 원인은 다른 곳


문제 상황

레벨을 정리할 때 어노말리 액터를 파괴하는 로직

AGameplayCueNotify_Actor 기반의 머티리얼 교체 Cue를 구현하였다.
OnRemove에서 MyTargetCachedMaterialCueData 유효성을 모두 체크한 뒤 원본 머티리얼 복원 함수를 호출했음에도, 액터가 파괴될 때 크래시가 발생하였다.

bool ADZGCN_AnomalyChangeMaterial::OnRemove_Implementation(
    AActor* MyTarget, const FGameplayCueParameters& Parameters)
{
    if (!MyTarget) return false;                     // ← 통과
    if (CachedMaterialCueData.Num() == 0) return false; // ← 통과

    for (auto& MaterialData : CachedMaterialCueData)
    {
        if (IsValid(MyTarget))
            ApplyOriginalMaterial(MyTarget, MaterialData); // ← 여기서 크래시
    }
    return true;
}
void ADZGCN_AnomalyChangeMaterial::ApplyOriginalMaterial(
    AActor* MyTarget, FDZMaterialCueData& CueData)
{
    UStaticMeshComponent* TargetMesh = CastChecked<UStaticMeshComponent>(
        MyTarget->FindComponentByTag(..., CueData.TargetMeshTag)); // ← 실제 크래시 지점
    ...
}

원인 분석

OnRemove에서의 IsValid(MyTarget) 체크는 통과하였다.
그러나 액터가 파괴 진행 중(BeginDestroy 이후) 상태일 경우, 액터 자체는 아직 유효한 포인터를 가지고 있더라도 내부 컴포넌트는 이미 정리된 상태일 수 있다.

이 상황에서 FindComponentByTagnullptr을 반환하고, 이 nullptrCastChecked에 넘기면 즉시 assert 크래시가 발생한다.

OnRemove 진입
    ↓
IsValid(MyTarget) → true  (액터 포인터는 아직 유효)
    ↓
FindComponentByTag → nullptr  (컴포넌트는 이미 정리됨)
    ↓
CastChecked<UStaticMeshComponent>(nullptr) → 💥 크래시

CastChecked는 내부적으로 입력값이 절대 null이 아님을 전제로 동작한다.
외부 조회 결과처럼 null 가능성이 있는 값에는 사용하면 안 된다.


해결 방법

CastCheckedCast 로 교체하고, 결과에 대해 명시적인 IsValid 체크를 추가하였다.

void ADZGCN_AnomalyChangeMaterial::ApplyOriginalMaterial(
    AActor* MyTarget, FDZMaterialCueData& CueData)
{
    UStaticMeshComponent* TargetMesh = Cast<UStaticMeshComponent>(
        MyTarget->FindComponentByTag(UStaticMeshComponent::StaticClass(), CueData.TargetMeshTag));

    if (!IsValid(TargetMesh))
    {
        UE_LOG(LogTemp, Warning,
            TEXT("ApplyOriginalMaterial: TargetMesh not found. Tag: %s"),
            *CueData.TargetMeshTag.ToString());
        return;
    }

    for (auto& SlotOverride : CueData.SlotOverrides)
    {
        TargetMesh->SetMaterial(SlotOverride.SlotIndex, SlotOverride.OriginalMaterial);
    }
}

OnRemove에서도 !MyTarget 대신 !IsValid(MyTarget)으로 교체하고, 중복 체크를 제거하였다.

bool ADZGCN_AnomalyChangeMaterial::OnRemove_Implementation(
    AActor* MyTarget, const FGameplayCueParameters& Parameters)
{
    if (!IsValid(MyTarget)) return false;
    if (CachedMaterialCueData.Num() == 0) return false;

    for (auto& MaterialData : CachedMaterialCueData)
    {
        ApplyOriginalMaterial(MyTarget, MaterialData);
    }
    return true;
}

핵심 교훈

구분내용
CastChecked입력이 절대 null이 아님을 개발자가 보장할 수 있을 때만 사용
Cast + IsValidFindComponentByTag, GetComponentByClass외부 조회 결과에는 반드시 이 조합 사용
!MyTarget vs !IsValid(MyTarget)raw null 체크만으로는 GC 수거 중인 객체를 잡지 못함. IsValid 사용이 안전
액터 파괴 순서OnRemove가 호출되는 시점에 액터 포인터는 유효해도, 내부 컴포넌트는 이미 정리되어 있을 수 있음

0개의 댓글