나만의 작은 도서관
[TIL][C++] 251113 MMO 서버 개발 136일차: [언리얼] BeginPlay()에 대해서…, 에디터 환경 유무에 따른 BeginPlay() 호출 순서 차이, 언리얼은 네트워크 구조가 기본으로 세팅된다, ULocalPlayer, FWorldContext 본문
Today I Learn
[TIL][C++] 251113 MMO 서버 개발 136일차: [언리얼] BeginPlay()에 대해서…, 에디터 환경 유무에 따른 BeginPlay() 호출 순서 차이, 언리얼은 네트워크 구조가 기본으로 세팅된다, ULocalPlayer, FWorldContext
pledge24 2025. 11. 13. 23:44주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다.
[언리얼] BeginPlay()에 대해서…
BeginPlay() 함수란?
- BeginPlay는 액터(AActor)가 월드(UWorld)에서 처음으로 활성화되고 시뮬레이션되기 시작할 때 단 한 번 호출되는 이벤트 함수이다.
BeginPlay()가 호출되는 정확한 시점?
- 하나의 액터를 두고 BeginPlay가 호출되는 시점을 정확하게 표현하자면 아래와 같다.
액터의 모든 초기화 작업이 완료되었으며(부착된 컴포넌트 포함), 월드에 해당 액터가 등록되어 인지할 수 있는 시점.
레벨에 배치된 액터의 BeginPlay(), 게임 도중에 스폰된 액터의 BeginPlay()
- 언리얼에서 이 둘의 BeginPlay() 호출 시점은 조금 다르다.
- 레벨의 배치된 액터의 경우 초기화 작업이 완료되어도 곧바로 BeginPlay()를 호출되지 않고, 레벨이 로드된 이후에 첫 프레임에 호출된다.
- 왜 BeginPlay 호출이 뒤로 밀리 나면, BeginPlay의 Play는 게임이 시작된 상태, 즉, 레벨이 완전히 로드가 된 이후에서만 가능한 상태이기 때문. 액터는 레벨에 종속되기 때문에 본인을 소유한 레벨보다 먼저 Play 상태가 되는 것은 말이 안 된다.
- 반면, 이미 플레이 상태인 게임에서 액터를 SpawnActor와 같은 함수로 스폰했다면, 레벨은 이미 Play상태이기 때문에 엔진은 최대한 빨리 BeginPlay를 호출한다. SpawnActor의 경우 동기 함수이기 때문에, 반환 시점에서 BeginPlay는 이미 호출된 경우가 많다.
- 요약
- 레벨에 배치된 액터는 레벨이 로드된 이후에 호출,
- 게임 도중에 스폰된 액터는 만들어진 즉시 호출
- 요약
렌더링과 BeginPlay()
- 그렇다면 BeginPlay()가 끝난 직후의 시점은 어떨까? 해당 액터가 레벨에서 눈에 보일까?
- 결론부터 말하자면 그렇지 않다. 엔진은 한 프레임(틱)내에서 렌더링은 로직을 다 처리한 뒤 마지막 부분에서 처리되는 작업이기 때문에, BeginPlay()가 막 끝난 시점에서 액터는 눈에 보이지 않는다.
BeginPlay와 Tick 함수의 첫 번째 호출은 같은 틱(프레임)에서 호출되지 않는다.
- BeginPlay()는 액터의 준비 단계를 담당하고, Tick()은 실제 게임 루프의 실행 단계를 담당하므로, BeginPlay()가 먼저 완료된 후에 첫 번째 Tick()이 발생한다.
에디터 환경 유무에 따른 BeginPlay() 호출 순서 차이
- 모든 액터는 BeginPlay() 함수를 가질 수 있다. 그런데 궁금한 점이 있다. 게임 플레이 도중에 스폰된 액터는 그렇다 쳐도, 레벨에 배치된 액터들의 경우 레벨이 로드된 이후 첫 프레임에 동시에 호출되는데 순서는 어떻게 될까?
- 기본적으로 BeginPlay()들끼리의 순서는 딱히 없다고 보는 것이 일반적이다. 누가 먼저 호출되고 누가 나중에 호출되고는 보장하지 않기 때문. 하지만, 이 말은 반은 맞고 반은 틀린 말이다.
특수 액터 타입에 대한 BeginPlay 호출 순서 보장
- 언리얼에서 액터는 모든 게임 오브젝트의 기반이 되는 그릇의 역할을 하기 때문에, 오만가지가 액터로 만들어져 있다. 그래서 월드에서 시스템 역할을 맡는 애들도 전부 액터로 만들어져 있다. 대표적으로 GameMode와 GameMode의 프로퍼티들이 있다.

- GameMode와 GameMode의 프로퍼티들과 같은 특수한 액터들은 BeginPlay의 호출 순서가 보장된다. 그런데 이 호출 순서가 특이하게도 “에디터 환경인가? 릴리즈 된 StandAlone(NetMode의 StandAlone 아님) 프로그램 환경인가?”에 따라 달라진다.
- 각각의 BeginPlay 함수의 호출 순서를 보면 아래와 같다.
에디터 환경
- 레벨에 배치된 Actor
- GameMode
- Level
- GameState
- PlayerController
- PlayerState
- HUD
- Pawn
빌드된 게임 (Standalone 상태)
- Pawn
- GameMode
- GameState
- PlayerState
- PlayerController
- HUD
- Level
- 레벨에 배치된 Actor
왜 다를까?
- 에디터 환경과 StandAlone 환경은 서로 다른 방식으로 레벨에 배치된 액터를 스폰하기 때문. 에 StandAlone의 경우 디스크에서 직접 액터의 데이터를 가져오는 반면, 에디터 환경의 경우 미리 에디터가 들고 있는 액터를 복제해서 스폰한다. 그래서 에디터 환경인 경우 다른 액터들보다(심지어 레벨보다도) 빠르게 BeginPlay가 호출된다.
언리얼은 네트워크 구조가 기본으로 세팅된다.
- NetMode를 StandAlone으로 했다는 것은 네트워크를 사용하지 않는 싱글플레이어 게임을 만들겠다는 건데, 이상하게도 네트워크 관련 객체들이 플레이할 때마다 생기는 것을 볼 수 있다.

