UE5 CharacterMovement 심층 분석
개요 — CMC의 역할과 구조
Section titled “개요 — CMC의 역할과 구조”UCharacterMovementComponent(CMC)는 UE5에서 ACharacter의 이동을 처리하는 핵심 컴포넌트입니다. 단순한 위치 이동을 넘어 다음을 담당합니다.
- Movement Mode 관리: Walking, Falling, Swimming, Flying, Custom 등
- 물리 기반 이동: 중력, 마찰, 공중 제어
- 네트워크 예측: 클라이언트 측 예측 + 서버 보정
- 루트 모션 통합: 애니메이션 루트 모션과 이동 로직 통합
CMC 업데이트 흐름──────────────────────────────TickComponent() └─ PerformMovement() ├─ StartNewPhysics() ← 현재 Mode에 맞는 물리 계산 │ ├─ PhysWalking() │ ├─ PhysFalling() │ ├─ PhysSwimming() │ └─ PhysCustom() ← 커스텀 구현 진입점 └─ MoveUpdatedComponent() ← 실제 위치 적용1. Movement Mode 이해
Section titled “1. Movement Mode 이해”1.1 기본 Mode 전환
Section titled “1.1 기본 Mode 전환”// 이동 모드 전환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));}1.2 커스텀 Movement Mode 열거형
Section titled “1.2 커스텀 Movement Mode 열거형”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 서브클래싱 — 커스텀 이동 구현”2.1 클래스 선언
Section titled “2.1 클래스 선언”#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;};2.2 PhysCustom 구현
Section titled “2.2 PhysCustom 구현”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;}4. 주요 파라미터 튜닝
Section titled “4. 주요 파라미터 튜닝”// 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 등 |