Skip to content

UE5 CharacterMovement 심층 분석

UCharacterMovementComponent(CMC)는 UE5에서 ACharacter의 이동을 처리하는 핵심 컴포넌트입니다. 단순한 위치 이동을 넘어 다음을 담당합니다.

  • Movement Mode 관리: Walking, Falling, Swimming, Flying, Custom 등
  • 물리 기반 이동: 중력, 마찰, 공중 제어
  • 네트워크 예측: 클라이언트 측 예측 + 서버 보정
  • 루트 모션 통합: 애니메이션 루트 모션과 이동 로직 통합
CMC 업데이트 흐름
──────────────────────────────
TickComponent()
└─ PerformMovement()
├─ StartNewPhysics() ← 현재 Mode에 맞는 물리 계산
│ ├─ PhysWalking()
│ ├─ PhysFalling()
│ ├─ PhysSwimming()
│ └─ PhysCustom() ← 커스텀 구현 진입점
└─ MoveUpdatedComponent() ← 실제 위치 적용

// 이동 모드 전환
void AMyCharacter::StartFlying()
{
if (UCharacterMovementComponent* CMC = GetCharacterMovement())
{
CMC->SetMovementMode(MOVE_Flying);
}
}
void AMyCharacter::StartSwimming()
{
GetCharacterMovement()->SetMovementMode(MOVE_Swimming);
}
// 커스텀 모드 진입 (Mode = MOVE_Custom, CustomMode = 원하는 값)
void AMyCharacter::StartWallRunning()
{
GetCharacterMovement()->SetMovementMode(MOVE_Custom, static_cast<uint8>(EMyMovementMode::WallRunning));
}
MyCharacterMovementComponent.h
UENUM(BlueprintType)
enum class EMyMovementMode : uint8
{
None UMETA(DisplayName = "None"),
WallRunning UMETA(DisplayName = "Wall Running"),
Sliding UMETA(DisplayName = "Sliding"),
Climbing UMETA(DisplayName = "Climbing"),
};

2. CMC 서브클래싱 — 커스텀 이동 구현

Section titled “2. CMC 서브클래싱 — 커스텀 이동 구현”
MyCharacterMovementComponent.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "MyCharacterMovementComponent.generated.h"
UCLASS()
class MYGAME_API UMyCharacterMovementComponent : public UCharacterMovementComponent
{
GENERATED_BODY()
public:
UMyCharacterMovementComponent();
// 커스텀 모드 물리 처리
virtual void PhysCustom(float DeltaTime, int32 Iterations) override;
// 이동 가능 여부 쿼리
virtual bool CanAttemptJump() const override;
// 서버 검증 (네트워크)
virtual bool ServerCheckClientError(float ClientTimeStamp,
float DeltaTime,
const FVector& Accel,
const FVector& ClientWorldLocation,
const FVector& RelativeClientLocation,
UPrimitiveComponent* ClientMovementBase,
FName ClientBaseBoneName,
uint8 ClientMovementMode) override;
// 벽타기 진입/종료
void EnterWallRunning(const FVector& WallNormal);
void ExitWallRunning();
bool IsWallRunning() const;
// 벽타기 파라미터
UPROPERTY(EditDefaultsOnly, Category = "Wall Running")
float WallRunSpeed = 800.f;
UPROPERTY(EditDefaultsOnly, Category = "Wall Running")
float WallRunGravityScale = 0.25f;
private:
void PhysWallRunning(float DeltaTime, int32 Iterations);
FVector WallNormal = FVector::ZeroVector;
bool bIsWallRunning = false;
};
MyCharacterMovementComponent.cpp
void UMyCharacterMovementComponent::PhysCustom(float DeltaTime, int32 Iterations)
{
// 현재 커스텀 모드에 따라 분기
switch (static_cast<EMyMovementMode>(CustomMovementMode))
{
case EMyMovementMode::WallRunning:
PhysWallRunning(DeltaTime, Iterations);
break;
case EMyMovementMode::Sliding:
// PhysSliding(DeltaTime, Iterations);
break;
default:
Super::PhysCustom(DeltaTime, Iterations);
break;
}
}
void UMyCharacterMovementComponent::PhysWallRunning(float DeltaTime, int32 Iterations)
{
if (DeltaTime < MIN_TICK_TIME) return;
// 중력 스케일 임시 적용
const float SavedGravityScale = GravityScale;
GravityScale = WallRunGravityScale;
// 벽 방향에 수직인 이동 방향 계산
FVector WallRunDirection = FVector::CrossProduct(WallNormal, FVector::UpVector);
WallRunDirection.Normalize();
// 속도 적용
Velocity = WallRunDirection * WallRunSpeed;
Velocity.Z = FMath::Max(Velocity.Z, -200.f); // 너무 빠르게 내려가지 않도록
// 가속 적용 및 위치 이동
Iterations++;
bJustTeleported = false;
FVector OldLocation = UpdatedComponent->GetComponentLocation();
const FVector Adjusted = Velocity * DeltaTime;
FHitResult Hit(1.f);
SafeMoveUpdatedComponent(Adjusted, UpdatedComponent->GetComponentQuat(), true, Hit);
if (Hit.IsValidBlockingHit())
{
// 벽에 충돌 — 계속 벽에 붙어 있는지 확인
FVector ImpactNormal = Hit.ImpactNormal;
if (FMath::Abs(ImpactNormal.Z) < 0.2f) // 수직에 가까운 벽
{
WallNormal = ImpactNormal;
}
else
{
ExitWallRunning();
}
}
GravityScale = SavedGravityScale;
// 이 프레임에서 이동한 속도 계산
const FVector NewLocation = UpdatedComponent->GetComponentLocation();
Velocity = (NewLocation - OldLocation) / DeltaTime;
}
void UMyCharacterMovementComponent::EnterWallRunning(const FVector& InWallNormal)
{
WallNormal = InWallNormal;
bIsWallRunning = true;
SetMovementMode(MOVE_Custom, static_cast<uint8>(EMyMovementMode::WallRunning));
}
void UMyCharacterMovementComponent::ExitWallRunning()
{
bIsWallRunning = false;
WallNormal = FVector::ZeroVector;
SetMovementMode(MOVE_Falling);
}
bool UMyCharacterMovementComponent::IsWallRunning() const
{
return MovementMode == MOVE_Custom
&& CustomMovementMode == static_cast<uint8>(EMyMovementMode::WallRunning);
}

