[UE5] Unreal Engine 5 길라잡이 - 8. 폰 조종하기 (삼인칭 샘플 프로젝트 분석)

세동네·2022년 6월 24일
0
post-thumbnail

이 시리즈는 이득우의 언리얼 C++ 게임 개발의 정석을 바탕으로 작성되었습니다.

플레이어의 분신이 되는 (Pawn)은 컨트롤러에 빙의(Possess)되어 동작한다. 플레이어가 입력은 플레이어 컨트롤러를 거쳐 플레이어 컨트롤러가 빙의한 에 전달되는데, 플레이어 컨트롤러에서 입력을 처리하는 코드가 있다면 에 전달되지 않는다. 플레이어 컨트롤러에 대한 추가적인 내용은 이 포스팅에서 더 살펴볼 수 있다.

만약 플레이어가 조종하는 이 여러 개라면 마다 입력받아 처리하는 방식이 다를 수 있기 때문에 플레이어 컨트롤러에서 일괄적으로 을 조종하는 코드를 구현하기보다 클래스 자체에 필요한 형태의 입력 처리를 구현하는 것이 일반적이다.

· 삼인칭 프로젝트 분석

이번 포스팅에선 언리얼 샘플 프로젝트 중 하나인 삼인칭(ThirdPerson) 프로젝트에서 플레이어 컨트롤이 어떻게 구현되어 있는지 살펴보고, 이를 C++ 스크립트로 작성해 볼 것이다. 해당 프로젝트를 C++ 클래스로 생성하자.

삼인칭 프로젝트는 PC 기준 마우스와 키보드를 모두 사용한 플레이어 컨트롤로 구현되어 있다. 키보드는 이동과 점프, 마우스는 회전을 담당한다. 우선 어떤 요소들로 컨트롤을 구현하였는지 살펴보기 위해 게임모드를 열어보자. 프로젝트는 'ThirdPersonCpp'라는 이름으로 생성하였다.

· 게임 모드

디폴트 폰 클래스를 제외하곤 기본 클래스들로 설정된 것으로 보인다. 좀 더 자세히 알기 위해 게임모드 클래스 파일로 이동해보면

#include "ThirdPersonCppGameMode.h"
#include "ThirdPersonCppCharacter.h"
#include "UObject/ConstructorHelpers.h"

AThirdPersonCppGameMode::AThirdPersonCppGameMode()
{
   // set default pawn class to our Blueprinted character
   static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter"));
   if (PlayerPawnBPClass.Class != NULL)
   {
      DefaultPawnClass = PlayerPawnBPClass.Class;
   }
}

해당 코드가 전부이다. 게임모드를 구성하는 클래스 중 디폴트 폰 클래스만 새롭게 지정해주었다는 것은 모든 컨트롤 구현이 폰 클래스에 되어 있다는 것을 뜻한다.

지정돼있는 디폴트 폰 클래스인 BP_ThirdPersonCharacter 블루프린트 클래스는 ThirdPersonCppCharacter 클래스의 자식 클래스이다.

해당 블루프린트 클래스의 노드 그래프가 비어있는 것을 봐선 추후 블루프린트를 활용한 확장을 염두에 두고 블루프린트화한 것으로 보인다. 아직까지 기능 구현은 부모 클래스인 ThirdPersonCppCharacter에 되어 있다.

· 디폴트 폰 클래스.h

// ThirdPersonCppCharacter.h

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "ThirdPersonCppCharacter.generated.h"

UCLASS(config=Game)
class AThirdPersonCppCharacter : public ACharacter
{
   GENERATED_BODY()

   /** Camera boom positioning the camera behind the character */
   UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
   class USpringArmComponent* CameraBoom;

   /** Follow camera */
   UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
   class UCameraComponent* FollowCamera;
public:
   AThirdPersonCppCharacter();

