2026년 10월 7일 팀원과 소통시간을 가진 후 강의를 들으며 내용정리 중이다.
오늘 배운 내용 한 줄 요약: 서버와 클라이언트가 같은 로그 창에 찍히게 만든 뒤, 접속·시작·빙의 함수가 어느 머신에서 어떤 순서로 도는지 본다.
Play 설정은 Launch Separate Server = false, Run Under One Process = true. 서버와 클라이언트 로그가 한 출력에 모인다.
에디터를 켠 직후 바로 플레이하면 CDO 생성자 로그가 여러 번 찍힌다. 네트워크 로그만 보려면 에디터를 켠 다음 Clear Log하고 플레이한다.
// 넷모드 + 함수 이름
#define DX_LOG_NET(LogCategory, Verbosity, Format, ...) \
UE_LOG(LogCategory, Verbosity, TEXT("[%s] %s %s"), NETMODE_TCHAR, FUNCTION_TCHAR, *FString::Printf(Format, ##__VA_ARGS__))
// 넷모드 + LocalRole/RemoteRole
#define DX_LOG_ROLE(...)
NETMODE_TCHAR는 Client00, Server, StandAlone 중 하나다. PIE 번호가 붙어서 어느 창의 로그인지 구분한다.
생성자에 DX_LOG_NET을 넣고 비교한다.


PostLogin에서 서버 쪽 연결 목록, PostNetInit에서 클라이언트 쪽 서버 연결. 양쪽을 나눠 보는 이유다.

GameMode::StartPlay의 Super를 빼면 모든 액터의 BeginPlay가 안 불린다. 캐릭터도 움직이지 않는다. 확인 뒤에는 주석을 푼다.
클라이언트에는 GameMode가 없다. 그래서 GameMode가 직접 시작하지 않고 GameState에 시작을 맡긴다. GameState는 복제된다.
GameMode::StartPlay
→ GameState::HandleBeginPlay
서버: bReplicatedHasBegunPlay = true
WorldSettings::NotifyBeginPlay() → 서버 액터 BeginPlay
클라이언트: OnRep_ReplicatedHasBegunPlay
(Authority가 아니면) NotifyBeginPlay() → 클라이언트 액터 BeginPlay
NotifyBeginPlay는 월드의 액터를 돌며 DispatchBeginPlay를 호출한다.
시작 전후 함수 순서:
PostInitializeComponents — 붙인 컴포넌트가 준비된 뒤PostNetInit — 서버에서 속성 세팅이 끝나고 클라이언트에 복제된 뒤. 항상 BeginPlay보다 먼저StartPlay — 게임 시작을 지시BeginPlay — 그 신호로 월드의 액터에서 호출PossessedBy가 SetOwner(NewController)를 한다. 이 소유로 어느 클라이언트의 NetConnection에 속하는지가 정해진다. UnPossessed는 Owner, Controller, PlayerState를 비우고 ForceNetUpdate한다.
PlayerController::OnPossess는 서버에서만 돈다. 클라이언트는 Possess()를 호출하지 않는다. Owner는 ReplicatedUsing이라, 클라이언트에서는 OnRep_Owner로 소유자가 들어온다.
빙의 전후에 DX_LOG_ROLE로 Local Role / Remote Role을 찍으면, 캐릭터의 Remote Role이 바뀌는 것을 볼 수 있다.
DX_LOG_NET으로 서버/클라이언트를 같은 로그에서 구분한다.PreLogin의 ErrorMessage. 연결 목록은 PostLogin, 서버 연결은 PostNetInit.bReplicatedHasBegunPlay가 클라이언트 BeginPlay를 연다.OnRep_Owner로 받는다.