PART 5 · 강의 7/7

Character Movement 확장

커스텀 이동 모드: 대시, 글라이딩, 클라이밍

01

커스텀 이동 모드

CharacterMovementComponent 확장

언리얼의 CharacterMovementComponent는 강력하지만, RPG 게임에서 필요한 대시, 글라이딩, 벽타기 같은 기능은 직접 구현해야 합니다. MOVE_Custom 모드를 활용하여 확장합니다.

💨

Dashing

빠른 방향 이동
무적 프레임 가능

🪂

Gliding

천천히 하강
원신 스타일

🧗

Climbing

벽면 이동
젤다 BotW 스타일

02

커스텀 이동 컴포넌트

UCharacterMovementComponent 상속

MyCharacterMovementComponent.h #pragma once #include "CoreMinimal.h" #include "GameFramework/CharacterMovementComponent.h" #include "MyCharacterMovementComponent.generated.h" // 커스텀 이동 모드 정의 UENUM(BlueprintType) enum class ECustomMovementMode : uint8 { CMOVE_None UMETA(Hidden), CMOVE_Dashing UMETA(DisplayName = "Dashing"), CMOVE_Gliding UMETA(DisplayName = "Gliding"), CMOVE_Climbing UMETA(DisplayName = "Climbing"), CMOVE_MAX UMETA(Hidden) }; UCLASS() class MYGAME_API UMyCharacterMovementComponent : public UCharacterMovementComponent { GENERATED_BODY() public: UMyCharacterMovementComponent(); // ===== 대시 설정 ===== UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom Movement|Dash") float DashDistance = 600.f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom Movement|Dash") float DashDuration = 0.25f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom Movement|Dash") float DashCooldown = 1.0f; // ===== 글라이딩 설정 ===== UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom Movement|Glide") float GlideGravityScale = 0.25f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom Movement|Glide") float GlideAirControl = 0.8f; // ===== 대시 함수 ===== UFUNCTION(BlueprintCallable, Category = "Movement") void StartDash(); UFUNCTION(BlueprintCallable, Category = "Movement") bool CanDash() const; // ===== 글라이딩 함수 ===== UFUNCTION(BlueprintCallable, Category = "Movement") void StartGlide(); UFUNCTION(BlueprintCallable, Category = "Movement") void StopGlide(); UFUNCTION(BlueprintPure, Category = "Movement") bool IsGliding() const; UFUNCTION(BlueprintPure, Category = "Movement") bool IsCustomMovementMode(ECustomMovementMode InMode) const; protected: virtual void InitializeComponent() override; virtual void OnMovementModeChanged( EMovementMode PreviousMovementMode, uint8 PreviousCustomMode) override; virtual void PhysCustom(float DeltaTime, int32 Iterations) override; virtual float GetMaxSpeed() const override; private: // 대시 상태 FVector DashDirection; float DashTimeRemaining; float DashCooldownRemaining; void PhysDash(float DeltaTime, int32 Iterations); void EndDash(); // 글라이딩 상태 float SavedGravityScale; float SavedAirControl; void PhysGlide(float DeltaTime, int32 Iterations); };
03

대시 구현

빠른 방향 이동

대시 파라미터