   /** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
   UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Input)
   float TurnRateGamepad;

헤더파일을 살펴 보자.

우선 클래스에 리플렉션이 있고, 클래스에서 config 지정자를 확인할 수 있다.

  • 클래스 config 지정자

    config 지정자는 클래스 내의 일부 프로퍼티를 [환경설정 파일명].ini라는 특정 환경설정 파일에 저장하여 프로젝트 로드시 초기화할 프로퍼티의 값을 지정하는 데에 사용한다. 같은 환경설정 파일에 접근하는 클래스끼리는 프로퍼티의 값을 공유할 수 있게 된다.

    이때 환경설정 파일에 저장할 프로퍼티는 프로퍼티 선언에서 config 지정자를 삽입해줘야 한다. 현재 소스코드에는 config 지정자를 포함한 프로퍼티가 없기 때문에 추후 프로퍼티를 환경설정 파일에 저장할 필요가 있을 경우를 대비한 것으로 보인다.

  • 프로퍼티 BlueprintReadOnly 지정자

    이름에서 쉽게 유추할 수 있다. 해당 프로퍼티를 블루프린트에서 접근할 수 있게 하고, 읽기 전용이므로 블루프린트 에디터에서 수정은 불가능하다.

  • 프로퍼티 meta 지정자

    메타데이터 지정자로, 언리얼 오브젝트를 선언할 때 엔진 및 에디터의 다양한 부분에 대한 작동 방식을 지정할 수 있다. 에디터 빌드 단계에서 주로 사용되고, 게임 로직에 메타데이터를 사용해선 안 된다.

    그 중 AllowPrivateAccess 태그는 private 접근지시자를 가진 요소를 에디터 디테일 패널에서 접근할 수 있게 할지 여부를 결정하는 태그다.

// ThirdPersonCppCharacter.h
...

protected:

   /** Called for forwards/backward input */
   void MoveForward(float Value);

   /** Called for side to side input */
   void MoveRight(float Value);

   /** 
    * Called via input to turn at a given rate. 
    * @param Rate   This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
    */
   void TurnAtRate(float Rate);

   /**
    * Called via input to turn look up/down at a given rate. 
    * @param Rate   This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
    */
   void LookUpAtRate(float Rate);

   /** Handler for when a touch input begins. */
   void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);

   /** Handler for when a touch input stops. */
   void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);

protected:
   // APawn interface
   virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
   // End of APawn interface
   
public:
   /** Returns CameraBoom subobject **/
   FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return  CameraBoom; }
   /** Returns FollowCamera subobject **/
   FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }

};
  • SetupPlayerInputComponent()

    폰 클래스가 기본적으로 보유하는 함수로, 입력에 관련된 설정을 처리해주기 위한 함수이다. 일반적으로 이곳에서 입력하는 키와 실제 기능을 연결하는 바인딩 처리를 해준다.

    실제 이동 및 회전 함수는 위의 'Move' / 'Turn' 등의 함수에 구현해 줄 것이다.

· 디폴트 폰 클래스.cpp

- 생성자

// ThirdPersonCppCharacter.cpp

AThirdPersonCppCharacter::AThirdPersonCppCharacter()
{
   // Set size for collision capsule
   GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

   // set our turn rate for input
   TurnRateGamepad = 50.f;

   // Don't rotate when the controller rotates. Let that just affect the camera.
   bUseControllerRotationPitch = false;
   bUseControllerRotationYaw = false;
   bUseControllerRotationRoll = false;

   // Configure character movement
   GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...   
   GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); // ...at this rotation rate

   // Note: For faster iteration times these variables, and many more, can be tweaked in the Character Blueprint
   // instead of recompiling to adjust them
   GetCharacterMovement()->JumpZVelocity = 700.f;
   GetCharacterMovement()->AirControl = 0.35f;
   GetCharacterMovement()->MaxWalkSpeed = 500.f;
   GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
   GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;

   // Create a camera boom (pulls in towards the player if there is a collision)
   CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
   CameraBoom->SetupAttachment(RootComponent);
   CameraBoom->TargetArmLength = 400.0f; // The camera follows at this distance behind the character   
   CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller

   // Create a follow camera
   FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
   FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
   FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm

   // Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) 
   // are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++)
}

캐릭터 액터는 기본적으로 콜라이더(캡슐), 무브먼트, 카메라암, 카메라 컴포넌트를 가지게 되는데, 생성자에서 이러한 컴포넌트들의 프로퍼티 초기화와 필요한 기능을 설정해준다.

