
지금까지 배운 RPC와 Replication 기술을 총동원하여, 멀티플레이 캐릭터의 핵심인 이동(애니메이션), 시선(AimOffset), 공격(판정), 상태(HP)를 동기화했다. 단순히 변수만 맞추는 게 아니라, 서버의 권한(Authority)과 클라이언트의 예측(Prediction) 사이의 균형을 맞추는 것이 핵심이다.
CharacterMovementComponent의 복제 속성을 활용한 기본 이동 처리Server RPC와 NetMulticast를 활용한 판정 및 이펙트 처리ActorComponent를 통한 독립적인 상태(HP) 관리 및 복제 조건 설정캐릭터가 걷거나 점프하는 동작을 모든 클라이언트에게 똑같이 보여주려면 어떻게 해야 할까?
CharacterMovementComponent는 이미 위치, 회전, 속도(Velocity) 등을 자동으로 동기화해주고 있다. (Replicate Movement 옵션)
따라서 애님 인스턴스에서는 이 동기화된 속도(Velocity)를 읽어와서 애니메이션 상태 머신을 구동하면 자연스럽게 해결된다.
캐릭터가 위아래를 쳐다보는 AimOffset은 단순히 애니메이션 변수만으로는 해결되지 않는다. 왜냐하면 '플레이어가 어디를 보고 있는지(Control Rotation)'는 보안상 서버와 내 컴퓨터(Owner)만 알고 있고, 다른 사람들은 모르기 때문이다.
Simulated Proxy)에서는 내가 하늘을 보고 있어도 정면을 보는 것처럼 나온다.Tick에서 내 컨트롤러의 Pitch(각도) 값을 읽어서 변수에 저장한다.ServerRPCUpdateAimValue를 호출해 서버에게 "나 여기 보고 있어"라고 알린다. (Unreliable로 설정해도 무방하다. 시선은 계속 변하니까.)Replicated 변수에 저장하고, 다른 클라이언트들에게 전파한다.// [PlayerCharacter.cpp]
void ADXPlayerCharacter::Tick(float DeltaTime)
{
// 로컬 클라이언트라면 내 시선 각도를 서버로 보낸다
if (IsLocallyControlled())
{
float NewPitch = GetControlRotation().Pitch;
if (NewPitch != CurrentAimPitch)
{
ServerRPCUpdateAimValue(NewPitch);
}
}
}
// 서버는 받아서 변수를 갱신 -> Replicated 속성이므로 다른 클라들에게 자동 전파됨
void ADXPlayerCharacter::ServerRPCUpdateAimValue_Implementation(float InPitch)
{
CurrentAimPitch = InPitch;
}
공격 버튼을 눌렀을 때의 처리는 '누가 무엇을 담당하느냐'가 가장 중요하다.
Server RPC를 호출한다.TakeDamage)을 수행하고, HP를 깎는다.NetMulticast RPC를 통해 "공격 동작을 재생하라"고 명령한다. (최적화를 위해 공격한 본인은 제외하고 재생시킬 수도 있다.)캐릭터의 HP나 스탯 관리는 별도의 ActorComponent로 분리하는 것이 좋다. 이때 컴포넌트 내부의 변수도 복제되려면 SetIsReplicatedByDefault(true) 설정과 GetLifetimeReplicatedProps 구현이 필수적이다.
특히 DOREPLIFETIME_CONDITION을 사용하면, 최대 체력(MaxHP) 같이 게임 중 잘 변하지 않거나 UI 표시용으로 주인만 알면 되는 정보를 조건부로 복제하여 네트워크 대역폭을 아낄 수 있다.
| 구분 | 동기화 방식 | 핵심 포인트 |
|---|---|---|
| 이동/점프 | Replicated Movement | 컴포넌트가 동기화해준 Velocity 등을 애님 인스턴스가 참조 |
| AimOffset | Server RPC + Rep | Control Rotation은 로컬 데이터이므로, RPC로 서버에 보내고 변수로 복제해야 함 |
| 공격 | Server RPC Multicast | 판정은 서버, 이펙트/몽타주는 멀티캐스트 (단, 로컬은 예측 가능) |
| HP/스탯 | Actor Component | SetIsReplicatedByDefault(true) 설정 필수. 조건부 복제로 최적화 |