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를 구현하세요.