주석 처리가 잘 돼있기 때문에 부가적인 설명이 필요한 코드만 설명하겠다.

  • 컨트롤 회전

    하드웨어 키 입력으로 회전 명령이 들어오면 플레이어 컨트롤러가 회전한다. 플레이어 컨트롤러의 회전값을 컨트롤 회전이라 하며, 플레이어 컨트롤러는 컨트롤 회전을 자신이 빙의한 폰과 PlayerCameraManager에 전달하여 각각의 회전을 조종한다.

    폰에 카메라 컴포넌트가 있다면 디테일 패널에서 카메라 세팅 섹션을 발견할 수 있고, 이곳에서 폰을 위한 카메라 회전을 제어할 수 있다.

    • bUseControllerRotation ('컨트롤러 회전 사용' 프로퍼티)

      위 코드에서 살펴볼 수 있는 bUseControllerRotation 계열 속성들은 폰의 컨트롤 회전을 관장하는 속성이다. 해당 속성이 true라면 설정된 방향의 컨트롤 회전에 따라 폰 메시 자체가 회전한다. Roll/Pitch/Yaw는 각각 X/Y/Z축을 기준으로 회전하는 방향을 말한다.

      해당 속성은 폰 액터 섹션에 있는 프로퍼티들로 에디터 상에서 설정할 수 있다. 위의 소스코드는 모든 속성이 false이므로 카메라만 컨트롤 회전에 반응하고 폰은 반응하지 않는다.

    • bOrientRotationToMovement ('무브먼트 방향으로 회전 조정' 프로퍼티)

      해당 속성은 폰이 움직일 때 바라보고 있는 방향으로 폰을 회전시킬지 여부를 결정한다. 해당 속성이 설정돼있다면 폰의 컨트롤 회전 설정을 꺼 두었다라도 이동할 때는 컨트롤 회전 방향에 따라 이동한다. 즉, 메시가 화면의 정면을 바라보고 이동한다.

      위 사진은 두 경우 모두 앞쪽 방향키를 누른 상태이며, 왼쪽은 무브먼트 방향으로 회전 조정 프로퍼티가 true인 경우, 오른쪽은 false인 경우이다.

    • SpringArm 컴포넌트

      CameraBoom이라는 이름으로 생성한 SpringArm 컴포넌트는 일정 거리를 유지하며 폰을 추적하는 카메라가 물체와 충돌했을 때 카메라를 폰에 가깝게 이동시켜 화면에서 물체가 폰을 가리지 않도록 하는 기능 등을 제공한다.

    • USpringArmComponent->bUsePawnControlRotation ('폰 제어 회전 사용' 프로퍼티)

      SpringArm 컴포넌트의 폰 제어 회전 사용 속성을 설정하면 컨트롤 회전에 따라 실시간으로 폰의 스프링암의 트랜스폼이 회전한다. SpringArm 컴포넌트는 폰과 카메라를 연결하기 때문에 카메라가 폰을 공전하듯이 거리를 유지하며 회전하는 것처럼 보인다. 피치/요/롤 각 방향에 대한 상속을 해제하면 해당 방향에 대한 카메라 회전이 제한된다.

    • UCameraComponent->bUsePawnControlRotation

      SpringArm 컴포넌트의 속성과 같은 이름이지만 Camera 컴포넌트에 속했기 때문에 별개의 동작을 위한 속성이다. 기본적으로 폰이 SpringArm 컴포넌트를 가진다면 폰이 SpringArm 컴포넌트의 부모로, SpringArmCamera 컴포넌트의 부모로 설정된다.

      따라서 카메라의 움직임은 스프링암에 종속되는데, 스프링암이 폰 제어 회전 사용 속성 설정이 되어있지 않다면 컨트롤 회전의 제어에서 벗어난 스프링암에 따라 카메라도 컨트롤 회전의 영향을 받지 않는다. 하지만 Camera 컴포넌트의 bUsePawnControlRotation 속성을 설정해주면 카메라가 스프링암의 제어에서 벗어나 컨트롤 회전의 영향을 받으면서 폰이 바라보는 방향으로 카메라를 회전시킨다.

- 입력 처리(키 바인딩)