- 근데 이게 기본적으로 정상이라고 한다. 뭔가 설정을 안 꺼서 나오나? 싶었는데, 그게 아니라 그냥 기본 구성품처럼 자동으로 생기는 거라고 한다.
- NetMode를 StandAlone으로 하면 이렇게 생성되는 네트워크 전용 객체들은 안에 텅텅 빈 상태로 껍데기만 남아있는다고 한다. 있는 거 자체가 불편하긴 하지만 문제가 되지는 않는다고 하니 뭐…
ULocalPlayer
- 엔진의 플레이어 표현(월드가 아님!). GameInstance가 아래에 존재한다.
- 엔진에서 관리되기 때문에 월드가 파괴되는 것이 ULocalPlayer의 생명주기에 영향을 주지 않는다.
- 보통은 1개만 있다. 한 게임에 동시에 여러 플레이어를 조종하는 구조라면 여러 개 있을 수 있다.

- ULocalPlayer는 UPlayer를 상속받는 클래스.
역할?
- APlayerController 생성 및 관리
- ULocalPlayer는 자신의 APlayerController를 소유하거나 이를 생성한다.
- 네트워크 세션 연결 시, ULocalPlayer 단위로 세션에 참여하게 된다.
- 입력(Input) 관리
- 입력 장치와(키보드, 패드 등)와 플레이어를 매핑
- ULocalPlayer가 여러 개면 각 ULocalPlayer가 별도의 매핑을 하고, 별도의 입력을 받는다.
- 렌더링 뷰(Viewport) 제어
- 각 ULocalPlayer는 FSceneView 또는 UPlayerCameraManager를 통해 자신만의 카메라 시점을 가진다.
- 화면을 나누는 방식(SplitScrren Layout)도 여기서 제어된다.
관계 요약
UGameInstance
├─ ULocalPlayer (플레이어 1)
│ ├─ APlayerController
│ ├─ UPlayerCameraManager
│ └─ 입력 및 뷰 관리
├─ ULocalPlayer (플레이어 2)
│ └─ ...
UGameInstance
└── TArray<ULocalPlayer*> LocalPlayers
└── APlayerController (각 LocalPlayer에 대응)
└── APawn / ACharacter
서버는 각 클라이언트 연결마다 UPlayer 타입을 가지는데, 그냥 가지지는 않고, 하위 클래스인 UNetConnection으로 가지고 있는다.
UPlayer는 직접 사용되지 않고, 두 가지 구체 타입으로 나뉩니다:
- ULocalPlayer: 클라이언트(또는 listen 서버의 로컬 클라이언트)에서 사용
- UNetConnection: 서버에서 원격 플레이어 연결을 표현
WorldContext(FWorldContext)
- 게임 인스턴스에 존재하는 특정 World 인스턴스 및 이와 관련된 모든 콘텍스트 데이터를 관리하는 “구조체”
- 쉽게 말해, 하나의 언리얼 엔진 애플리케이션 (프로그램)이 실행될 때, 여러 개의 독립적인 '세계(World)'를 가질 수 있는데, 이 각각의 세계에 대한 중요한 정보와 상태를 묶어서 관리하는 '주소록' 또는 '콘텍스트'라고 생각하면 된다.
주요 기능 및 역할
- UWorld 인스턴스 관리
- 현재 활성화된 UWordl 인스턴스를 관리한다. ( 포인터 타입으로 가지고 있음 UWorld*)
- 다중 월드 지원
- 게임인스턴스는 활성화된 UWorld는 유일하지만, UWorld 인스턴스 자체는 여러 개를 동시에 가질 수 있다.
- PIE (Play In Editor): 에디터에서 플레이할 때, 각 클라이언트(및 서버)마다 별도의 월드 인스턴스를 가집니다. 각 PIE 세션은 고유한 WorldContext를 가집니다.
- 독립 실행형 게임: 메인 게임 월드가 있고, 때로는 별도의 로딩 화면이나 메뉴용 월드를 가질 수 있습니다.
- 게임인스턴스는 활성화된 UWorld는 유일하지만, UWorld 인스턴스 자체는 여러 개를 동시에 가질 수 있다.
- 콘텍스트 제공 (Mode/Type):
- WorldContext는 해당 월드가 어떤 목적으로 사용되고 있는지에 대한 콘텍스트 정보를 담고 있다. 예를 들어, EWorldContextType 열거형을 통해 이 월드가 게임 월드 (Game)인지, 에디터 유틸리티 (Editor)용인지, PIE 클라이언트 (PIEClient)인지, 아니면 PIE 서버 (PIEServer)인지 등을 알려준다.
- 엔진에 접근 기능
- 엔진의 특정 시스템은 WorldContext를 통해 해당 월드에 해당하는 올바른 콘텍스트를 파악하고 접근한다.