파라미터 기본값 설명
DashDistance 600 총 이동 거리 (유닛)
DashDuration 0.25 대시 지속 시간 (초)
DashCooldown 1.0 재사용 대기 시간 (초)
MyCharacterMovementComponent.cpp UMyCharacterMovementComponent::UMyCharacterMovementComponent() { NavAgentProps.bCanCrouch = true; NavAgentProps.bCanJump = true; DashTimeRemaining = 0.f; DashCooldownRemaining = 0.f; } void UMyCharacterMovementComponent::InitializeComponent() { Super::InitializeComponent(); SavedGravityScale = GravityScale; SavedAirControl = AirControl; } bool UMyCharacterMovementComponent::CanDash() const { return IsMovingOnGround() && DashCooldownRemaining <= 0.f && !IsFalling(); } void UMyCharacterMovementComponent::StartDash() { if (!CanDash()) return; // 대시 방향 결정 (입력 방향 또는 전방) FVector InputDirection = GetLastInputVector(); if (InputDirection.IsNearlyZero()) { InputDirection = CharacterOwner->GetActorForwardVector(); } DashDirection = InputDirection.GetSafeNormal2D(); DashTimeRemaining = DashDuration; SetMovementMode(MOVE_Custom, static_cast<uint8>(ECustomMovementMode::CMOVE_Dashing)); } void UMyCharacterMovementComponent::EndDash() { DashCooldownRemaining = DashCooldown; SetMovementMode(MOVE_Walking); } void UMyCharacterMovementComponent::PhysDash( float DeltaTime, int32 Iterations) { if (DashTimeRemaining <= 0.f) { EndDash(); return; } DashTimeRemaining -= DeltaTime; // 대시 속도 계산 const float DashSpeed = DashDistance / DashDuration; Velocity = DashDirection * DashSpeed; Velocity.Z = 0.f; // 수평 이동만 // 이동 수행 FHitResult Hit; SafeMoveUpdatedComponent( Velocity * DeltaTime, UpdatedComponent->GetComponentRotation(), true, Hit); if (Hit.IsValidBlockingHit()) { // 벽에 부딪히면 대시 종료 EndDash(); } }
04

글라이딩 구현

공중에서 천천히 하강

MyCharacterMovementComponent.cpp (계속) void UMyCharacterMovementComponent::StartGlide() { if (!IsFalling()) return; SavedGravityScale = GravityScale; SavedAirControl = AirControl; SetMovementMode(MOVE_Custom, static_cast<uint8>(ECustomMovementMode::CMOVE_Gliding)); } void UMyCharacterMovementComponent::StopGlide() { if (IsCustomMovementMode(ECustomMovementMode::CMOVE_Gliding)) { GravityScale = SavedGravityScale; AirControl = SavedAirControl; SetMovementMode(MOVE_Falling); } } bool UMyCharacterMovementComponent::IsGliding() const { return IsCustomMovementMode(ECustomMovementMode::CMOVE_Gliding); } bool UMyCharacterMovementComponent::IsCustomMovementMode( ECustomMovementMode InMode) const { return MovementMode == MOVE_Custom && CustomMovementMode == static_cast<uint8>(InMode); } void UMyCharacterMovementComponent::OnMovementModeChanged( EMovementMode PreviousMovementMode, uint8 PreviousCustomMode) { Super::OnMovementModeChanged(PreviousMovementMode, PreviousCustomMode); // 글라이딩 시작 시 중력 조정 if (IsCustomMovementMode(ECustomMovementMode::CMOVE_Gliding)) { GravityScale = GlideGravityScale; AirControl = GlideAirControl; } } void UMyCharacterMovementComponent::PhysGlide( float DeltaTime, int32 Iterations) { // 기본 Falling 물리 사용 (중력만 감소) PhysFalling(DeltaTime, Iterations); // 땅에 닿으면 글라이딩 종료 if (IsMovingOnGround()) { StopGlide(); } } void UMyCharacterMovementComponent::PhysCustom( float DeltaTime, int32 Iterations) { Super::PhysCustom(DeltaTime, Iterations); switch (static_cast<ECustomMovementMode>(CustomMovementMode)) { case ECustomMovementMode::CMOVE_Dashing: PhysDash(DeltaTime, Iterations); break; case ECustomMovementMode::CMOVE_Gliding: PhysGlide(DeltaTime, Iterations); break; default: break; } // 쿨다운 업데이트 if (DashCooldownRemaining > 0.f) { DashCooldownRemaining -= DeltaTime; } } float UMyCharacterMovementComponent::GetMaxSpeed() const { if (IsCustomMovementMode(ECustomMovementMode::CMOVE_Dashing)) { return DashDistance / DashDuration; } return Super::GetMaxSpeed(); }
스태미나 연동

글라이딩 중 GAS의 스태미나 Attribute를 소모하고, 스태미나가 0이 되면 StopGlide를 호출하여 자연스럽게 추락하도록 구현할 수 있습니다.

SUMMARY

핵심 요약

  • MOVE_Custom 모드 — CharacterMovementComponent의 커스텀 이동 모드를 활용하여 새로운 이동 방식을 추가합니다.
  • PhysCustom 오버라이드 — 커스텀 모드별로 물리 업데이트 로직을 구현합니다.
  • 대시 구현 — 고정 속도로 짧은 시간 동안 이동하며, 벽 충돌 시 중단됩니다.
  • 글라이딩 구현 — 중력과 Air Control을 조정하여 천천히 하강하며 조종할 수 있습니다.
  • GetMaxSpeed 오버라이드 — 모드별로 다른 최대 속도를 반환합니다.
PRACTICE

도전 과제

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

실습 1: 커스텀 Movement Mode 추가

UCharacterMovementComponent를 상속하여 RPG에 필요한 커스텀 이동 모드(Climbing, Swimming, Flying)를 추가하세요. PhysCustom()에서 각 모드의 물리 로직을 구현하세요.

실습 2: 대시/회피 이동 구현

CharacterMovementComponent에서 Launch 또는 AddImpulse를 활용하여 RPG 전투 대시/회피를 구현하세요. 무적 프레임(iFrame)과 쿨다운을 GAS AbilityTag로 관리하세요.

심화 과제: 네트워크 동기화 커스텀 무브먼트

FSavedMove_Character를 상속하여 커스텀 이동 데이터를 네트워크에서 동기화하세요. 클라이언트 예측과 서버 보정이 올바르게 작동하도록 GetPredictionData_Client()와 ServerMove를 구현하세요.