플레이어가 입력하는 것에 폰이 반응하게 하려면 우선 키 바인딩(Binding)이 필요하다. 우선 키보드나 조이스틱과 같은 장치에서의 입력을 게임이 구별할 수 있는 입력값으로 변환하는 '매핑' 작업 후 매핑된 키가 특정한 기능을 발휘하게 하는 것을 '바인딩'이라 한다.

바인딩을 알아보기 전에 키 매핑을 먼저 알아보자.

  • 키 매핑

    키 매핑은 입력 장치로부터 온 신호를 게임 엔진에서 구분 가능한 입력값으로 변환하는 작업을 말한다.

    키 매핑은 축 매핑(Axis Mapping)과 액션 매핑(Action Mapping)으로 구분되는데, 축 매핑은 일반적으로 방향키나 조이스틱 레버 같은 이동을, 액션 매핑은 공격이나 점프 같은 액션 이벤트를 발생시키는 키 입력을 가리킨다.

    언리얼 에디터의 [프로젝트 세팅 - 엔진 - 입력] 설정에서 이를 지정해줄 수 있다.

    언리얼 엔진에는 기본적인 입력에 대한 매핑이 구현되어 있다.

    WASD에 대한 이동은 Move Forward / Backward, Move Right / Left라는 이름으로, 마우스 이동 회전은 Turn Right / Left Mouse, Look Up / Down Mouse라는 이름으로 매핑되어 있다. 구현해 놓았으며, 매핑된 키를 입력하면 AxisName과 우측에 지정한 Scale 값이 게임에 전달된다.

키 매핑으로 입력 장치의 신호를 원하는 입력값으로 변환했다면 키 입력이 발생했을 때 즉, 원하는 입력값이 발생했을 때 시스템의 어떤 오브젝트가 어떤 일을 해야 하는지 결정할 필요가 있다. 이것이 키 바인딩이다.

// ThirdPersonCppCharacter.cpp

///////////////////// Input
void AThirdPersonCppCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
   // Set up gameplay key bindings
   check(PlayerInputComponent);
   PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
   PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);

   PlayerInputComponent->BindAxis("Move Forward / Backward", this, &AThirdPersonCppCharacter::MoveForward);
   PlayerInputComponent->BindAxis("Move Right / Left", this, &AThirdPersonCppCharacter::MoveRight);

   // We have 2 versions of the rotation bindings to handle different kinds of devices differently
   // "turn" handles devices that provide an absolute delta, such as a mouse.
   // "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
   PlayerInputComponent->BindAxis("Turn Right / Left Mouse", this, &APawn::AddControllerYawInput);
   PlayerInputComponent->BindAxis("Turn Right / Left Gamepad", this, &AThirdPersonCppCharacter::TurnAtRate);
   PlayerInputComponent->BindAxis("Look Up / Down Mouse", this, &APawn::AddControllerPitchInput);
   PlayerInputComponent->BindAxis("Look Up / Down Gamepad", this, &AThirdPersonCppCharacter::LookUpAtRate);

   // handle touch devices
   PlayerInputComponent->BindTouch(IE_Pressed, this, &AThirdPersonCppCharacter::TouchStarted);
   PlayerInputComponent->BindTouch(IE_Released, this, &AThirdPersonCppCharacter::TouchStopped);
}

캐릭터 액터는 입력 바인딩을 위한 SetupPlayerInputComponent() 함수를 제공한다. 일반적으로 바인딩은 Bind~([매핑명], [처리할 오브젝트], [바인딩 함수])와 같은 매개변수 형태를 가지며, 바인딩 종류에 따라 입력 이벤트를 받기도 한다.

