포스트

[TIL] 2026-06-23 — 팀플 네트워크 토대 설계 · Fab BuildId 불일치 트러블슈팅

[TIL] 2026-06-23 — 팀플 네트워크 토대 설계 · Fab BuildId 불일치 트러블슈팅

Ch4 팀플(TeamCarry — 오버쿡드+무빙아웃 풍 이사 협동게임)에서 내 파트인 네트워크·물리 통합(접착제·안전망)의 1차 토대를 깔았다. 가구 운반의 핵심인 다인 합산 이동을 서버 권위로 설계하고, 가구가 구현할 계약 인터페이스만 제공해 도메인 결합도를 낮췄다. 중간에 에디터가 Missing Modules: Fab로 막혀 BuildId 불일치를 추적한 게 의외의 수확. 저녁 회의에서 운반 방식이 “2인이 들고 양쪽 입력이 모두 들어올 때 방향벡터 합산”으로 확정되며 내 설계와 맞물렸다.

오늘 한 일 요약

  1. Network/ 도메인에 권위 헬퍼·합산 유틸·운반 계약·세션 골격 4종 작성
  2. 팀 공통 복제 표준 문서 + 코드리뷰/운용 가이드 + PIE 테스트 케이스 21종 정리
  3. 에디터 Missing Modules: Fab 막힘 → BuildId 불일치로 원인 규명(엔진 검증으로 해결)
  4. md 문서 제외하고 코드만 커밋 → PR(base: develop) 발행
  5. 저녁 회의 — 스테이지 선택/시점/협동 운반 방식 확정

1. 내 파트 — 접착제·안전망

팀 5명 전원 프로그래머에 도메인이 갈려 있다(Player/Furniture/Level/Core/UI). 내 역할은 단일 기능이 아니라 통합·빌드·게임필 + 네트워크/물리 조율이다. 팀 규칙의 핵심:

네트워크·물리는 단일 소유자가 아니라 각 도메인에 분산 — 복제 변수는 도메인 담당이 소유하고, 통합 담당이 표준·세션·교차검증으로 묶는다.

그래서 오늘 방향은 “내가 가구 로직을 다 짠다”가 아니라 “각 도메인이 자기 복제 변수를 채워 넣을 표준과 계약을 깐다”가 됐다.

2. 네트워크 복제 표준을 먼저 세운 이유

멀티에서 가장 흔한 버그가 “호스트만 보이고 클라엔 안 보임”이다. 변수 추가할 때마다 규칙이 흔들리면 통합 단계에서 터진다. 그래서 코드보다 표준을 먼저 문서화했다.

  • 서버 권위(클라는 입력만, 결과를 복제받음)
  • 상태 변경은 서버에서만 — 직전에 HasAuthority() 가드
  • 지속 상태 = UPROPERTY(Replicated), 1회성 연출 = Multicast, 클라 요청 = Server RPC + 서버 재검증
  • 잡힘/파손 연출은 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.modulesBuildId vs 엔진 Build.versionChangelist vs 플러그인의 .modules BuildId를 셋 다 비교
  • 해결: Epic Games Launcher에서 엔진 검증(Verify) → Fab만 53629095로 복원
  • 교훈: “Missing Modules”가 떠도 내 모듈 BuildId가 엔진과 같으면 내 코드 문제가 아니다. 엔진 플러그인일 수 있다.

6. 회의 결정사항과 내 설계의 접점

저녁 회의에서 나온 결정이 오늘 내 코드와 정확히 맞물렸다.

확정

  • 스테이지 선택: 이사센터 로비 게시판에서 선택 + 직접 트럭 운전(리썰 컴퍼니 풍)
  • 시점: 인게임 3인칭(벽·문 보이게), 로비 1인칭, 시점 변환 키 도입
  • 가구 이동: 2인이 들고 양쪽 WASD 입력이 모두 들어올 때 방향벡터 합산
  • Git: 본인 브랜치 커밋 → 빌드 확인 → develop 병합

내 설계와의 접점

  • 회의에서 “이동 중 속도(velocity) 동기화가 안 되고 순간이동 문제가 난다”고 했는데, 내가 합산을 물리 velocity가 아니라 서버 AddActorWorldOffset(단순 이동)으로 잡은 이유가 바로 이거다. 물리 시뮬은 동기화가 까다로워 캐주얼 게임엔 과하다.
  • “2인 입력이 모두 들어와야 이동”은 내 RequiredCarriers(정원) + CombineCarryVelocity(양쪽 입력 합산) 구조와 그대로 대응된다.

7. 오늘 배운 것 정리

  1. 표준을 코드보다 먼저. 멀티는 복제 규칙이 흔들리면 통합에서 터진다. 권위 모델·OnRep 규칙을 문서로 박아두고 시작했다.
  2. 결합도는 인터페이스로 끊는다. 남의 도메인 변수를 안 건드리고 “계약”만 주면, 같은 파일 동시수정 충돌 없이 시스템이 굴러간다.
  3. 합산 로직은 순수 함수로. 상태 없이 빼면 테스트·튜닝이 쉽고 중복 구현을 막는다. “반대로 당기면 정지”가 협동 규칙으로 자연스럽게 떨어졌다.
  4. Missing Modules는 BuildId부터 비교. 내 모듈이 엔진 Changelist와 같으면 내 코드 탓이 아니다. 엔진 플러그인(Fab) 자동업데이트가 범인일 수 있고, 해법은 엔진 검증.
  5. 설계가 회의 결정과 맞으면 자신감이 붙는다. 물리 대신 단순 이동을 택한 근거가 회의의 “velocity 동기화 안 됨” 이슈로 검증됐다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.