[C++] Move, Look 함수 정의 / PlayerController 클래스
카메라 시점에 따른 캐릭터 이동 수정
저번에는 Move() 함수에서 Controller의 Rotation값 전체를 뽑아와서 방향을 만들었다.
이렇게 되면 Camera가 위 아래로 움직였을 때 방향을 가져오는 부분에 문제가 생긴다.
따라서 Controller의 Z축 회전(Yaw)만 가져와서 방향을 만들도록 수정했다.
void AGPCCharacter::MoveFB(float const Value)
{
//먼저 카메라의 위치를 받아와줄것임(카메라는 Controller에 있음)
FRotator const Rotator = FRotator(0, GetControlRotation().Yaw, 0);
FVector const Direction = Rotator.Quaternion().GetForwardVector();
//방향, Scale
AddMovementInput(Direction, Value);
}
언리얼 C++에서 FRotator는 (Pitch(Y), Yaw(Z), Roll(X))의 순서로 되어있으니 주의하자.
SpringArm / Camera Component 설정하기
GPCCharacter.cpp에서 SpringArm과 Camera 인스턴스를 추가해주었었다.
언리얼 에디터의 레벨에 BP_Character를 올려서 움직여보면 카메라가 따로 노는 것을 볼 수 있다.
이는 SpringArm에 Camera가 붙어있지 않기 때문이다. 따라서 SpringArm에 Camera Component를 붙여볼 것이다.
그리고 시점 변환에 따라 캐릭터가 움직이도록 해주기 위해서 먼저 생성자에서 옵션을 설정해주었다.
#include "GameFramework/CharacterMovementComponent.h"
AGPCCharacter::AGPCCharacter()
{
PrimaryActorTick.bCanEverTick = true;
bUseControllerRotationYaw = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
CreateDefaultSubobjectAuto(SpringArm)->SetupAttachment(GetRootComponent());
SpringArm->bUsePawnControlRotation = true;
CreateDefaultSubobjectAuto(Cam)->SetupAttachment(SpringArm);
}
먼저 bUseControllerRotationYaw : Controller의 Yaw Rotation의 사용을 꺼주었다.
그리고 CharacterMovementComponent.h 파일을 포함해주어, 그 안에 있는 OrientRotationToMovement는 활성화해준다.
그리고 SpringArm 인스턴스 생성 후, 안에 있는 UsePawnControlRotation을 활성화했다.
이렇게 옵션을 설정해준 뒤, 빌드하여 블루프린트 상에서 해당하는 옵션들이 제대로 적용되었는지 꼭 확인해주자.
* 미적용 되었다면 그냥 언리얼 에디터를 껐다 켜보도록 하자.
SetupAttachment(..., ...);
이제 Component를 특정 Component에게 붙여주기 위해서는 SetupAttachment() 함수를 사용하면 된다.
SetupAttachment(SceneComponent, SocketName);
SceneComponent에는 해당 컴포넌트를 어디 컴포넌트에 붙일지 정해주면 된다.
SocketName은 지정할 수 있을 때 입력해주고, 없다면 제외해도 상관없다.
따라서 SpringArm은 SetupAttachment(GetRootComponent()); RootComponent에 붙여주고,
Camera는 SetupAttachment(SpringArm); SpringArm Component에 붙여주었다.
빌드 후, 언리얼 에디터에서 무사히 SpringArm 밑에 Camera Component가 붙여진 것을 볼 수 있다.
이제 레벨에 BP_Character를 올려보면 잘 이동하는 것을 확인해볼 수 있다.
* GPCCharacter 클래스 헤더파일에서 SpringArm과 Camera 선언 부분의 접근 지정자를 protected:로 수정했다.
여기서 추가한 SpringArm / Camera Component의 위치 조정은 그냥 블루프린트에서 해주는 것이 좋다.
그래서 SpringArm의 Z축 위치와 Socket Offset Z축 위치를 수정해주었다.
Look() 함수 정의
이번엔 시점 변환을 구현해보도록 하자. GPCCharacter.cpp에서 SetupPlayerInputComponent() 함수에서 LookLR / UD도
PlayerInputComponent->BindAxis()를 추가해서 입력을 받으면 호출할 수 있도록 했다.
void AGPCCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("MoveFB", this, &ThisClass::MoveFB);
PlayerInputComponent->BindAxis("MoveLR", this, &ThisClass::MoveLR);
PlayerInputComponent->BindAxis("LookLR", this, &ThisClass::LookLR);
PlayerInputComponent->BindAxis("LookUD", this, &ThisClass::LookUD);
}
이제 Look() 함수를 정의했다. 먼저 LookLR()은 시점의 좌우 움직임이므로, Controller의 Yaw에 Value값을 추가해준다.
LookUD()는 상하의 움직임으로 Controller의 Pitch에 Value값을 추가했다.
void AGPCCharacter::LookLR(float const Value)
{
AddControllerYawInput(Value);
}
void AGPCCharacter::LookUD(float const Value)
{
AddControllerPitchInput(Value);
}
그런데 이런식으로 함수 정의가 됬다면, 형식이 함수 안의 함수 하나만 부르게 되는 것이라서,
애초에 위에서 PlayerInputComponent->BindAxis("LookLR",this, &ThisClass::AddControllerYawInput); 형식으로 호출이
되어도 문제는 없다.
이제 빌드 후, 에디터에서 BP_Character의 카메라 이동이 원활한 것을 볼 수 있다.
자주 사용하는 코드라면 GPC_CPP_12 헤더에 넣기
#include <type_traits>
#define CreateDefaultSubobjectAuto(Pointer)\
(Pointer = CreateDefaultSubobject<std::remove_reference<decltype(*Pointer)>::type>(#Pointer))
위의 매크로는 GPCCharacter 클래스에서 뿐만 아니라 다른 클래스에서도 사용이 용이하기 때문에, 프로젝트 이름의
헤더파일에 옮겨놓는다면 GPC_CPP_12 헤더만 포함한다면 바로 사용할 수가 있다.
GPCPlayerController 만들기
그동안 블루프린트에서 구현했을 때, Character의 움직임은 보통 Controller에서 구현해주었다.
따라서 C++에서도 Controller 클래스를 만들어서 이동 로직을 옮겨주도록 하겠다.
언리얼 에디터에서 새 C++ 클래스 추가 - 부모 클래스 Player Controller 선택하여 GPCPlayerController라는 클래스를
새로 추가해주었다.
class GPC_CPP_12_API AGPCPlayerController : public APlayerController
{
GENERATED_BODY()
protected:
void SetupInputComponent() override;
private:
void MoveFB(float const Value);
void MoveLR(float const Value);
void LookLR(float const Value);
void LookUD(float const Value);
};
PlayerController에서 오버라이드 가능한 함수 중 하나인 SetupInputComponent()를 선언해주었다.
이는 앞서 GPCCharacter 클래스에서 입력 받으면 호출되는 SetupPlayerInputComponent() 함수와 같다.
그리고 GPCCharacter 클래스의 Move()와 Look() 함수를 가져왔다.
그리고 이제 GPCPlayerController.cpp에 함수들을 정의해줄 것이다.
void AGPCPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAxis("MoveFB", this, &ThisClass::MoveFB);
InputComponent->BindAxis("MoveLR", this, &ThisClass::MoveLR);
InputComponent->BindAxis("LookLR", this, &ThisClass::LookLR);
InputComponent->BindAxis("LookUD", this, &ThisClass::LookUD);
}
여기서 Super::SetupInputComponent()를 추가해주는 것이 중요하다고 할 수 있다.
이는 언리얼에서 제공하는 Framework 내용을 상속받아 오버라이드 하는 경우에 특별한 이유가 없다면 반드시 Super콜해주어야하며, 부모 함수 호출과 같다. 만약 Super콜을 하지 않았다면 Input의 입력을 받지 못한다.
GPCCharacter 클래스에선 PlayerInputComponent을 통해 해당 함수를 부르도록 추가했지만,
여기선 앞의 Player를 뺀 InputComponent를 사용하였다.
Move()와 Look() 함수 또한 GPCPlayerController 클래스에서 다시 정의해주었다.
void AGPCPlayerController::MoveFB(float const Value)
{
if (auto* const ControlledPawn = GetPawn())
{
FRotator const Rotator = FRotator(0, GetControlRotation().Yaw, 0);
FVector const Direction = Rotator.Quaternion().GetForwardVector();
ControlledPawn->AddMovementInput(Direction, Value);
}
}
void AGPCPlayerController::MoveLR(float const Value)
{
if (auto* const ControlledPawn = GetPawn())
{
FRotator const Rotator = FRotator(0, GetControlRotation().Yaw, 0);
FVector const Direction = Rotator.Quaternion().GetRightVector();
ControlledPawn->AddMovementInput(Direction, Value);
}
}
void AGPCPlayerController::LookLR(float const Value)
{
AddYawInput(Value);
}
void AGPCPlayerController::LookUD(float const Value)
{
AddPitchInput(Value);
}
Look() 함수의 정의는 Controller 내에서 하는 정의이므로, AddYawInput(), AddPitchInput()으로 바로 호출해줄 수 있다.
Move() 함수는 if (auto* const ControlledPawn = GetPawn()) 조건을 통해 ControlledPawn에 정상적으로 Pawn이 입력되었다면 실행하도록 수정하였다. 내부의 이동 로직은 GPCCharacter 클래스와 같다.