Unreal Engine 네트워크 Replication 시행착오
오늘 학습한 내용
오늘은 LevelManager를 멀티플레이 환경에서 서버와 클라이언트가 동일하게 사용하도록 만드는 과정에서 Replication과 관련된 문제를 직접 확인하고 해결했다.
처음부터 완성된 구조를 만든 것이 아니라,
서버에서는 정상적으로 작동하는데 클라이언트에서는 왜 안 되는가?
를 로그를 하나씩 확인하면서 원인을 찾아갔다.
1. 서버와 클라이언트에서 Zone 배치가 달라지는 문제
현재 게임에서는 여러 개의 Middle Zone을 가지고 있고, 게임이 시작될 때 서버에서 Zone의 순서를 랜덤하게 섞도록 구현했다.
예를 들어 Middle Zone이:
0 1 2 3 4 5
라면 서버에서 Shuffle한 결과가:
0 5 1 4 3 2
처럼 만들어진다.
서버에서는 이 순서를 기준으로 Zone을 배치하고 있었다.
그런데 멀티플레이로 실행해 보니 서버와 클라이언트의 Zone 배치가 서로 달라지는 문제가 발생했다.
처음에는 단순히 Zone 배치 코드 자체의 문제일 수도 있다고 생각했지만, 실제로는 서버가 결정한 Shuffle 결과를 클라이언트가 제대로 전달받지 못하고 있는 문제였다.
2. 클라이언트에서 랜덤을 다시 실행하면 안 된다는 점
처음 문제를 해결하는 과정에서 중요한 부분을 확인했다.
서버와 클라이언트가 각각 랜덤을 실행하면 안 된다.
예를 들어 서버에서:
Server
0 5 1 4 3 2
가 나왔다고 해서 클라이언트에서도 똑같이 랜덤을 실행하면:
Client
2 4 0 5 1 3
처럼 다른 결과가 나올 수 있다.
그러면 서버가 생각하는 맵과 클라이언트가 생각하는 맵이 달라진다.
따라서 구조를:
서버
↓
Zone 순서 결정
↓
결과를 저장
↓
클라이언트에게 전달
↓
클라이언트는 전달받은 결과를 사용
하는 방식으로 변경했다.
이번 과정에서 멀티플레이에서는 랜덤 자체보다 랜덤의 결과를 누가 결정하고 공유하느냐가 중요하다는 것을 알게 되었다.
3. MiddleZoneOrder를 Replication하도록 변경
서버에서 결정한 Zone 순서를 클라이언트에게 전달하기 위해 MiddleZoneOrder를 Replicated Property로 만들었다.
UPROPERTY(ReplicatedUsing = OnRep_MiddleZoneOrder)
TArray<int32> MiddleZoneOrder;
그리고 GetLifetimeReplicatedProps()에도 등록했다.
DOREPLIFETIME(ALevelManager, MiddleZoneOrder);
이렇게 해서 서버가 만든:
0 5 1 4 3 2
라는 순서를 클라이언트에게 전달하는 구조를 만들었다.
또한 클라이언트에서는 이 값이 전달되었을 때:
OnRep_MiddleZoneOrder()
가 실행되고 ArrangePlacedZones()를 호출하도록 했다.
즉 최종적으로:
Server
MiddleZoneOrder 생성
↓
Replication
↓
Client
MiddleZoneOrder 수신
↓
OnRep_MiddleZoneOrder()
↓
ArrangePlacedZones()
구조를 만들었다.
4. 그런데 Replication을 설정했는데도 클라이언트에서 OnRep가 호출되지 않았다
여기서 또 문제가 발생했다.
서버 로그를 확인했을 때는:
[Server] LevelManager BeginPlay
[Server] MiddleZoneOrder Num: 6
[Server] MiddleZoneOrder[0] = 0
[Server] MiddleZoneOrder[1] = 5
[Server] MiddleZoneOrder[2] = 1
[Server] MiddleZoneOrder[3] = 4
[Server] MiddleZoneOrder[4] = 3
[Server] MiddleZoneOrder[5] = 2
정상적으로 출력되었다.
즉 서버에서는 Shuffle도 정상적으로 이루어지고 있었다.
그런데 클라이언트에서는:
[Client] LevelManager BeginPlay
만 출력되고:
[Client] OnRep_MiddleZoneOrder
가 출력되지 않았다.
이것을 보고 MiddleZoneOrder 자체가 클라이언트까지 제대로 전달되지 않고 있다는 것을 확인했다.
5. 처음에는 ForceNetUpdate()를 의심했다
문제를 해결하는 과정에서 서버가 MiddleZoneOrder를 변경한 직후 네트워크 업데이트를 강제로 요청하도록:
ForceNetUpdate();
를 추가해 보기도 했다.
하지만 이것이 이번 문제의 핵심적인 해결 방법은 아니었다.
결국 중요한 것은 해당 LevelManager Actor 자체가 클라이언트에게 제대로 Relevant한 상태인지였다.
6. 최종적으로 Always Relevant가 문제의 원인이라는 것을 확인했다
LevelManager에:
bReplicates = true;
bAlwaysRelevant = true;
를 설정했다.
그리고 다시 실행하자 클라이언트에서도 서버가 만든 MiddleZoneOrder를 정상적으로 전달받기 시작했다.
즉 이번 문제는 단순히:
변수를 Replicated로 만들지 않았다
가 아니라,
변수는 Replicated로 만들어져 있었지만
그 변수를 가지고 있는 LevelManager Actor가
클라이언트에게 항상 필요한 Actor로 제대로 취급되지 않는 문제
였던 것이다.
7. 이번 문제를 통해 Relevancy를 이해하게 되었다
이번 일을 겪기 전에는 bReplicates = true이면 그냥 서버의 Actor가 클라이언트에게 전부 전달되는 것이라고 생각하기 쉬웠다.
하지만 실제로는 Replication과 Relevancy가 함께 고려된다.
즉:
bReplicates = true
라고 해서 무조건 모든 상황에서 모든 클라이언트에게 계속 전달되는 것이 아니다.
Unreal Engine은 네트워크 최적화를 위해 해당 클라이언트에게 Actor가 필요한지 판단한다.
그런데 LevelManager는 플레이어 주변에 있는 특정 오브젝트가 아니라 게임 전체의 Zone을 관리하는 관리자 Actor이다.
따라서 이 Actor는 특정 위치에 있느냐와 관계없이 클라이언트에게 필요하다.
그래서:
bAlwaysRelevant = true;
를 사용하는 것이 현재 LevelManager의 역할에 맞았다.
8. 가장 중요한 시행착오
오늘 가장 큰 시행착오는 다음과 같다.
문제 1
서버와 클라이언트의 Zone 배치가 다름.
↓
확인
서버의 Shuffle 결과는 정상.
↓
문제 발견
클라이언트가 서버의 Shuffle 결과를 받지 못함.
↓
수정
MiddleZoneOrder를 Replication하도록 변경.
↓
그런데도 문제 발생
OnRep_MiddleZoneOrder()가 클라이언트에서 호출되지 않음.
↓
추가 확인
LevelManager Actor 자체의 네트워크 관련 설정을 확인.
↓
최종 해결
bReplicates = true;
bAlwaysRelevant = true;
설정.
↓
결과
클라이언트가 MiddleZoneOrder를 정상적으로 전달받고 OnRep_MiddleZoneOrder()를 실행.
↓
최종적으로
서버가 결정한 Zone 순서를 클라이언트가 동일하게 사용하여 Zone을 배치할 수 있게 됨.
9. 오늘 가장 크게 배운 점
오늘은 단순히 Replicated나 OnRep의 문법을 배운 것보다 네트워크 문제가 발생했을 때 어디부터 확인해야 하는지를 경험한 것이 가장 중요했다.
처음에는:
"서버에서 잘 되는데 클라이언트에서 안 된다."
라는 결과만 보였다.
하지만 로그를 통해 하나씩 확인하면서:
서버에서 Shuffle이 제대로 되는가?
↓
Yes
MiddleZoneOrder가 제대로 생성되는가?
↓
Yes
클라이언트에서 OnRep가 호출되는가?
↓
No
그렇다면 데이터가 전달되지 않는 이유는?
↓
LevelManager의 네트워크 관련 설정 확인
Always Relevant 활성화
↓
해결
이라는 식으로 문제의 범위를 좁혀갔다.
이번 경험을 통해 멀티플레이 문제를 해결할 때는 단순히 코드를 수정하기보다 서버 → 복제 → 클라이언트의 각 단계에서 어디까지 정상적으로 진행되고 있는지 로그로 확인하는 것이 중요하다는 것을 배웠다.
오늘의 핵심 정리
이번 문제를 한 문장으로 정리하면:
서버에서 결정한 Zone Shuffle 결과를 클라이언트에게 복제하려 했지만 LevelManager의 Relevancy 문제로 제대로 전달되지 않았고, Always Relevant를 적용하여 해결했다.
그리고 앞으로 비슷한 문제가 발생했을 때는 다음 순서로 확인해야겠다.
1. 서버에서 값이 제대로 만들어지는가?
↓
2. 해당 Actor가 Replicates 상태인가?
↓
3. Replicated Property로 등록되어 있는가?
↓
4. GetLifetimeReplicatedProps()에 등록되어 있는가?
↓
5. 클라이언트에서 OnRep가 호출되는가?
↓
6. Actor의 Relevancy에 문제가 없는가?
↓
7. 필요한 Actor라면 Always Relevant가 적절한가?
오늘은 "서버에서 정상적으로 동작한다"와 "클라이언트에서도 정상적으로 동작한다"는 완전히 다른 문제라는 것을 직접 경험했다.
'내일배움캠프_언리얼9기' 카테고리의 다른 글
| Unreal Engine의 Foliage와 HISM, 그리고 자료구조·알고리즘의 연관성 (0) | 2026.09.03 |
|---|---|
| 내일배움캠프 72일차 TIL 2026/08/04 (0) | 2026.08.04 |
| 내일배움캠프 71일차 TIL 2026/08/03 (0) | 2026.08.03 |
| 내일배움캠프 70일차 TIL 2026/07/31 (0) | 2026.07.31 |
| 내일배움캠프 69일차 TIL 2026/07/30 (0) | 2026.07.30 |