Choose your operating system:
Windows
macOS
Linux
-
이제 우리 게임에 사용할 수 있는 입력 매핑이 생겼으니, 받은 데이터를 저장할 멤버 변수를 구성해 줍시다. 업데이트 도중, 이동 및 마우스 방향 축 값을 알아야 하며, 둘 다 2차원 벡터입니다. 줌인 또는 줌아웃 뷰로 이동중인지도 알아야 하며, 현재 그 두 상태의 어디쯤 와 있는지도 알아야 합니다. 그러기 위해서는
PawnWithCamera.h
의 클래스 정의에 다음 코드를 추가해 줘야 합니다://입력 변수 FVector2D MovementInput; FVector2D CameraInput; float ZoomFactor; bool bZoomingIn;
-
그런 다음 입력에 대한 기록을 유지할 함수를 만들어 줘야 하니,
PawnWithCamera.h
의 클래스 정의에 다음의 코드줄 역시도 추가해 줍시다://입력 함수 void MoveForward(float AxisValue); void MoveRight(float AxisValue); void PitchCamera(float AxisValue); void YawCamera(float AxisValue); void ZoomIn(); void ZoomOut();
이제
PawnWithCamera.cpp
의 이 함수를 다음 코드로 채워주면 됩니다://입력 함수 void APawnWithCamera::MoveForward(float AxisValue) { MovementInput.X = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f); } void APawnWithCamera::MoveRight(float AxisValue) { MovementInput.Y = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f); } void APawnWithCamera::PitchCamera(float AxisValue) { CameraInput.Y = AxisValue; } void APawnWithCamera::YawCamera(float AxisValue) { CameraInput.X = AxisValue; } void APawnWithCamera::ZoomIn() { bZoomingIn = true; } void APawnWithCamera::ZoomOut() { bZoomingIn = false; }
Pawn 의 Tick
-
입력 데이터를 저장할 코드가 생겼으니, 이제 언리얼 엔진 더러 그 코드 호출 시점을 알려주기만 하면 됩니다. Pawn 에 대한 입력 이벤트에 함수를 바인딩하는 것은, 다음과 같이 APawnWithCamera::SetupPlayerInputComponent 에 바인딩 코드를 추가해 주기만 하면 됩니다:
//"ZoomIn" 에 대한 이벤트를 걸어줍니다 InputComponent->BindAction("ZoomIn", IE_Pressed, this, &APawnWithCamera::ZoomIn); InputComponent->BindAction("ZoomIn", IE_Released, this, &APawnWithCamera::ZoomOut); //네 축에 대한 매 프레임 처리를 걸어줍니다 InputComponent->BindAxis("MoveForward", this, &APawnWithCamera::MoveForward); InputComponent->BindAxis("MoveRight", this, &APawnWithCamera::MoveRight); InputComponent->BindAxis("CameraPitch", this, &APawnWithCamera::PitchCamera); InputComponent->BindAxis("CameraYaw", this, &APawnWithCamera::YawCamera);
-
마지막으로 Tick 함수에서 그 값을 사용하여 매 프레임 Pawn 과 Camera 를 업데이트하면 됩니다.
PawnWithCamera.cpp
의 APawnWithCamera::Tick 에 다음 코드 블록을 모두 추가하면 될 것입니다://ZoomIn 버튼이 눌렸으면 줌인, 아니면 도로 줌아웃 시킵니다 { if (bZoomingIn) { ZoomFactor += DeltaTime / 0.5f; //0.5 초에 걸쳐 줌인 } else { ZoomFactor -= DeltaTime / 0.25f; //0.25 초에 걸쳐 줌아웃 } ZoomFactor = FMath::Clamp<float>(ZoomFactor, 0.0f, 1.0f); //ZoomFactor 에 따라 스프링 암의 길이와 카메라의 시야 블렌딩 OurCamera->FieldOfView = FMath::Lerp<float>(90.0f, 60.0f, ZoomFactor); OurCameraSpringArm->TargetArmLength = FMath::Lerp<float>(400.0f, 300.0f, ZoomFactor); }
UPROPERTY(EditAnywhere)
//액터의 요를 회전, 붙어있는 카메라도 따라서 회전됩니다 { FRotator NewRotation = GetActorRotation(); NewRotation.Yaw += CameraInput.X; SetActorRotation(NewRotation); } //카메라의 피치를 회전하지만, 항상 아래를 보도록 제한시킵니다 { FRotator NewRotation = OurCameraSpringArm->GetComponentRotation(); NewRotation.Pitch = FMath::Clamp(NewRotation.Pitch + CameraInput.Y, -80.0f, -15.0f); OurCameraSpringArm->SetWorldRotation(NewRotation); }
이 코드 블록은 Pawn 의 요를 마우스 X 축으로 직접 회전시키되, 카메라 시스템은 마우스 Y 축의 피치 변화에만 반응합니다. 액터 나 그 서브클래스를 회전시키면 실제로 루트 레벨의 컴포넌트 가 회전되어, 거기 붙어있는 모든 것에 간접적으로 영향을 끼칩니다.
//"MoveX" 와 "MoveY" 축에 따라 이동을 처리합니다 { if (!MovementInput.IsZero()) { //이동 입력 축 값에 초당 100 유닛 스케일을 적용합니다 MovementInput = MovementInput.SafeNormal() * 100.0f; FVector NewLocation = GetActorLocation(); NewLocation += GetActorForwardVector() * MovementInput.X * DeltaTime; NewLocation += GetActorRightVector() * MovementInput.Y * DeltaTime; SetActorLocation(NewLocation); } }
GetActorForwardVector 와 GetActorRightVector 를 사용하면 액터가 향하는 방향을 기준으로 이동하는 것이 가능합니다. 카메라가 액터와 같은 쪽을 향하고 있기 때문에, 전방 키가 항상 플레이어가 바라보는 것을 기준으로 앞이 되도록 해 줍니다.
-
코딩 작업이 끝났습니다. 이제 코드를 컴파일한 다음 콘텐츠 브라우저 에서 새로 만든 클래스를 끌어 언리얼 엔진 에디터의 레벨 에디터 창에 놓는 것으로 새 인스턴스를 만들 수 있습니다.
스태틱 메시 나 다른 비주얼 컴포넌트 를 추가하든, 아니면 없이든 자유롭게 플레이해 보세요. 카메라가 레벨을 따라다니면서 부드럽게 가속되고 감속되지만, 회전은 약간 빡빡하고 즉각적인 느낌이 들 것입니다. SpringArmComponent 의 프로퍼티 몇 가지, 이를테면 "Camera Rotation Lag" (카메라 회전 랙)을 추가하거나 "Camera Lag" (카메라 랙)을 높이고 낮춰서 조작감에 어떤 영향을 끼치는지 살펴보세요.
-
원뿔 메시를 회전시킨 최종 제품 모습은 다음과 같습니다:
완성 코드
PawnWithCamera.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "PawnWithCamera.generated.h"
UCLASS()
class HOWTO_PLAYERCAMERA_API APawnWithCamera : public APawn
{
GENERATED_BODY()
public:
// Sets default values for this pawn's properties
APawnWithCamera();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
protected:
UPROPERTY(EditAnywhere)
USpringArmComponent* OurCameraSpringArm;
UCameraComponent* OurCamera;
//Input variables
FVector2D MovementInput;
FVector2D CameraInput;
float ZoomFactor;
bool bZoomingIn;
//Input functions
void MoveForward(float AxisValue);
void MoveRight(float AxisValue);
void PitchCamera(float AxisValue);
void YawCamera(float AxisValue);
void ZoomIn();
void ZoomOut();
};
PawnWithCamera.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "HowTo_PlayerCamera.h"
#include "PawnWithCamera.h"
// Sets default values
APawnWithCamera::APawnWithCamera()
{
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
//Create our components
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
OurCameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));
OurCameraSpringArm->SetupAttachment(RootComponent);
OurCameraSpringArm->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, 50.0f), FRotator(-60.0f, 0.0f, 0.0f));
OurCameraSpringArm->TargetArmLength = 400.f;
OurCameraSpringArm->bEnableCameraLag = true;
OurCameraSpringArm->CameraLagSpeed = 3.0f;
OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("GameCamera"));
OurCamera->SetupAttachment(OurCameraSpringArm, USpringArmComponent::SocketName);
//Take control of the default Player
AutoPossessPlayer = EAutoReceiveInput::Player0;
}
// Called when the game starts or when spawned
void APawnWithCamera::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void APawnWithCamera::Tick( float DeltaTime )
{
Super::Tick(DeltaTime);
//Zoom in if ZoomIn button is down, zoom back out if it's not
{
if (bZoomingIn)
{
ZoomFactor += DeltaTime / 0.5f; //Zoom in over half a second
}
else
{
ZoomFactor -= DeltaTime / 0.25f; //Zoom out over a quarter of a second
}
ZoomFactor = FMath::Clamp<float>(ZoomFactor, 0.0f, 1.0f);
//Blend our camera's FOV and our SpringArm's length based on ZoomFactor
OurCamera->FieldOfView = FMath::Lerp<float>(90.0f, 60.0f, ZoomFactor);
OurCameraSpringArm->TargetArmLength = FMath::Lerp<float>(400.0f, 300.0f, ZoomFactor);
}
//Rotate our actor's yaw, which will turn our camera because we're attached to it
{
FRotator NewRotation = GetActorRotation();
NewRotation.Yaw += CameraInput.X;
SetActorRotation(NewRotation);
}
//Rotate our camera's pitch, but limit it so we're always looking downward
{
FRotator NewRotation = OurCameraSpringArm->GetComponentRotation();
NewRotation.Pitch = FMath::Clamp(NewRotation.Pitch + CameraInput.Y, -80.0f, -15.0f);
OurCameraSpringArm->SetWorldRotation(NewRotation);
}
//Handle movement based on our "MoveX" and "MoveY" axes
{
if (!MovementInput.IsZero())
{
//Scale our movement input axis values by 100 units per second
MovementInput = MovementInput.SafeNormal() * 100.0f;
FVector NewLocation = GetActorLocation();
NewLocation += GetActorForwardVector() * MovementInput.X * DeltaTime;
NewLocation += GetActorRightVector() * MovementInput.Y * DeltaTime;
SetActorLocation(NewLocation);
}
}
}
// Called to bind functionality to input
void APawnWithCamera::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
Super::SetupPlayerInputComponent(InputComponent);
//Hook up events for "ZoomIn"
InputComponent->BindAction("ZoomIn", IE_Pressed, this, &APawnWithCamera::ZoomIn);
InputComponent->BindAction("ZoomIn", IE_Released, this, &APawnWithCamera::ZoomOut);
//Hook up every-frame handling for our four axes
InputComponent->BindAxis("MoveForward", this, &APawnWithCamera::MoveForward);
InputComponent->BindAxis("MoveRight", this, &APawnWithCamera::MoveRight);
InputComponent->BindAxis("CameraPitch", this, &APawnWithCamera::PitchCamera);
InputComponent->BindAxis("CameraYaw", this, &APawnWithCamera::YawCamera);
}
//Input functions
void APawnWithCamera::MoveForward(float AxisValue)
{
MovementInput.X = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}
void APawnWithCamera::MoveRight(float AxisValue)
{
MovementInput.Y = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}
void APawnWithCamera::PitchCamera(float AxisValue)
{
CameraInput.Y = AxisValue;
}
void APawnWithCamera::YawCamera(float AxisValue)
{
CameraInput.X = AxisValue;
}
void APawnWithCamera::ZoomIn()
{
bZoomingIn = true;
}
void APawnWithCamera::ZoomOut()
{
bZoomingIn = false;
}