앞서 살펴본 키 매핑에서 축 매핑과 액션 매핑이 구분돼있는 것에 따라 실제 바인딩도 축 바인딩과 액션 바인딩을 위한 함수가 구분돼있다.

  • check() 매크로

    언리얼 엔진 프레임워크에는 어설션(Assertion)이라는 런타임에서 주어진 코드를 검사하는 기능을 제공한다. 어설션 매크로의 한 가지 형태인 check() 매크로는 매개변수로 전달하는 표현식의 결과가 false일 경우 프로그램 실행을 중단한다.

    위 코드에선 PlayerInputComponentnullptr인지를 검사하는 표현식이며, nullptr일 경우 비정상적인 경우라 판단하며 프로그램 실행을 중단하기 위함이다.

  • Bind~() 바인딩 함수

    바인딩 함수는 축 바인딩, 액션 바인딩, 터치 바인딩으로 구분되며, 사용하는 입력의 종류나 입력 장치의 종류에 따라 구분된다. 기본적으로 액션, 터치 입력은 단일 이벤트, 축 입력은 지속 이벤트를 발생시킨다.

    축 입력으로 발생하는 것을 Axis 이벤트라 하는데, Tick 이벤트와 별개로 매 프레임마다 호출된다. 플레이어의 입력이 액터의 행동을 결정하기 때문에 같은 프레임에 두 이벤트가 호출되더라도 Axis 이벤트로 입력값을 먼저 확인하고 그것을 Tick 이벤트에서 활용해 액터의 최종 행동을 결정한다.

    단일 이벤트를 일으키는 입력은 형태에 따라 서로 다른 입력 이벤트 처리가 가능하며, EInputEvent라는 열거자로 그 종류가 선언돼있다.

    //	EInputEvent
    
    UENUM( BlueprintType, meta=(ScriptName="InputEventType"))
    enum EInputEvent
    {
    	IE_Pressed              =0,
    	IE_Released             =1,
    	IE_Repeat               =2,
    	IE_DoubleClick          =3,
    	IE_Axis                 =4,
    	IE_MAX                  =5,
    };

    키 누름(Press), 놓음(Release), 더블 클릭(Double Click)등 여러 형태의 입력을 지원하기 때문에 필요한 형태의 입력 이벤트를 선택할 수 있다.

    키보드나 조이스틱처럼 레이아웃의 형태가 정해진 경우 특정 키 입력에 따른 매핑 이름을 정해주지만, 터치 입력에 대한 바인딩은 입력 이벤트 형태에 따라 우선 입력을 받고 함수 안에서 터치 좌표에 따라 미리 지정한 입력 인덱스를 정보로 전달 받는다.

    // ThirdPersonCppCharacter.cpp
    
    void AThirdPersonCppCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
    {
    	Jump();
    }
    
    void AThirdPersonCppCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
    {
    	StopJumping();
    }

- 바인딩 함수 구현

앞서 매핑한 입력값들에 바인딩한 함수들의 구현부를 살펴볼 것이다. 이 삼인칭 캐릭터 액터에는 회전, 이동을 위한 함수가 구현되어 있다. Space Bar 키를 입력해 점프할 수 있지만, 이는 직접 구현하지 않고 ACharacter::Jump()라는 엔진에 이미 구현된 함수를 사용한다.

// ThirdPersonCppCharacter.cpp

void AThirdPersonCppCharacter::TurnAtRate(float Rate)
{
	// calculate delta for this frame from the rate information
	AddControllerYawInput(Rate * TurnRateGamepad * GetWorld()->GetDeltaSeconds());
}

void AThirdPersonCppCharacter::LookUpAtRate(float Rate)
{
	// calculate delta for this frame from the rate information
	AddControllerPitchInput(Rate * TurnRateGamepad * GetWorld()->GetDeltaSeconds());
}

AddControllerInput() 함수는 특정 방향으로의 컨트롤 회전을 발생시키는 함수이다. 무브먼트 컴포넌트의 bUseControllerRoataion 프로퍼티는 모든 방향에 대해 false로 설정해 회전 입력에 따라선 액터가 회전하지 않지만, bOrientRotationToMovement 프로퍼티를 설정해 놓았기 때문에 이동 입력이 들어올 때 컨트롤 회전 방향에 따라 액터를 회전시킨다.

회전 속도를 TurnRateGamepad 변수로 결정하고 실행 환경에 따라 게임의 프레임수가 다른 것을 고려하여 GetDeltaSeconds()로 모든 실행 환경에서 같은 시간에 회전하는 양이 갖도록 설정해주었다.

  • GetDeltaSeconds()

    직전 프레임에서 이번 프레임까지의 실행 시간을 반환하는 함수이다. 1초간 실행하는 프레임 수가 적은 환경에선 해당 함수의 값이 크고, 프레임 수가 많은 환경에선 함수가 반환하는 값이 작다.

    이를 회전이나 이동값에 활용하면 프레임 수가 적은 환경에서 한 번에 더 많이 변화를 줄 수 있으므로 여러 프레임의 변화량을 합치면 결과적으로 서로 다른 프레임수를 가지는 모든 환경에서 거의 똑같은 변화량을 가지도록 설계할 수 있다.

