개발자 능력치 물약/서버
게임 동기화 방식) 최종 일관성 , 서버 되감기 기법
하시무
2025. 5. 28. 23:55
CAP 정리를 통한 게임 동기화
NDC 발표자료 학습 내용 정리 및 언리얼 엔진 적용
🔹 CAP Theorem 정리 기본 개념
분산 시스템에서는 다음 3가지 속성 중 최대 2가지만 동시에 만족할 수 있다:
- 일관성 (Consistency): 모든 노드가 같은 시점에 같은 데이터를 보는 것
- 가용성 (Availability): 시스템이 항상 응답 가능한 상태를 유지하는 것
- 분할용인 (Partition Tolerance): 네트워크 분할 상황에서도 시스템이 동작하는 것
멀티플레이 네트워크 게임은 분산 시스템이라고 할 수 있다.
🎮 게임 서버 설계 방식
동기 vs 비동기 처리
- 동기 처리: 코드 작성 유리, 성능 불리 → 실시간 네트워크게임에 적합
- 비동기 처리: 코드 작성 불리, 성능 유리 → 웹서비스, 모바일/소셜게임에 적합
💡 실제로는 비동기와 동기의 적절한 균형이 중요하며, 코드가 단순해지는 방향을 선호
📊 게임 동기화 방식별 특징
타입 1: 서버 동기화 (A + P)
주요 장르: FPS, MMORPG, MOBA, Sports
- 목표: 플레이어에게 랙 없는 경험 제공
- 방식: 클라이언트 이벤트를 서버에서 계산 후 브로드캐스트
- 높은 TickRate = 정확도 향상
- AOI(Area Of Interest): 필요한 범위의 정보만 전송하여 최적화
- AOI 최적화: 낮으면 플레이 경험 불쾌, 높으면 치팅에 취약
- 단점: Rubber Banding 현상 (클라이언트 예측과 서버 보정 간의 차이)
타입 2: Lock-Step 동기화 (C + P)
주요 장르: RTS, 과거 AOS
- 목표: 플레이어에게 공평한 경험 제공
- 방식: 모든 플레이어의 입력을 수집한 후 동시에 시뮬레이션
- 특징: 한 플레이어가 멈추면 모든 플레이어가 대기
- TickRate 조절: 지연시간과 반응성의 트레이드오프
Latency 높을 때: 반응성 ↑, 대기 가능성 ↑
Latency 낮을 때: 반응성 ↓, 부드러운 플레이 진행
⚡ Lag 보정 기법
서버 되감기(Server Rollback)
서버 되감기를 통한 동기화 방식의 동작 원리:
- 서버와 클라이언트가 각자 시뮬레이션 수행
- 서버는 클라이언트 입력 기준 과거 시점으로 되감기하여 시뮬레이션
- 결과가 다르면 클라이언트에게 보정 신호 전송
- 스냅샷 보관이 필요 (메모리 오버헤드)
상세 동작 과정
- 클라이언트: 입력 → 시뮬레이션 → 해시 계산 → 서버에 입력 및 해시 전송
- 입력 히스토리 기록 (Client Input Frame 단위로 보관)
- 서버: 이전 WorldFrame 상태 → WorldFrame++ → 월드상태 업데이트
- 될감기 시뮬레이션을 위한 월드상태 히스토리 보관
- 해시 비교 → 다르면 Desync 상황 → 클라이언트에 Resync 요청
단점: 후판정 문제
최대 지연시간만큼의 후판정이 발생하여, 엄폐물 뒤에 숨었음에도 피격되는 상황이 생길 수 있음. 주로 FPS 게임에서 사용되며, 강한 일관성 대신 최종 일관성을 추구함.
- 쿼터뷰 특성: 제한된 시야로 AOI 최적화 가능
구체적 구현 전략
- 이동/기본 액션: 클라이언트 예측 + 서버 보정
- 스킬/특수 액션: 서버 권한 처리 후 브로드캐스트
- UI/시각 효과: 클라이언트 즉시 처리
C++ 구현 예시
// MyCharacter.h
UCLASS()
class MYGAME_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyCharacter();
// Replication 설정을 위한 필수 override
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// 서버에서 클라이언트로 상태 복제
UPROPERTY(Replicated)
float Health;
// 하이브리드 동기화 구현
UPROPERTY(ReplicatedUsing = OnRep_Position)
FVector ReplicatedPosition;
// 새로운 RPC 함수들 (virtual 불필요)
UFUNCTION(Server, Reliable, WithValidation)
void ServerMove(FVector NewLocation);
UFUNCTION(Server, Reliable)
void ServerExecuteSkill(int32 SkillID);
// RepNotify 함수
UFUNCTION()
void OnRep_Position();
protected:
// 언리얼 라이프사이클 함수들
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
private:
void PredictMovement();
void CorrectPosition();
};
// MyCharacter.cpp
void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyCharacter, Health);
DOREPLIFETIME(AMyCharacter, ReplicatedPosition);
}
void AMyCharacter::ServerMove_Implementation(FVector NewLocation)
{
// 서버에서 실행되는 로직
SetActorLocation(NewLocation);
}
bool AMyCharacter::ServerMove_Validate(FVector NewLocation)
{
// 입력 유효성 검사
return !NewLocation.ContainsNaN();
}
💡 개발 권장사항
- 언리얼의 CharacterMovementComponent 활용 (최적화된 네트워크 이동 시스템)
- 프로토타입 단계에서는 언리얼 기본 네트워크 시스템 사용
- 성능/게임플레이 이슈 발생 시 세부 커스터마이징 진행
- Network Relevancy 설정으로 불필요한 데이터 전송 최소화
- 중요도별 다른 Replication 주기 설정 (Critical vs Non-Critical)
팀 프로젝트 적용 방안
게임의 장르와 특성에 따라 CAP 중 2가지를 선택하여 네트워크 아키텍처를 설계해야 한다. 3인 액션 쿼터뷰 게임의 경우 실시간성과 반응성이 중요하므로 가용성(A)과 분할용인(P)을 선택한 서버 동기화 방식이 적합하며, 언리얼 엔진의 기본 네트워크 시스템이 이를 잘 지원한다.
특히 언리얼의 CharacterMovementComponent는 클라이언트 예측과 서버 보정을 자동으로 처리하므로, 초기 개발 단계에서는 빠르게 프로토타입을 구현하고, 필요에 따라 게임 특성에 맞는 커스터마이징을 진행하는 것이 효율적일 것 같다.