3. 입력에 반응하는 C++ 코드 작성

입력으로 폰과 카메라를 제어하는 코드를 구현합니다.

Choose your operating system:

Windows

macOS

Linux

  1. 이제 우리 게임에 사용할 수 있는 입력 매핑이 생겼으니, 받은 데이터를 저장할 멤버 변수를 구성해 줍시다. 업데이트 도중, 이동 및 마우스 방향 축 값을 알아야 하며, 둘 다 2차원 벡터입니다. 줌인 또는 줌아웃 뷰로 이동중인지도 알아야 하며, 현재 그 두 상태의 어디쯤 와 있는지도 알아야 합니다. 그러기 위해서는 PawnWithCamera.h 의 클래스 정의에 다음 코드를 추가해 줘야 합니다:

    //입력 변수
    FVector2D MovementInput;
    FVector2D CameraInput;
    float ZoomFactor;
    bool bZoomingIn;
  2. 그런 다음 입력에 대한 기록을 유지할 함수를 만들어 줘야 하니, 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

  3. 입력 데이터를 저장할 코드가 생겼으니, 이제 언리얼 엔진 더러 그 코드 호출 시점을 알려주기만 하면 됩니다. 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);
  4. 마지막으로 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 를 사용하면 액터가 향하는 방향을 기준으로 이동하는 것이 가능합니다. 카메라가 액터와 같은 쪽을 향하고 있기 때문에, 전방 키가 항상 플레이어가 바라보는 것을 기준으로 앞이 되도록 해 줍니다.

  5. 코딩 작업이 끝났습니다. 이제 코드를 컴파일한 다음 콘텐츠 브라우저 에서 새로 만든 클래스를 끌어 언리얼 엔진 에디터의 레벨 에디터 창에 놓는 것으로 새 인스턴스를 만들 수 있습니다.

    ClassInContentBrowser.png

    스태틱 메시 나 다른 비주얼 컴포넌트 를 추가하든, 아니면 없이든 자유롭게 플레이해 보세요. 카메라가 레벨을 따라다니면서 부드럽게 가속되고 감속되지만, 회전은 약간 빡빡하고 즉각적인 느낌이 들 것입니다. SpringArmComponent 의 프로퍼티 몇 가지, 이를테면 "Camera Rotation Lag" (카메라 회전 랙)을 추가하거나 "Camera Lag" (카메라 랙)을 높이고 낮춰서 조작감에 어떤 영향을 끼치는지 살펴보세요.

    SpringArmValues.png

  6. 원뿔 메시를 회전시킨 최종 제품 모습은 다음과 같습니다:

    FinalScreen.png

완성 코드

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;
}
언리얼 엔진 문서의 미래를 함께 만들어주세요! 더 나은 서비스를 제공할 수 있도록 문서 사용에 대한 피드백을 주세요.
설문조사에 참여해 주세요
취소