3. 네트워크 예측 — SavedMove 패턴

Section titled “3. 네트워크 예측 — SavedMove 패턴”

멀티플레이어 게임에서 CMC는 클라이언트 측 예측으로 입력 반응성을 유지하고, 서버와 주기적으로 동기화합니다. 커스텀 이동 데이터를 네트워크로 전송하려면 FSavedMove_Character를 확장해야 합니다.

// MyCharacterMovementComponent.h 추가
class MYGAME_API FSavedMove_My : public FSavedMove_Character
{
typedef FSavedMove_Character Super;
public:
// 커스텀 저장 데이터
uint8 bSavedWantsToWallRun : 1;
virtual bool CanCombineWith(const FSavedMovePtr& NewMove,
ACharacter* InCharacter,
float MaxDelta) const override;
virtual void Clear() override;
virtual uint8 GetCompressedFlags() const override;
virtual void SetMoveFor(ACharacter* C, float InDeltaTime, FVector const& NewAccel,
FNetworkPredictionData_Client_Character& ClientData) override;
virtual void PrepMoveFor(ACharacter* C) override;
};
class MYGAME_API FNetworkPredictionData_Client_My
: public FNetworkPredictionData_Client_Character
{
public:
FNetworkPredictionData_Client_My(const UCharacterMovementComponent& ClientMovement);
virtual FSavedMovePtr AllocateNewMove() override;
};
// SavedMove 구현
void FSavedMove_My::Clear()
{
Super::Clear();
bSavedWantsToWallRun = 0;
}
uint8 FSavedMove_My::GetCompressedFlags() const
{
uint8 Result = Super::GetCompressedFlags();
if (bSavedWantsToWallRun) Result |= FLAG_Custom_0;
return Result;
}
void FSavedMove_My::SetMoveFor(ACharacter* C, float InDeltaTime,
FVector const& NewAccel,
FNetworkPredictionData_Client_Character& ClientData)
{
Super::SetMoveFor(C, InDeltaTime, NewAccel, ClientData);
if (UMyCharacterMovementComponent* CMC = Cast<UMyCharacterMovementComponent>(C->GetCharacterMovement()))
{
bSavedWantsToWallRun = CMC->bWantsToWallRun;
}
}
void FSavedMove_My::PrepMoveFor(ACharacter* C)
{
Super::PrepMoveFor(C);
if (UMyCharacterMovementComponent* CMC = Cast<UMyCharacterMovementComponent>(C->GetCharacterMovement()))
{
CMC->bWantsToWallRun = bSavedWantsToWallRun;
}
}
// CMC에서 커스텀 PredictionData 반환
FNetworkPredictionData_Client* UMyCharacterMovementComponent::GetPredictionData_Client() const
{
if (!ClientPredictionData)
{
UMyCharacterMovementComponent* MutableThis =
const_cast<UMyCharacterMovementComponent*>(this);
MutableThis->ClientPredictionData =
new FNetworkPredictionData_Client_My(*this);
}
return ClientPredictionData;
}

// AMyCharacter::BeginPlay 또는 생성자에서 설정
void AMyCharacter::SetupMovementParameters()
{
UCharacterMovementComponent* CMC = GetCharacterMovement();
if (!CMC) return;
// 걷기
CMC->MaxWalkSpeed = 600.f;
CMC->MaxWalkSpeedCrouched = 300.f;
CMC->BrakingDecelerationWalking = 2048.f;
CMC->GroundFriction = 8.f;
// 점프
CMC->JumpZVelocity = 600.f;
CMC->AirControl = 0.35f; // 공중 제어 비율 (0=무제어, 1=완전제어)
CMC->AirControlBoostMultiplier = 2.f;
CMC->FallingLateralFriction = 0.f;
// 중력
CMC->GravityScale = 1.75f; // 기본(1.0)보다 빠른 낙하 — 더 빠릿한 조작감
// 계단 처리
CMC->MaxStepHeight = 45.f;
CMC->SetWalkableFloorAngle(46.f); // 등반 가능한 최대 경사각
// 네트워크
CMC->NetworkSmoothingMode = ENetworkSmoothingMode::Exponential;
CMC->NetworkMaxSmoothUpdateDistance = 92.f;
CMC->NetworkNoSmoothUpdateDistance = 140.f;
}

기능진입점
커스텀 이동 구현PhysCustom() 오버라이드
이동 모드 전환SetMovementMode(MOVE_Custom, CustomMode)
네트워크 예측 데이터FSavedMove_Character 서브클래스
이동 불가 조건CanAttemptJump() 등 오버라이드
파라미터 조정MaxWalkSpeed, GravityScale, AirControl