[TIL] 2026-06-23 — 팀플 네트워크 토대 설계 · Fab BuildId 불일치 트러블슈팅
Ch4 팀플(TeamCarry — 오버쿡드+무빙아웃 풍 이사 협동게임)에서 내 파트인 네트워크·물리 통합(접착제·안전망)의 1차 토대를 깔았다. 가구 운반의 핵심인 다인 합산 이동을 서버 권위로 설계하고, 가구가 구현할 계약 인터페이스만 제공해 도메인 결합도를 낮췄다. 중간에 에디터가
Missing Modules: Fab로 막혀 BuildId 불일치를 추적한 게 의외의 수확. 저녁 회의에서 운반 방식이 “2인이 들고 양쪽 입력이 모두 들어올 때 방향벡터 합산”으로 확정되며 내 설계와 맞물렸다.
오늘 한 일 요약
Network/도메인에 권위 헬퍼·합산 유틸·운반 계약·세션 골격 4종 작성- 팀 공통 복제 표준 문서 + 코드리뷰/운용 가이드 + PIE 테스트 케이스 21종 정리
- 에디터
Missing Modules: Fab막힘 → BuildId 불일치로 원인 규명(엔진 검증으로 해결) - md 문서 제외하고 코드만 커밋 → PR(base: develop) 발행
- 저녁 회의 — 스테이지 선택/시점/협동 운반 방식 확정
1. 내 파트 — 접착제·안전망
팀 5명 전원 프로그래머에 도메인이 갈려 있다(Player/Furniture/Level/Core/UI). 내 역할은 단일 기능이 아니라 통합·빌드·게임필 + 네트워크/물리 조율이다. 팀 규칙의 핵심:
네트워크·물리는 단일 소유자가 아니라 각 도메인에 분산 — 복제 변수는 도메인 담당이 소유하고, 통합 담당이 표준·세션·교차검증으로 묶는다.
그래서 오늘 방향은 “내가 가구 로직을 다 짠다”가 아니라 “각 도메인이 자기 복제 변수를 채워 넣을 표준과 계약을 깐다”가 됐다.
2. 네트워크 복제 표준을 먼저 세운 이유
멀티에서 가장 흔한 버그가 “호스트만 보이고 클라엔 안 보임”이다. 변수 추가할 때마다 규칙이 흔들리면 통합 단계에서 터진다. 그래서 코드보다 표준을 먼저 문서화했다.
- 서버 권위(클라는 입력만, 결과를 복제받음)
- 상태 변경은 서버에서만 — 직전에
HasAuthority()가드 - 지속 상태 =
UPROPERTY(Replicated), 1회성 연출 =Multicast, 클라 요청 =ServerRPC + 서버 재검증 - 잡힘/파손 연출은
OnRep_에서 처리해 모든 클라 일관
권위 가드를 매번 손으로 쓰지 않게 UTCNetStatics::HasAuthority/IsLocallyControlledPawn로 단일 창구를 만들었다.
3. 합산 물리는 순수 함수로 분리
가구 운반의 핵심은 “여러 명이 동시에 끌 때 어디로 가나”다. 이걸 액터에 박지 않고 상태 없는 static 함수로 뺐다.
1
2
3
4
5
6
7
8
9
10
11
FVector UTCCarryStatics::CombineCarryVelocity(const TArray<FVector>& MoveInputs, int32 RequiredCarriers, float BaseSpeed)
{
// 운반자 의도 벡터 합산 → 방향 정규화
FVector Sum = FVector::ZeroVector;
for (const FVector& In : MoveInputs) Sum += In;
const FVector Dir = Sum.GetSafeNormal();
if (Dir.IsNearlyZero()) return FVector::ZeroVector; // 서로 반대로 당기면 정지
const float Ratio = CarrierFulfillRatio(MoveInputs.Num(), RequiredCarriers); // 정원 미달이면 감속
return Dir * BaseSpeed * Ratio;
}
순수 함수라 액터 없이 단위 테스트·튜닝이 되고, 가구가 자체 합산 로직을 또 짜는 중복을 막는다. “서로 반대로 당기면 합벡터≈0 → 정지”가 협동을 강제하는 게임 규칙으로 그대로 떨어진 게 마음에 들었다.
4. 가구는 계약만 구현 — 인터페이스로 결합도 낮추기
가구 복제 변수(bIsGrabbed 등)는 다른 도메인 소유라 내가 못 건드린다. 대신 “이 함수들만 있으면 운반이 굴러간다”는 계약을 인터페이스로 줬다.
1
2
3
4
5
6
7
8
9
class ITCGrabbable
{
public:
virtual bool CanAddCarrier(ATCPlayerCharacter* C) const = 0; // 정원·중복 검증
virtual void AddCarrier(ATCPlayerCharacter* C) = 0;
virtual void RemoveCarrier(ATCPlayerCharacter* C) = 0;
virtual void ApplyCarryVelocity(const FVector& V, float Dt) = 0;
virtual int32 GetRequiredCarriers() const = 0;
};
기존 상호작용 인터페이스(포커스·입력)와 역할을 나눴다 — 이쪽은 운반 상태·물리 전담. 덕분에 가구 도메인과 네트워크 도메인이 한 파일을 동시에 안 건드린다(팀 규칙: 같은 파일 동시수정 금지).
세션도 OnlineSubsystem(Steam) 없이 리슨 서버 + 직접 IP로 최소 구현했다. GetWorld()->ServerTravel("맵?listen") 한 줄이면 호스트가 서버 겸 플레이어가 된다 — PIE 2~4인 테스트엔 이걸로 충분하고, Steam은 나중에 이 클래스에 확장하면 된다.
5. Fab BuildId 불일치 트러블슈팅
빌드 후 에디터를 여니 Missing Modules: Fab — built with a different engine version로 막혔다. 처음엔 내가 추가한 코드 탓인 줄 알았는데, 추적해보니 전혀 아니었다.
1
2
3
엔진 5.8 본체 Changelist : 53629095
내 게임 모듈 : 53629095 ✅ 일치 (내 코드 정상 컴파일됨)
Fab 엔진 플러그인 : 55116800 ❌ 불일치
.uproject엔 Fab 참조가 없었다 — Fab은 엔진 기본 플러그인이고, 이놈이 자기 자신을 최신 버전(55116800)으로 자동 업데이트해서 엔진 본체(53629095)와 어긋난 것. UE의 컴파일된 모듈은 BuildId(엔진 Changelist)로 호환성을 검사하는데, 이 값이 다르면 “다른 엔진 버전” 취급이라 로드를 거부한다.
- 진단:
Binaries/Win64/UnrealEditor.modules의BuildIdvs 엔진Build.version의Changelistvs 플러그인의.modulesBuildId를 셋 다 비교 - 해결: Epic Games Launcher에서 엔진 검증(Verify) → Fab만 53629095로 복원
- 교훈: “Missing Modules”가 떠도 내 모듈 BuildId가 엔진과 같으면 내 코드 문제가 아니다. 엔진 플러그인일 수 있다.
6. 회의 결정사항과 내 설계의 접점
저녁 회의에서 나온 결정이 오늘 내 코드와 정확히 맞물렸다.
확정
- 스테이지 선택: 이사센터 로비 게시판에서 선택 + 직접 트럭 운전(리썰 컴퍼니 풍)
- 시점: 인게임 3인칭(벽·문 보이게), 로비 1인칭, 시점 변환 키 도입
- 가구 이동: 2인이 들고 양쪽 WASD 입력이 모두 들어올 때 방향벡터 합산
- Git: 본인 브랜치 커밋 → 빌드 확인 → develop 병합
내 설계와의 접점
- 회의에서 “이동 중 속도(velocity) 동기화가 안 되고 순간이동 문제가 난다”고 했는데, 내가 합산을 물리 velocity가 아니라 서버
AddActorWorldOffset(단순 이동)으로 잡은 이유가 바로 이거다. 물리 시뮬은 동기화가 까다로워 캐주얼 게임엔 과하다. - “2인 입력이 모두 들어와야 이동”은 내
RequiredCarriers(정원) +CombineCarryVelocity(양쪽 입력 합산) 구조와 그대로 대응된다.
7. 오늘 배운 것 정리
- 표준을 코드보다 먼저. 멀티는 복제 규칙이 흔들리면 통합에서 터진다. 권위 모델·OnRep 규칙을 문서로 박아두고 시작했다.
- 결합도는 인터페이스로 끊는다. 남의 도메인 변수를 안 건드리고 “계약”만 주면, 같은 파일 동시수정 충돌 없이 시스템이 굴러간다.
- 합산 로직은 순수 함수로. 상태 없이 빼면 테스트·튜닝이 쉽고 중복 구현을 막는다. “반대로 당기면 정지”가 협동 규칙으로 자연스럽게 떨어졌다.
Missing Modules는 BuildId부터 비교. 내 모듈이 엔진 Changelist와 같으면 내 코드 탓이 아니다. 엔진 플러그인(Fab) 자동업데이트가 범인일 수 있고, 해법은 엔진 검증.- 설계가 회의 결정과 맞으면 자신감이 붙는다. 물리 대신 단순 이동을 택한 근거가 회의의 “velocity 동기화 안 됨” 이슈로 검증됐다.