Room 단위로 서브 레벨을 스트리밍하여 맵을 동적으로 구성하는 기능을 구현하는 과정에서, 싱글 플레이와 리슨 서버 환경에서는 정상적으로 동작하지만, 두 번째 클라이언트 접속 시 MissingLevelPackage 오류와 함께 접속이 끊어지는 문제가 발생했습니다. 이 TIL에서는 해당 문제가 발생한 원인과, 언리얼 엔진의 레벨 스트리밍·네트워크 동기화 구조를 정리하여, 앞으로 같은 실수를 반복하지 않기 위한 기록을 남기고자 합니다.
리슨 서버에서 StreamingLevelName = L_StreamingTest2 설정 후 LoadLevelInstance 계열 로직으로 레벨 인스턴스 생성
리슨 서버 단독 플레이에서는 정상적으로 방이 원하는 위치에 생성
두 번째 클라이언트가 접속하면 다음과 같은 로그 출력 후 연결 끊김

ServerUpdateLevelVisibility() ignored non-existant packageMissingLevelPackageYour connection to the host has been lost.결과적으로, 서버 입장에서는 스트리밍이 성공한 것처럼 보이나, 새로 접속한 클라이언트 기준에서는 해당 패키지를 찾지 못해 접속이 강제로 종료되는 상황 발생
ULevelStreamingDynamic / LoadLevelInstance 계열 APILoadLevelInstance 내부 구현MakeUniqueObjectName(..., TEXT("_LevelInstance")) 로 스트리밍 레벨 인스턴스 이름 자동 생성_LevelInstance_135, _LevelInstance_396 같이 접미사 숫자가 내부 카운터 기반으로 붙음LoadLevelInstance를 호출해도L_StreamingTest2_LevelInstance_135L_StreamingTest2_LevelInstance_396..._135를 visibility 업데이트 대상으로 삼지만..._135가 존재하지 않음 → ignored non-existant package 로그 출력ULevelStreamingDynamic 으로 런타임에 새 스트리밍 레벨을 로드하는 행위 자체ServerUpdateLevelVisibility 에서 해당 패키지를 클라에게도 보여주려고 시도MissingLevelPackage 로 연결 종료LoadLevelInstance 는 다음 요소들에 의존하는 비결정적(Non-Deterministic) 특성 존재ULevelStreamingDynamic / LoadLevelInstance 기반 구조는 멀티플레이에서 다음 이유로 부적합MakeUniqueObjectName 특성상 이름이 달라질 수밖에 없고, 호출 순서/개수 차이로 완전히 다른 결과가 나옴이번 문제는 단순히 코드 버그라기보다, 언리얼 엔진의 레벨 스트리밍과 네트워크 동기화가 어떻게 설계되어 있는지 충분히 이해하지 못한 상태에서 ULevelStreamingDynamic을 멀티플레이 환경에 그대로 적용한 데에서 비롯된 것이었습니다. 서버와 클라이언트가 동일한 레벨 인스턴스를 공유한다고 가정하고 구현했으나, 실제로는 인스턴스 이름 생성, 스트리밍 레벨 목록 관리, 패키지 로딩 흐름이 네트워크와 분리되어 있어, 새로 접속한 클라이언트가 동적으로 생성된 레벨을 찾지 못하는 상황이 발생했습니다.
이 TIL을 통해, 멀티플레이에서 레벨을 다루는 방법은 기존 싱글 플레이/에디터 중심 사고방식과는 다르게 접근해야 한다는 점을 다시 한 번 정리하게 되었습니다. 앞으로는 월드 파티션, 고정 스트리밍 레벨, 액터 기반 동적 생성 등 언리얼이 제공하는 네트워크 친화적 구조를 우선적으로 고려하여 설계하고, 특히 "서버가 만든 것을 클라에 그대로 복제한다"는 직관적인 기대 대신, "서버와 클라가 합의된 규칙에 따라 동일한 월드를 재구성한다"는 관점에서 시스템을 구성하고자 합니다.