앞서 Playable 클래스를 통해 Physics pipeline을 만들었으면 이것을 활용해서 일반적인 이동을 하는 클래스와 드론과 유사한 이동을 하는 클래스를 만들어보자.
우선 일반적인 이동만 하는 클래스인 DefaultPlayable 클래스이다. 해당 클래스는 WASD 와 Shift, Space 입력을 받아 움직이고 뛰고 점프를 할 수 있다.
DefaultPlayable.h
#pragma once
#include "CoreMinimal.h"
#include "Playable.h"
#include "DefaultPlayable.generated.h"
UCLASS()
class ASSIGNMENT_API ADefaultPlayable : public APlayable
{
GENERATED_BODY()
public:
ADefaultPlayable();
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
virtual void BeginPlay() override;
virtual void PressSpace(const FInputActionValue& Value) override;
virtual void PressShift(const FInputActionValue& Value) override;
virtual void Move(const FInputActionValue& Value) override;
virtual void Look(const FInputActionValue& Value) override;
private:
UPROPERTY(EditAnywhere, Category = "Physics") float JumpScalar;
UPROPERTY(EditAnywhere, Category = "Physics") float SprintSpeed;
float OriginSpeed;
UFUNCTION() void ReleaseShift(const FInputActionValue& Value);
};
Look 함수는 마우스 X,Y 축 입력을 받아서 캐릭터의 Yaw 회전과 컨트롤러의 움직임을 제어한다.
void ADefaultPlayable::Look(const FInputActionValue& Value)
{
FVector2D LookInput = Value.Get<FVector2D>();
if (!LookInput.IsNearlyZero())
{
FRotator ActorRotation = GetActorRotation();
ActorRotation.Yaw += LookInput.X;
SetActorRotation(ActorRotation);
FRotator ControlRotation = GetControlRotation();
ControlRotation.Yaw += LookInput.X;
ControlRotation.Pitch = FMath::Clamp(ControlRotation.Pitch - LookInput.Y, -80.0f, 80.0f); // Pitch 제한
Controller->SetControlRotation(ControlRotation);
}
}
Move함수는 WASD 입력을 받고 각 입력에 맞는 방향의 힘에 MoveScalar를 곱한만큼 AddForce를 한다. 여기서 만약 캐릭터가 공중에 떠있다면 속력을 제한해준다.
void ADefaultPlayable::Move(const FInputActionValue& Value)
{
const FVector2D MoveInput = Value.Get<FVector2D>();
if (MoveInput.IsNearlyZero()) return;
FVector Forward = GetActorForwardVector();
FVector Right = GetActorRightVector();
// 입력에 따라 이동 벡터 계산
FVector InputForce = Forward * MoveInput.X + Right * MoveInput.Y;
InputForce = InputForce.GetSafeNormal() * MoveScalar;
//공중에서 속력 제한
if (!bIsGround) {
InputForce *= 0.7;
}
// 힘 적용
AddForce(InputForce * Mass);
}
PressShift를 통해 Shift 버튼을 눌렀을때 MoveScalar를 SprintSpeed로 변경하고 ReleaseShift를 통해 Shift 버튼을 떼면 MoveScalar를 다시 OriginSpeed로 변경하여 달리기를 구현했다. (SetupPlayerInputComponent으로 Input Action 바인드하는 것은 생략한다.)
void ADefaultPlayable::ReleaseShift(const FInputActionValue& Value)
{
MoveScalar = OriginSpeed;
}
void ADefaultPlayable::PressShift(const FInputActionValue& Value)
{
MoveScalar = SprintSpeed;
}
PressSpace는 Space 버튼을 눌렀을때 현재 캐릭터가 지면에 있으면 Z 방향으로 JumpScalar 만큼 힘을 가해 점프를 구현한다.
void ADefaultPlayable::PressSpace(const FInputActionValue& Value)
{
if (bIsGround) {
bIsGround = false;
AddForce({ 0,0,JumpScalar * Mass });
}
}
다음으로 드론과 유사한 움직임을 하는 AircraftPlayable 클래스 이다. 해당 클래스는 실제 드론 운행 영상을 참고해서 실제와 비슷한 움직임을 내도록 의도했다.
AircraftPlayable.h
#pragma once
#include "CoreMinimal.h"
#include "Playable.h"
#include "AircraftPlayable.generated.h"
UCLASS()
class ASSIGNMENT_API AAircraftPlayable : public APlayable
{
GENERATED_BODY()
public:
AAircraftPlayable();
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
virtual void PressShift(const FInputActionValue& Value) override;
virtual void PressSpace(const FInputActionValue& Value) override;
virtual void Move(const FInputActionValue& Value) override;
virtual void Look(const FInputActionValue& Value) override;
private:
UPROPERTY(EditAnywhere, Category = "Physics") float AngularDrag;
UPROPERTY(EditAnywhere, Category = "Physics") float AngularThreshold;
UFUNCTION() void Roll(const FInputActionValue& Value);
void AddAngularDrag(float DeltaTime);
void AddLift();
};
우선 필자가 참고한 드론의 동작 방식에 대해서 이야기 하자면 드론은 공중에 정지 상태일때, 중력과 프로펠러로 발생하는 양력이 평형을 이룬다. 따라서 중력과 양력이 평형 상태일때 드론이 회전하게 된다면 양력의 방향또한 회전한 드론의 Up 방향으로 바뀌게 되어 중력과의 평형 또한 깨지게 된다.
Tick 함수에서 중력을 상쇄하는 양력을 드론의 Up 방향으로 지속적으로 적용하는 코드를 추가해 이를 구현한다.
void AAircraftPlayable::AddLift()
{
// 양력 추가
FVector Up = GetActorUpVector();
AddForce(Up * Gravity * Mass);
}
드론은 6DOF로 움직이지만 전후좌우(드론 기준)로 움직일때와 상하로 움직일때의 속도가 각각 다르다. 이는 드론의 이동 방식에서 알 수 있는데 드론은 전후좌우로 움직일때 (프로펠러가 4개인 쿼드콥터 기준) 각 이동 방향의 프로펠러 RPM을 조절하여 움직이기 때문이다. 따라서 드론이 상승할때에는 프로펠러 4개의 양력을 받아 상승하기 때문에 프로 드론 레이싱의 영상을 보면 드론 자체를 기울여 지면과 거의 수직을 만든다음 프로펠러 4개의 추진력으로 빠르게 움직인다.
따라서 Space와 Shift를 눌러 드론의 상승과 하강을 할때 드론의 상승에는 속력의 2배가 주어지도록 하였다.
void AAircraftPlayable::PressSpace(const FInputActionValue& Value)
{
FVector Up = GetActorUpVector();
AddForce(Up * MoveScalar * Mass * 2);
}
void AAircraftPlayable::PressShift(const FInputActionValue& Value)
{
FVector Down = GetActorUpVector() * -1;
AddForce(Down * MoveScalar * Mass);
}
또한 Look 함수에서 Yaw는 이전과 동일하지만 Pitch를 마우스 Y축 값에 따라 회전하도록 하고 QE 입력에 대한 Input Action을 추가하여 QE 입력으로 Roll을 회전할 수 있도록 하였다.
void AAircraftPlayable::Look(const FInputActionValue& Value)
{
FVector2D LookInput = Value.Get<FVector2D>();
if (!LookInput.IsNearlyZero())
{
// 1. 현재 캐릭터의 회전값 업데이트
FRotator ActorRotation = GetActorRotation();
ActorRotation.Yaw += LookInput.X;
ActorRotation.Pitch = FMath::Clamp(ActorRotation.Pitch - LookInput.Y, -AngularThreshold, AngularThreshold);
SetActorRotation(ActorRotation);
// 2. 컨트롤러 회전값 업데이트
FRotator ControlRotation = GetControlRotation();
ControlRotation.Yaw += LookInput.X;
ControlRotation.Pitch = FMath::Clamp(ControlRotation.Pitch - LookInput.Y, -AngularThreshold, AngularThreshold);
Controller->SetControlRotation(ControlRotation);
}
}
void AAircraftPlayable::Roll(const FInputActionValue& Value)
{
// QE 입력에 따른 Roll 회전
const float MoveInput = Value.Get<float>();
FRotator ActorRotation = GetActorRotation();
ActorRotation.Roll = FMath::Clamp(ActorRotation.Roll + MoveInput, -AngularThreshold, AngularThreshold);
SetActorRotation(ActorRotation);
}
조금 더 사실적인 움직임을 위해 드론이 기울어져 있을때 중력과 평형을 이루기 위해 기울기가 되돌아가는 각 Damping을 추가하였다. 원래는 Pitch도 각 Damping이 적용되어야 하지만 마우스 Y축으로 Pitch가 조절되기 때문에 조작이 불편하여 Pitch는 제외를 하였다.
void AAircraftPlayable::AddAngularDrag(float DeltaTime)
{
FRotator ActorRotation = GetActorRotation();
// 각 댐핑 적용
//ActorRotation.Pitch *= FMath::Pow(1 - AngularDrag, DeltaTime);
ActorRotation.Roll *= FMath::Pow(1 - AngularDrag, DeltaTime);
SetActorRotation(ActorRotation);
}
'Unreal 5 > Study' 카테고리의 다른 글
2. [Unreal 5 / C++] 2D 절차적 맵 생성 알고리즘 (미로 생성 알고리즘) (0) | 2025.03.07 |
---|---|
1. [Unreal 5 / C++] 2D 절차적 맵 생성 알고리즘 (미로 생성 알고리즘) (0) | 2025.02.21 |
[Unreal 5 / C++] 2. Pawn 클래스로 3D 캐릭터 만들기 - Collision (1) | 2025.01.31 |
[Unreal 5 / C++] 1. Pawn 클래스로 3D 캐릭터 만들기 - Physics base (0) | 2025.01.27 |
[Unreal 5/C++] 회전 발판과 움직이는 장애물 퍼즐 스테이지 (0) | 2025.01.22 |