Unreal 게임 개발 과정 TIL

Today's I Learning

본캠프 TIL

07.23 Unreal 본캠프) 단일 맵 전투 최적화, Enhanced Input 트리거 수정, BGM 크래시 방지

Sunlight__ 2026. 7. 23. 22:54

📌 오늘의 요약

단일 맵 전투 시스템 최적화, 체크포인트 부활 시 발생하는 프레임 멈춤(Lag) 개선, 다수 몬스터 전투 중 레벨 전환 시 발생하는 오디오 크래시 등 총 5가지 핵심 이슈의 원인을 분석하고 C++로 해결했다.

🛠️ 주요 트러블슈팅 및 구현 내용

1. ⚔️ 단일 맵 구조 정돈 & 몬스터 자동 감지 승리 시스템

  • 🔴 문제 상황: 기존 멀티 스테이지 전용 잔여 코드(CurrentStage 등)가 남아있어 불필요한 태그 검사가 실행되고 로직이 꼬이는 현상 발생.
  • 🟢 해결 방법:
    • 쓰이지 않는 스테이지 이동/추적 로직을 C++(J_GameMode.cpp)에서 완전히 제거했다.
    • BeginPlay 시점에 맵에 스폰된 몬스터 수를 자동 스캔(GetAllActorsOfClass)하여 목표 점수(TargetScore)로 자동 지정하도록 수정했다.
    • 몬스터 처치 시 KillCount >= TargetScore 조건을 만족하면 바로 승리 처리(HandleVictory)되도록 구조를 단순화했다.

2. 🦘 Enhanced Input 점프 꾹 누름 시 공중 부양 버그 픽스

  • 🔴 문제 상황: 점프키(Space)를 꾹 누르고 있으면 캐릭터가 바닥으로 떨어지지 않고 계속 점프하여 공중 부양하는 현상 발생.
  • 🔍 원인 분석: 점프 입력 바인딩이 ETriggerEvent::Triggered(매 프레임 호출)로 설정되어 있어, 키를 홀드하는 동안 매 프레임마다 상향 가속도가 지속 주입되었다.
  • 🟢 해결 코드: 입력 이벤트를 Started로 변경하여 첫 프레임 1회만 점프가 실행되도록 수정하고 불필요한 조건문을 제거했다.
// ETriggerEvent::Triggered ➔ ETriggerEvent::Started 로 변경
EnhancedInput->BindAction(PlayerController->JumpAction, ETriggerEvent::Started, this, &AJ_PlayerCharacter::StartJump);

void AJ_PlayerCharacter::StartJump(const FInputActionValue& value)
{
    if (bIsDead || bIsRolling || bIsUsingSkill) return;
    Jump(); // 조건문 없이 1회 즉시 점프
}

3. 🚀 체크포인트 무로딩(In-Memory) 부활 시스템 구현

  • 🔴 문제 상황: 체크포인트 부활 시 UGameplayStatics::OpenLevel을 사용해 맵을 새로고침하면서, 3D 에셋 디스크 동기 읽기 및 메모리 정리(GC)로 인해 약 1초간 화면이 멈추는 프레임 히치(Lag) 발생. 또한 레벨 재로드 후 메인메뉴 UI가 겹치거나 즉시 패배 처리되는 버그가 동반되었다.
  • 🟢 해결 방법 (In-Memory Reset):
    • OpenLevel 대신, 맵 시작 시 몬스터의 초기 스폰 위치(FMonsterSpawnInfo)를 기억해두고 부활 시 메모리 상에서 즉시 재스폰하여 0ms Lag 스무스 부활을 구현했다.
    • J_GameInstance에 bIsGameStarted 플래그를 추가하여 게임 진행 중 부활 시 메인메뉴 UI를 스킵하고 인게임 HUD로 바로 진입하도록 처리했다.
    • 플레이어를 체크포인트 위치로 순간이동시키고 HP(100%), 제한시간, 킬 스코어, 탄약 수치를 완전 복원했다.

4. 💥 레벨 전환/재시작 시 BGM 오디오 크래시(Crash) 방지

  • 🔴 문제 상황: 다수의 적(2명 이상)과 전투 중 체크포인트로 부활하거나 재시작하면 엔진이 튕기는 현상 발생.
  • 🔍 원인 분석: 레벨 재로드 시 몬스터가 순차 소멸하면서 추적 몬스터 수가 0이 되고, 이 순간 BGM을 전투 ➔ 비전투로 전환하려는 시도가 발생했다. 하지만 이미 해체 중인(Tearing Down) 월드의 오디오 컴포넌트 및 타이머 매니저 메모리에 접근(Access Violation)하면서 엔진이 크래시되었다.
  • 🟢 해결 코드: 레벨이 파괴 중이거나 레벨 전환으로 인한 소멸일 경우 BGM 조작을 조기 리턴하도록 방어 로직을 추가했다.
// J_GameMode.cpp - 레벨 해체 중일 때 오디오 조작 시 튕김 방지
void AJ_GameMode::UpdateBGMState()
{
    // 월드가 없거나 레벨 파괴 중일 때는 BGM 조작 조기 리턴!
    if (!GetWorld() || GetWorld()->bIsTearingDown) return; 
    
    // ... BGM 전환 로직 ...
}

// MonsterAIController.cpp - 레벨 전환에 의한 소멸 시 BGM 해제 요청 금지
void AMonsterAIController::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);
    
    if (EndPlayReason == EEndPlayReason::LevelTransition || 
        EndPlayReason == EEndPlayReason::Quit || 
        EndPlayReason == EEndPlayReason::EndPlayInEditor)
    {
        return;
    }
    // ... 추적 몬스터 수 감소 로직 ...
}

 

5. 🔫 초기 탄약 UI 불일치 동기화

  • 🔴 문제 상황: 시작 시 기본 무기가 칼임에도 UI에 탄약이 1 / 20으로 잘못 표시되다가 무기를 스왑해야 정상화되는 문제.
  • 🟢 해결 방법: J_GameMode의 기본 탄약 수량을 5발로 맞추고, CombatComponent::BeginPlay 시점에 현재 장착된 무기가 총(AGunBase*)인지 칼인지 검사하여 시작 시점부터 UI에 Ammo: 0 / 5로 정밀 동기화되도록 수정했다.

💡 오늘의 레슨 (Key Takeaways)

  1. Enhanced Input 트리거 타입 선택: 매 프레임 연속 입력이 필요한 동작(이동, 시점 회전)은 Triggered, 1회성 동작(점프, 구르기, 공격)은 Started를 명확히 구분하여 사용해야 불필요한 연산과 버그를 막을 수 있다.
  2. 레벨 전환 시 메모리 안전성: 액터 소멸(EndPlay)이나 레벨 전환 시에는 GetWorld()->bIsTearingDown 및 EEndPlayReason을 반드시 체크해야 한다. 해체 중인 렌더링/오디오/타이머 시스템에 접근하는 것은 치명적인 메모리 크래시를 유발한다.
  3. OpenLevel vs In-Memory Reset: 씬(Scene) 전환이 필요 없는 단순 캐릭터 부활은 맵을 통째로 다시 읽는 OpenLevel보다 메모리 내에서 상태만 초기화하는 인메모리 리셋 방식이 성능(0ms Lag)과 유저 경험 측면에서 압도적으로 우수하다.