Repository navigation
12_Multiplayer_Synchronization
Sonheim은 Listen Server에서 게임플레이 결과를 확정하고, 각 화면·Actor가 필요한 상태를 복제합니다. 이 장에서는 아이템 슬롯, 스킬 실행, Pal 선택, 포획, 보스와 던전 상태에 실제 사용한 전달 방식을 정리했습니다.
flowchart LR
A["클라이언트 입력"] --> B["Server RPC"]
B --> C["서버 조건 확인 · 상태 변경"]
C --> D["프로퍼티 / FastArray<br/>Subobject / Snapshot"]
D --> E["클라이언트 상태 갱신"]
E --> F["Widget · Actor 표현"]
A -. "일부 조작의 로컬 피드백" .-> F
| 조작 | 서버에서 확정하는 값 | 클라이언트에 반영되는 상태 |
|---|---|---|
| 인벤토리 아이템 이동 | 슬롯 순서·아이템 수량 |
RepItems → 인벤토리 UI |
| 무기 전환 | 활성 무기 슬롯 |
CurrentWeaponSlot → 장비 표현 |
| 스킬 사용 | 사용 권한·시전·쿨타임 |
SkillSpecs + UBaseSkill, 애니메이션 RPC |
| Pal 선택 | CurrentPalIndex |
소유 플레이어의 선택 UI |
| 포획 | 성공 판정·보유 목록·PartnerOwner | Reveal 이벤트와 확정된 Pal 기록 |
| 던전 진행 | Stage·목표·분기·보스·결과 |
GameState의 현재 Snapshot |
로컬에서 RPC를 보냈다고 동작을 확정하지 않습니다. 스킬·인벤토리·포획 등은 서버가 요청 시점의 상태를 검증합니다. 로컬 피드백이 필요한 일부 조작에는 실제 상태를 변경하지 않는 표시 경로를 별도로 뒀습니다.
| 값 | 실제 복제 방식 | 사용 대상 |
|---|---|---|
UInventoryComponent::RepItems / EquippedSlots |
COND_OwnerOnly |
소유 플레이어의 슬롯·장비 UI |
UInventoryComponent::CurrentWeaponSlot |
일반 DOREPLIFETIME
|
무기 상태를 보는 클라이언트 |
UQuestComponent::Quests · UMonsterDexComponent::Dex
|
OwnerOnly FastArray | 플레이어별 퀘스트·도감 |
UPalInventoryComponent::OwnedPals / CurrentPalIndex |
COND_OwnerOnly |
Pal 보유·선택 화면 |
USonheimSkillComponent::SkillSpecs |
일반 FastArray | 시전 상태·쿨타임 정보 |
ABossMonster::Status |
일반 프로퍼티 복제 | 현재 보스 상태 |
ASonheimGameState::DungeonStageState |
일반 프로퍼티 복제 | 던전의 현재 진행 상태 |
COND_OwnerOnly는 전송할 연결의 범위를 제한하고, 서버 권한 검증은 누가 그 값을 변경할 수 있는지를 결정합니다. 또한 일반 DOREPLIFETIME이라고 해서 모든 연결이 항상 받는 것은 아니며 Actor의 Relevancy 영향을 받습니다.
서버의 InventoryItems는 게임플레이 상태이고, FRepInventoryList는 슬롯 인덱스·ID·수량을 복제하는 배열입니다.
// InventoryComponent.cpp
DOREPLIFETIME_CONDITION(UInventoryComponent, RepItems, COND_OwnerOnly);
DOREPLIFETIME_CONDITION(UInventoryComponent, EquippedSlots, COND_OwnerOnly);
DOREPLIFETIME(UInventoryComponent, CurrentWeaponSlot);서버는 수량·슬롯 변경 시 MarkItemDirty, 배열 구조 변경 시 MarkArrayDirty를 사용합니다. 클라이언트는 OnRep_RepItems()에서 RebuildInventoryArrayFromRep()를 호출해 슬롯 인덱스 기준 InventoryItems를 재구성하고 OnInventoryChanged로 화면을 갱신합니다.
서버 InventoryItems
→ RepItems (FastArray Dirty)
→ OwnerOnly 복제
→ OnRep_RepItems
→ 로컬 InventoryItems 재구성
→ OnInventoryChanged → Slot UI
슬롯 교환과 보관함 아이템 이동에 따른 화면 갱신은 인벤토리·보관함 플레이 영상에서 볼 수 있습니다.
보관함은 공유 월드 Actor입니다. UContainerComponent::PreReplication()은 Subscribers가 있을 때만 RepContainerItems의 복제를 활성화합니다.
// ContainerComponent.cpp
const bool bActive = Subscribers.Num() > 0;
DOREPLIFETIME_ACTIVE_OVERRIDE(
UContainerComponent, RepContainerItems, bActive);이 코드는 구독자가 한 명이라도 있는지 확인하는 방식입니다. 구독자가 아닌 클라이언트에게 개별적으로 필터를 적용하는 방식이 아니라는 점을 구분했습니다. Viewer가 등록되면 ForceNetUpdate도 요청합니다.
USonheimSkillComponent는 FSonheimSkillSpecItem의 SkillId·Level·bIsCasting·CooldownEndTime을 FastArray로 복제합니다. UI나 타 시스템은 스킬별 시전 여부와 종료 시각을 이 요약 데이터로 조회할 수 있습니다.
동시에 EnsureSkillInstance가 만든 UBaseSkill 객체도 AAreaObject::ReplicateSubobjects()로 복제합니다.
// AreaObject.cpp — ReplicateSubobjects() 발췌
for (const auto& Pair : m_SkillComponent->GetSkillInstances())
{
if (UBaseSkill* Skill = Pair.Value)
bWroteSomething |= Channel->ReplicateSubobject(
Skill, *Bunch, *RepFlags);
}| 경로 | 전달 내용 |
|---|---|
SkillSpecs FastArray |
SkillId·Level·시전 여부·쿨타임 종료 시각 |
UBaseSkill Subobject |
m_CurrentPhase·m_CurrentCoolTime·m_TargetPos·m_NextSkillID
|
MultiCast_CastSkill |
스킬 사용 애니메이션 연출 |
스킬 실행의 실제 타격·비용 소비는 서버 경로이고, 애니메이션 시작은 별도 RPC입니다. Spec과 Subobject 사이 일부 상태는 중복되지만 UI가 필요한 요약 상태와 실행 객체의 상태가 별도 복제 경로라는 점이 현재 구조의 특징입니다.
인벤토리 슬롯 교환: bEnableClientPrediction을 켜면 로컬에서 InventoryItems를 복사한 PredictedInventory를 교환해 OnInventoryChanged에 전달합니다. 원본 배열은 이 과정에서 변경하지 않고, 서버 RepItems가 도착하면 확정된 슬롯 순서로 재구성합니다.
// InventoryComponent.cpp — SwapItems 클라이언트 경로
if (bEnableClientPrediction)
PerformClientPrediction_SwapItems(FromIndex, ToIndex);
ServerSwapItems(FromIndex, ToIndex);이 옵션은 C++ 기본값이 false이므로, 실제 Blueprint 설정을 확인하지 않고 모든 인벤토리 이동에 예측이 적용된다고 주장하지 않습니다.
Pal 슬롯 선택: 클라이언트는 예상 인덱스를 계산해 OnSelectedPalChanged로 HUD를 먼저 갱신하지만 CurrentPalIndex 자체는 덮어쓰지 않습니다. ServerRPC_SwitchPalSlot에서 서버가 확정한 인덱스를 OwnerOnly로 받고 OnRep_CurrentPalIndex로 다시 표시합니다.
두 경우 모두 표시를 먼저 바꾸는 것과 권위 있는 게임플레이 값을 먼저 바꾸는 것은 다릅니다.
서버는 포획 성공 여부와 FPalCaptureRevealParams(연출 시간·Segment·실패 지점)를 결정한 뒤 Multicast_BeginCaptureReveal로 연출을 시작합니다. 연출이 끝날 때 ApplyCaptureOutcome()에서 실제 소유권을 반영합니다.
sequenceDiagram
participant C as Client
participant S as Server
participant M as Monster
C->>S: 포획 요청
S->>S: 포획 성공 판정
S-->>C: Reveal 시작 이벤트
S->>S: 연출 완료 대기 · Pal 보유 공간 재확인
S->>M: PartnerOwner · 보유 목록 변경
M-->>C: 최종 Pal 상태 복제
연출 시작부터 결과 적용까지 시간이 흐르므로, 그 사이 보유 Pal 수가 달라질 수 있습니다. ApplyCaptureOutcome()은 소유권 변경 직전에 보유 공간을 재확인하고, 공간이 없으면 포획 실패 처리로 전환합니다.
ABossMonster::Status는 복제 프로퍼티로 유지하며 OnRep_Status()에서 현재 상태를 반영합니다. 반면 Montage 시작·섹션 이동·종료는 MulticastPlayMontage 등의 이벤트로 보냅니다.
던전 진입 직후 보스가 Wake를 시작하면, 클라이언트에게 보스 Actor가 아직 relevant하지 않은 시점에 Multicast가 발생할 수 있습니다. 그 경우 현재 보스 Status를 받는 것과 이전 Montage 시작 이벤트를 받는 것은 별개입니다.
이 문제는 Reliable 설정만의 문제가 아니라 Actor Relevancy, Actor 초기 상태, 이벤트 발생 시점을 함께 확인해야 하는 사례입니다. 상태 복제만으로 이미 지나간 Montage 이벤트 전체가 자동 재생된다고 설명하지 않습니다.
UDungeonStageRuntimeSubsystem의 진행 결과를 ASonheimGameState::DungeonStageState에 게시합니다. Snapshot에는 RunId·Revision, Stage·목표·분기·보스 상태·서버 기준 시간·결과가 들어갑니다.
// DungeonStagePresenter.cpp — OnSnapshot
if (Snapshot.RunId == Latest.RunId &&
Snapshot.Revision <= Latest.Revision) return;같은 실행에서 이미 처리한 Revision 이하라면 UDungeonStagePresenter가 다시 반영하지 않습니다. 위젯을 나중에 열거나 다시 만들 때도 마지막 Snapshot을 기반으로 ViewData를 만들기 때문에 Stage·타이머가 UI 생성 시점에 초기화되지 않습니다.
Listen Server 호스트는 PublishDungeonStageState()에서 OnRep_DungeonStageState()를 직접 호출해 서버 로컬 UI에도 변경 알림을 전달합니다. 던전 Snapshot은 UI의 복구에 필요한 현재 값, 진행 알림이나 보스 Montage RPC는 발생 시점의 이벤트라는 구분입니다.
표시 데이터 변환은 11. Client State Presentation, 각각의 게임플레이 구현은 08. Pal Capture·10. Boss Encounter에 정리했습니다.
이 기술 문서는 프로젝트의 상세 구현을 다루는 '심층 분석' 자료입니다. README에서 생긴 궁금증을 이곳에서 코드를 통해 확인해 보세요.
| 문서 | 역할 | 내용 |
|---|---|---|
| 📋 Project Gallery | Root (전체 개요) | 주요 프로젝트 목록, 핵심 역량 요약 |
| 📁 Repository README | What (개요) | 프로젝트 목표, 데모 영상, 핵심 기능, 아키텍처 |
| 🔗 Tech Docs | How & Why (상세 구현) | 코드 분석, 설계 과정, 기술 회고, 트러블슈팅 |
- 04. Player & Character Systems
- 05. Combat, Skill & Animation
- 06. World Interaction Systems
- 07. Inventory & Crafting
- 08. Pal Capture & Partner Lifecycle
- 16. MonsterDex System
- 17. Quest System
- 19. World Map & Fast Travel