Post

2026-07-02 TIL (89일차)

2026-07-02 TIL (89일차)

[TIL] 언리얼 네트워크 관련성(Relevancy) 이슈와 git mv 활용법

1. 트러블슈팅: 공유 캐릭터가 클라이언트에서 굳어버리는 현상

증상 (Symptom)

  • 멀티플레이 테스트 중, 디버그 로그를 보면 플레이어의 키 입력(MoveLeft, MoveRight 등)은 서버로 정상적으로 전달되고 있음.
  • 하지만 클라이언트 화면에서는 캐릭터가 전혀 움직이지 않음.
  • 클라이언트 측 캐릭터의 CurrentLane 변수 값이 초기값(1)에서 영원히 업데이트되지 않는 상태.

원인 분석: 네트워크 관련성(Network Relevancy)의 함정

결론부터 말하자면, 서버가 대역폭을 아끼기 위해 “이 클라이언트한테는 저 캐릭터의 정보를 안 보내도 되겠다!”라고 자체 판단하여 복제(Replication)를 끊어버렸기 때문입니다.

언리얼 엔진은 최적화를 위해 매 틱마다 액터의 ‘네트워크 관련성’을 계산합니다.

  • 기본 판정 기준: 클라이언트의 시점(보통 조종하는 폰) 위치에서 액터까지의 거리가 NetCullDistanceSquared (기본 약 150m) 이내인가?

** 우리의 특수한 프로젝트 구조가 만든 충돌:** 우리 게임은 4명이 1개의 캐릭터를 조종하는 비대칭 협동 게임이므로, 각 플레이어마다 캐릭터가 스폰되는 것을 막기 위해 게임모드에서 DefaultPawnClass = nullptr로 설정했습니다.

  1. 플레이어에게 폰(Pawn)이 없습니다.
  2. 폰이 없으니 서버는 클라이언트의 ‘시점(Viewer Position)’을 맵의 원점(0,0,0)이나 임의의 스폰 지점으로 잡게 됩니다.
  3. 공유 캐릭터가 그 임의의 기준점에서 150m 이상 멀어지는 순간, 서버는 “아, 이 클라이언트는 저 캐릭터랑 멀리 떨어져 있으니 데이터 안 보내야지” 하고 뚝 끊어버린 것입니다. (그래서 액터 자체는 보이지만 변수 업데이트가 안 된 것!)

DefaultPawnClass에 캐릭터를 넣으면 안 될까?

만약 이 거리를 좁히기 위해 DefaultPawnClass에 공유 캐릭터 클래스를 넣으면 어떻게 될까요?

  • 4명이 접속하면 각자의 화면에 자기만의 캐릭터가 1개씩, 총 4마리의 캐릭터가 스폰되어 버립니다.
  • “4명이 1개를 나눈다”는 게임의 핵심 기획이 완전히 망가지게 됩니다. 따라서 DefaultPawnClass = nullptr 설정은 유지하는 것이 맞습니다.

해결책: bAlwaysRelevant = true

이 문제를 해결하는 가장 완벽하고 설계상 올바른 방법은 공유 캐릭터 클래스에 항상 관련성 있음(Always Relevant) 옵션을 켜주는 것입니다.

1
2
3
4
5
ASharedRailCharacter::ASharedRailCharacter()
{
    bReplicates = true;
    bAlwaysRelevant = true;  // 이 액터는 거리에 상관없이 모든 클라이언트에 항상 복제됨
}

왜 이게 정답인가? 우리 게임의 공유 캐릭터는 맵 구석에 있는 잡동사니가 아니라, 모든 플레이어가 항상 보고 상호작용해야 하는 게임의 유일한 중심 액터입니다. 월드에 단 1개뿐이므로 대역폭 낭비도 없습니다. GameState가 모든 클라이언트에게 무조건 복제되는 것과 완벽히 같은 이치입니다.

2. 번외: 파일 이름 변경과 이동의 정석, git mv

프로젝트를 진행하다 보면 C++ 클래스 이름이나 폴더 구조를 바꿔야 할 때가 있습니다. 이때 윈도우 탐색기에서 그냥 이름을 바꾸면 Git이 혼란스러워합니다. 이때 사용하는 것이 git mv입니다.

git mv란 무엇인가요?

Git 저장소 내에서 파일이나 디렉토리의 이름을 바꾸거나 위치를 이동시킬 때 사용하는 Git 전용 명령어입니다.

일반 이동 vs Git 이동

  • 윈도우 탐색기에서 그냥 파일명을 바꿨을 때: Git은 이를 2개의 작업으로 인식합니다. “어? 기존 파일이 삭제(Deleted)됐고, 처음 보는 새 파일이 추가(Untracked)됐네?” (이렇게 되면 파일의 기존 수정 기록(History)이 끊어질 위험이 있습니다.)
  • git mv 명령어를 사용했을 때: Git에게 명확하게 “이 파일 이름 바꾼 거야(Renamed)”라고 알려줍니다. Git은 기존 파일의 히스토리를 그대로 유지한 채로 이동/변경 사항을 자동으로 Staging Area에 올려줍니다.

** 요약:** Git으로 관리되는 프로젝트에서 파일을 옮기거나 이름을 바꿀 때는 윈도우 우클릭 메뉴가 아니라, 터미널에서 git mv를 사용하는 습관을 들이면 히스토리가 꼬이는 것을 완벽하게 방지할 수 있습니다!


git mv 활용법

왜 PR을 보낼 때 git mv가 필수일까?

만약 탐색기에서 파일 이름을 바꾸고 PR을 올린다면, 원본 저장소 관리자(리뷰어)가 보게 될 화면은 끔찍합니다.

  • 일반 변경 (탐색기): A.cpp 파일이 통째로 삭제(빨간색 수백 줄) 처리되고, B.cpp 파일이 통째로 추가(초록색 수백 줄)된 것으로 보입니다. 리뷰어는 코드가 어떻게 수정되었는지 알아볼 수 없습니다.
  • git mv 사용 시: 깃허브(GitHub) PR 화면에 깔끔하게 A.cpp → B.cpp (Renamed) 라고 표시되며, 오직 ‘파일 내용이 수정된 부분’만 하이라이트 되어 보여집니다. 코드 리뷰가 압도적으로 편해집니다.

💻 Fork 환경에서의 작업 순서 (Workflow)

1. 내 포크 저장소 클론 및 브랜치 생성

1
2
3
git clone [https://github.com/내계정/포크한저장소.git](https://github.com/내계정/포크한저장소.git)
cd 포크한저장소
git checkout -b feature/rename-classes

2. git mv를 이용한 파일 이름/경로 변경

1
2
3
4
5
# 파일명 변경
git mv OldClassName.cpp NewClassName.cpp

# 폴더 이동
git mv OldClassName.h ./Includes/

3. 커밋(Commit) 및 푸시(Push)

1
2
3
4
5
# git mv를 쓰면 이미 Staging Area에 올라가 있으므로 바로 커밋 가능
git commit -m "Refactor: Rename OldClassName to NewClassName"

# 내 포크(Origin) 저장소로 푸시
git push origin feature/rename-classes

4. Pull Request (PR) 생성

  • 이제 GitHub에 접속하여 내 포크(Fork) 저장소에서 원본(Upstream) 저장소로 Pull Request를 생성합니다.
  • 리뷰어는 “아, 기존 히스토리를 유지한 채 파일 이름만 깔끔하게 바꿨구나!” 하고 칭찬할 것입니다.
This post is licensed under CC BY 4.0 by the author.