// ThirdPersonCppCharacter.cpp

void AThirdPersonCppCharacter::MoveForward(float Value)
{
	if ((Controller != nullptr) && (Value != 0.0f))
	{
		// find out which way is forward
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0);

		// get forward vector
		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		AddMovementInput(Direction, Value);
	}
}

void AThirdPersonCppCharacter::MoveRight(float Value)
{
	if ( (Controller != nullptr) && (Value != 0.0f) )
	{
		// find out which way is right
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0);
	
		// get right vector 
		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
		// add movement in that direction
		AddMovementInput(Direction, Value);
	}
}

이동 구현 코드는 우선 GetControlRotation() 함수로 컨트롤 회전을 불러와 Z축(Yaw) 기준으로의 회전값만 추출하였다. 이후 추출한 회전값을 FRotationMatrix 클래스 타입으로 변환하는데, 클래스 식별자가 44Matrix로 정의되어있어 동차좌표계를 이용한 행렬로 변환했다고 유추할 수 있다. 동차좌표계는 행렬로 표현된 좌표계 연산을 쉽고 빠르게 처리하기 위한 좌표계를 말한다.

행렬에서 뽑아낸 전방, 우측 방향에 대한 방향 단위 벡터를 구하고 나서야 마침내 주어진 방향에 대해 이동하는 AddMovementInput() 함수를 활용한다. 처음 시작이 컨트롤 회전을 불러와 이동 방향을 결정하였기 때문에 액터가 바라보는 방향이 아닌 카메라가 바라보는 방향 즉, 컨트롤 회전에 따라 액터의 이동 방향으로 이동하도록 설계되었다는 것을 알 수 있다.

AddMovementInput() 함수는 해당 방향으로 어떻게 움직일 것인지 두 번째 인자에 전달하는데, 양수이면 해당 방향, 음수이면 반대 방향으로 진행한다. 해당 함수로는 이동속도를 변경할 수 없고, UCharacterMovementComponent 컴포넌트의 MaxWalkSpeed 프로퍼티를 변경해야 이동속도를 변경할 수 있다.

// ThirdPersonCppCharacter.cpp

AThirdPersonCppCharacter::AThirdPersonCppCharacter(){
	...
    
	GetCharacterMovement()->MaxWalkSpeed = 500.f;
    
    ...
}

해당 캐릭터 클래스는 생성자에서 이동속도를 500.f로 이미 초기화해주었다.

  • AddMovementInput() 함수 유의점

    폰을 특정 방향으로 이동시키는 AddMovementInput() 함수는 UPawnMovementComponent 계열 컴포넌트를 반드시 포함해야 한다.

    삼인칭 프로젝트의 캐릭터 액터는 ACharacter 클래스를 상속하는 캐릭터 타입 액터인데, 해당 액터 클래스는 기본적으로 UCharacterMovementComponent 타입의 무브먼트 컴포넌트를 포함하기 때문에 따로 컴포넌트를 추가해줄 필요는 없다.

    이는 UPawnMovementComponent 컴포넌트를 상속하는 즉, 같은 계열의 컴포넌트이기에 AddMovementInput() 함수를 사용할 수 있다.

    아래는 Pawn 클래스에 정의된 AddMovementInput() 함수의 구현부이다. UPawnMovementComponent 컴포넌트를 활용한다는 것을 확인할 수 있고, 다형성으로 UPawnMovementComponent 컴포넌트를 상속하는 클래스들로 대체하는 것이 가능하다.

    // Pawn.cpp
    
    void APawn::AddMovementInput(FVector WorldDirection, float ScaleValue, bool bForce /*=false*/)
    {
      UPawnMovementComponent* MovementComponent = GetMovementComponent();
      if (MovementComponent)
      {
         MovementComponent->AddInputVector(WorldDirection * ScaleValue, bForce);
      }
       
       ...
    }

· 참고

언리얼 엔진 Assert
플레이어 입력 매핑
[UE Document] Input

0개의 댓글