PART 4 - 강의 3/3

커스텀 CMC 구현

스프린트, 대시, 스태미나 시스템의 네트워크 예측 구현

01

CharacterMovementComponent 상속

커스텀 이동 컴포넌트 생성

MyCharacterMovement.h UCLASS() class UMyCharacterMovement : public UCharacterMovementComponent { GENERATED_BODY() public: // 커스텀 이동 설정 UPROPERTY(EditDefaultsOnly, Category = "Sprint") float SprintSpeedMultiplier = 1.5f; UPROPERTY(EditDefaultsOnly, Category = "Sprint") float SprintStaminaCost = 10.f; // 런타임 상태 (복제됨) uint8 bWantsToSprint : 1; float CurrentStamina = 100.f; // CMC 오버라이드 virtual void UpdateFromCompressedFlags(uint8 Flags) override; virtual class FNetworkPredictionData_Client* GetPredictionData_Client() const override; virtual float GetMaxSpeed() const override; // 스프린트 제어 void StartSprint(); void StopSprint(); };
02

GetMaxSpeed 오버라이드

스프린트 속도 계산

MyCharacterMovement.cpp float UMyCharacterMovement::GetMaxSpeed() const { float MaxSpeed = Super::GetMaxSpeed(); // 스프린트 중이고 스태미나가 있으면 속도 증가 if (bWantsToSprint && CurrentStamina > 0.f) { MaxSpeed *= SprintSpeedMultiplier; } return MaxSpeed; } void UMyCharacterMovement::OnMovementUpdated( float DeltaSeconds, const FVector& OldLocation, const FVector& OldVelocity) { Super::OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity); // 스프린트 중 스태미나 소모 (서버에서만) if (CharacterOwner->HasAuthority() && bWantsToSprint && IsMovingOnGround()) { CurrentStamina = FMath::Max(0.f, CurrentStamina - SprintStaminaCost * DeltaSeconds); if (CurrentStamina <= 0.f) { StopSprint(); } } }
03

네트워크 예측 데이터

커스텀 SavedMove 연결

C++ // 커스텀 네트워크 예측 데이터 클래스 class FNetworkPredictionData_Client_MyCharacter : public FNetworkPredictionData_Client_Character { public: typedef FNetworkPredictionData_Client_Character Super; FNetworkPredictionData_Client_MyCharacter( const UCharacterMovementComponent& ClientMovement) : Super(ClientMovement) { } virtual FSavedMovePtr AllocateNewMove() override { return FSavedMovePtr(new FSavedMove_MyCharacter()); } }; // CMC에서 예측 데이터 반환 FNetworkPredictionData_Client* UMyCharacterMovement::GetPredictionData_Client() const { if (!ClientPredictionData) { UMyCharacterMovement* MutableThis = const_cast<UMyCharacterMovement*>(this); MutableThis->ClientPredictionData = new FNetworkPredictionData_Client_MyCharacter(*this); } return ClientPredictionData; }
04

커스텀 SavedMove 구현

FSavedMove 상속과 Compressed Flags

C++ class FSavedMove_MyCharacter : public FSavedMove_Character { public: uint8 bSavedWantsToSprint : 1; virtual void Clear() override { Super::Clear(); bSavedWantsToSprint = 0; } virtual uint8 GetCompressedFlags() const override { uint8 Flags = Super::GetCompressedFlags(); if (bSavedWantsToSprint) Flags |= FLAG_Custom_0; return Flags; } virtual bool CanCombineWith( const FSavedMovePtr& NewMove, ACharacter* InCharacter, float MaxDelta) const override { const FSavedMove_MyCharacter* MyMove = static_cast<const FSavedMove_MyCharacter*>( NewMove.Get()); if (bSavedWantsToSprint != MyMove->bSavedWantsToSprint) return false; return Super::CanCombineWith(NewMove, InCharacter, MaxDelta); } virtual void SetMoveFor(ACharacter* C, float InDeltaTime, FVector const& NewAccel, FNetworkPredictionData_Client_Character& ClientData) override { Super::SetMoveFor(C, InDeltaTime, NewAccel, ClientData); UMyCharacterMovement* CMC = Cast<UMyCharacterMovement>(C->GetCharacterMovement()); if (CMC) bSavedWantsToSprint = CMC->bWantsToSprint; } };
CanCombineWith 최적화

연속된 동일 상태의 Move들은 하나로 결합되어 대역폭을 절약합니다. 커스텀 플래그가 변경되면 결합을 중단하여 상태 변경 시점을 정확히 전달하세요.

SUMMARY

핵심 요약

  • CMC 상속 - UCharacterMovementComponent 확장
  • GetMaxSpeed - 커스텀 속도 계산 로직
  • GetPredictionData_Client - 커스텀 SavedMove 사용
  • 스태미나 - 서버에서만 소모, 결과는 복제
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 스프린트 CMC 구현

UCharacterMovementComponent를 상속하여 bWantsToSprint 플래그와 SprintSpeedMultiplier를 구현하세요. GetMaxSpeed()를 오버라이드하고, UpdateFromCompressedFlags()에서 FLAG_Custom_0으로 스프린트 상태를 복원하세요.

실습 2: SavedMove 커스텀 플래그

FSavedMove_Character를 상속하여 스프린트 플래그를 저장하는 SavedMove를 구현하세요. GetCompressedFlags(), CanCombineWith(), SetMoveFor()를 모두 오버라이드하고, 200ms 지연 환경에서 예측이 정확한지 테스트하세요.

심화 과제

스프린트에 스태미나 시스템을 결합하세요. 서버에서 OnMovementUpdated()로 스태미나를 소모하고, 클라이언트에서도 예측을 위해 동일한 소모 로직을 실행하세요. 서버-클라이언트 간 스태미나 불일치 시 보정이 올바르게 작동하는지 확인해보세요.