이 튜토리얼 강좌에서는 폰 클래스를 확장하여 플레이어 입력에 반응하도록 하는 방법을 알아봅니다.
1 - 폰 커스터마이징
언리얼 엔진 을 처음 사용하신다면 프로그래밍 퀵스타트를 먼저 읽어보세요. 이 튜토리얼에서는 여러분이 프로젝트 생성, 프로젝트에 C++ 코드 추가, 코드 컴파일에 익숙하다고 가정합니다.
-
시작용 콘텐츠를 활용하여 이름이 HowTo_PlayerInput인 새 기본 코드 프로젝트를 생성하는 것부터 시작합니다. 그런 다음에는 커스터마이징된 폰(Pawn) 클래스, MyPawn을 프로젝트에 추가합니다.
폰은 인간 플레이어 또는 AI가 제어하도록 설계된 액터(Actor) 의 한 종류입니다.
-
우선 게임 시작 시 자동으로 MyPawn이 플레이어 입력에 반응하도록 설정합니다. 폰 클래스는 이를 처리하는 초기화 과정 도중에 설정할 수 있는 변수를 제공합니다. MyPawn.cpp에서 다음 코드를 AMyPawn::AMyPawn 에 추가합니다.
// 이 폰이 가장 낮은 숫자의 플레이어에 의해 제어되도록 설정 AutoPossessPlayer = EAutoReceiveInput::Player0;
-
다음으로는 기본 컴포넌트(Components) 몇 가지를 빌드합니다. 코드로 컴포넌트를 추가하고 관리하는 방법이나 앞으로 보게 될 몇 가지 일반적인 타입의 컴포넌트에 대한 자세한 정보는 컴포넌트 튜토리얼을 참조하세요. 생성할 컴포넌트를 계속 트래킹하기 위해 MyPawn.h의 클래스 정의 맨 아래에 다음 코드를 추가합니다.
UPROPERTY(EditAnywhere) USceneComponent* OurVisibleComponent;
이 변수는 언리얼 엔진 에서 보이도록 UPROPERTY 로 태그되었습니다. 게임이 실행될 때나 프로젝트 또는 레벨이 종료되고 다시 로드될 때 변수가 리셋되지 않도록 막아주므로 매우 중요합니다.
다시 MyPawn.cpp에서 다음 코드를 AMyPawn::AMyPawn에 추가합니다.
// 어태치의 대상이 될 수 있는 더미 루트 컴포넌트를 생성합니다. RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent")); // 카메라 및 표시 오브젝트 생성 UCameraComponent* OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("OurCamera")); OurVisibleComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("OurVisibleComponent")); // 카메라 및 표시 오브젝트를 루트 컴포넌트에 어태치합니다. 카메라에 오프셋을 적용하고 회전합니다. OurCamera->SetupAttachment(RootComponent); OurCamera->SetRelativeLocation(FVector(-250.0f, 0.0f, 250.0f)); OurCamera->SetRelativeRotation(FRotator(-45.0f, 0.0f, 0.0f)); OurVisibleComponent->SetupAttachment(RootComponent);
-
이제 변경 사항을 저장한 후 Visual Studio 에서 Build 명령을 사용하거나 언리얼 에디터 에서 컴파일(Compile) 버튼을 사용해서 컴파일하면 됩니다.
게임의 입력에 반응하도록 할 커스터마이징된 폰이 생겼으니, 해당 입력을 정의해야 합니다. 이렇게 하려면 언리얼 에디터* 에서 프로젝트의 입력 세팅(Input Settings)** 을 환경설정하면 됩니다.
작업 중인 코드
MyPawn.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/Pawn.h"
#include "MyPawn.generated.h"
UCLASS()
class HOWTO_PLAYERINPUT_API AMyPawn : public APawn
{
GENERATED_BODY()
public:
// 디폴트값 설정
AMyPawn();
protected:
// 게임 시작 또는 스폰 시 호출
virtual void BeginPlay() override;
public:
// 프레임마다 호출
virtual void Tick( float DeltaSeconds ) override;
//함수 기능을 입력에 바인딩하기 위해 호출
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
UPROPERTY(EditAnywhere)
USceneComponent* OurVisibleComponent;
};
MyPawn.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "HowTo_PlayerInput.h"
#include "MyPawn.h"
// 디폴트값 설정
AMyPawn::AMyPawn()
{
// 이 폰이 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다.
PrimaryActorTick.bCanEverTick = true;
// 이 폰이 가장 낮은 숫자의 플레이어에 의해 제어되도록 설정
AutoPossessPlayer = EAutoReceiveInput::Player0;
// 어태치의 대상이 될 수 있는 더미 루트 컴포넌트를 생성합니다.
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
// 카메라 및 표시 오브젝트 생성
UCameraComponent* OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("OurCamera"));
OurVisibleComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("OurVisibleComponent"));
// 카메라 및 표시 오브젝트를 루트 컴포넌트에 어태치합니다. 카메라에 오프셋을 적용하고 회전합니다.
OurCamera->SetupAttachment(RootComponent);
OurCamera->SetRelativeLocation(FVector(-250.0f, 0.0f, 250.0f));
OurCamera->SetRelativeRotation(FRotator(-45.0f, 0.0f, 0.0f));
OurVisibleComponent->SetupAttachment(RootComponent);
}
// 게임 시작 또는 스폰 시 호출
void AMyPawn::BeginPlay()
{
Super::BeginPlay();
}
// 프레임마다 호출
void AMyPawn::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
//함수 기능을 입력에 바인딩하기 위해 호출
void AMyPawn::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
Super::SetupPlayerInputComponent(InputComponent);
}
2 - 게임 입력 환경설정
입력 매핑에는 두 가지 타입이 있습니다. 액션(Action)과 축(Axis)입니다.
- 액션 매핑(Action Mappings) 은 마우스나 조이스틱의 버튼처럼 '예/아니요' 입력이라고 생각하면 편합니다. 액션 매핑은 누르거나, 떼거나, 더블 클릭하거나, 짧은 시간 동안 누르기를 유지할 경우 리포트합니다. 점프, 슈팅 또는 오브젝트와의 인터랙션 등 개별 액션이 이 매핑 타입에 적합한 후보입니다.
- 축 매핑(Axis Mappings) 은 조이스틱의 스틱이나 마우스 커서의 위치처럼 지속적인 입력으로, 입력의 '정도'라고 생각하면 편합니다. 축 매핑은 이동 중이 아니더라도 프레임마다 값을 리포트합니다. 걷기, 둘러보기, 비히클 스티어링 등 규모나 방향이 있는 요소가 주로 이러한 방식으로 처리됩니다.
입력 매핑을 코드에서 바로 정의할 수도 있지만, 보통 언리얼 엔진 에디터에서 정의하므로 이 튜토리얼에서는 후자를 사용합니다.
-
언리얼 엔진 에디터의 편집(Edit) 드롭다운 메뉴에서 프로젝트 세팅(Project Settings) 옵션을 클릭합니다.
-
여기에서 좌측의 엔진(Engine) 섹션의 입력(Input) 옵션을 선택합니다. 그런 다음 우측에 표시되는 바인딩(Bindings) 카테고리를 펼치고 액션 매핑(Action Mapping) 1개와 축 매핑(Axis Mappings) 2개를 추가합니다.
액션 매핑 또는 축 매핑 섹션 헤딩 옆의 더하기 기호로 새 매핑을 추가할 수 있습니다. 좌측의 확장 화살표를 사용하여 매핑을 표시하거나 숨길 수 있습니다. 매핑에 추가 입력을 더하려면 해당 매핑 옆의 더하기 기호를 클릭합니다. 다음은 필요한 매핑 및 입력입니다. S 및 A 입력의 음수 값에 주목하세요.
액션 매핑 Grow 스페이스 바 축 매핑 MoveX W 1.0 S -1.0 MoveY A -1.0 D 1.0 -
이제 입력 환경설정을 마쳤으니 레벨의 MyPawn을 구성합니다. MyPawn 클래스는 콘텐츠 브라우저 에 나타나며, 레벨 에디터 로 드래그해도 되는 상태입니다.
-
MyPawn을 구성하려면 과정 하나가 더 필요합니다. 게임 내에서 볼 수 있도록 스태틱 메시(Static Mesh) 를 부여해야 합니다. 이렇게 하려면 방금 생성한 MyPawn을 선택하고 디테일 패널 에서 OurVisibleComponent (Inherited) 컴포넌트를 선택한 후 스태틱 메시 카테고리의 드롭다운 상자를 통해 에셋을 할당하면 됩니다. 이 튜토리얼의 경우 Shape_Cylinder 에셋을 사용하면 좋습니다.
-
이제 레벨을 저장하고 Visual Studio 로 돌아가, 배치한 MyPawn이 정의한 입력에 반응하도록 하는 코드를 작성할 수 있습니다.
이제 Visual Studio 에서 MyPawn 클래스 코딩을 마칠 준비가 되었습니다.
3 - 게임 액션 프로그래밍 및 바인딩
1. Visual Studio 에서 MyPawn.h를 열고 MyPawn의 클래스 정의 맨 아래에 다음 코드를 추가합니다.
//입력 함수 void Move_XAxis(float AxisValue); void Move_YAxis(float AxisValue); void StartGrowing(); void StopGrowing();
//입력 변수 FVector CurrentVelocity; bool bGrowing;
4개의 입력 함수가 입력 이벤트에 바인딩됩니다. 입력 함수는 실행 시 새 입력 변수에 저장된 값을 업데이트하며, MyPawn은 이 변수를 사용하여 게임 도중에 수행해야 할 일을 결정합니다.
-
MyPawn.cpp로 전환하여 방금 선언한 4개의 함수를 프로그래밍합니다. 다음 코드를 추가하세요.
void AMyPawn::Move_XAxis(float AxisValue) { // 앞으로 또는 뒤로 초당 100유닛 이동 CurrentVelocity.X = FMath::Clamp(AxisValue, -1.0f, 1.0f) * 100.0f; } void AMyPawn::Move_YAxis(float AxisValue) { // 오른쪽 또는 왼쪽으로 초당 100유닛 이동 CurrentVelocity.Y = FMath::Clamp(AxisValue, -1.0f, 1.0f) * 100.0f; } void AMyPawn::StartGrowing() { bGrowing = true; } void AMyPawn::StopGrowing() { bGrowing = false; }
FMath::Clamp 를 사용하여 입력의 값을 -1~+1 범위로 제한합니다. 이 예시에서는 문제가 되지 않지만, 축에 동일한 방식으로 영향을 미칠 수 있는 다수의 키가 있을 경우 플레이어가 해당 입력을 동시에 누르면 값이 합쳐지게 됩니다. 예를 들어 W 및 위 화살표가 모두 스케일 1.0의 MoveX에 매핑되어 있는 경우, W와 위 화살표를 둘 다 누르면 축 값(AxisValue)은 2.0이 되므로 범위제한이 없으면 플레이어가 2배의 속도로 이동합니다.
2개의 'Move' 함수가 축 값을 플로트로 받는 반면 'Grow' 함수는 그렇지 않다는 점을 눈치채셨을 것입니다. 이는 Move 함수가 MoveX 및 MoveY에 매핑되어(축 매핑) 플로팅 포인트 파라미터를 가지게 되기 때문입니다. 액션 매핑에는 이 파라미터가 없습니다.
-
이제 입력 함수를 정의했으니, 적합한 입력에 반응하도록 바인딩해야 합니다. AMyPawn::SetupPlayerInputComponent 내부에 다음 코드를 추가합니다.
// 'Grow' 키를 누르거나 뗐을 때 반응합니다. InputComponent->BindAction("Grow", IE_Pressed, this, &AMyPawn::StartGrowing); InputComponent->BindAction("Grow", IE_Released, this, &AMyPawn::StopGrowing); // 2개의 이동 축 'MoveX' 'MoveY'의 값에 프레임마다 반응합니다. InputComponent->BindAxis("MoveX", this, &AMyPawn::Move_XAxis); InputComponent->BindAxis("MoveY", this, &AMyPawn::Move_YAxis);
-
이제 변수가 환경설정한 입력에 의해 업데이트됩니다. 남은 일은 이를 통해 뭔가 하도록 코드를 작성하는 것입니다. 다음 코드를 AMyPawn::Tick 에 추가합니다.
// 'Grow' 액션을 기반으로 확장 및 축소 처리 { float CurrentScale = OurVisibleComponent->GetComponentScale().X; if (bGrowing) { // 1초에 걸쳐 크기를 2배로 확장 CurrentScale += DeltaTime; } else { // 확장 속도만큼 빠르게 반으로 축소 CurrentScale -= (DeltaTime * 0.5f); } // 시작 크기보다 작아지거나 2배 크기를 초과하지 않도록 설정 CurrentScale = FMath::Clamp(CurrentScale, 1.0f, 2.0f); OurVisibleComponent->SetWorldScale3D(FVector(CurrentScale)); } // 'MoveX' 및 'MoveY' 축을 기반으로 이동 처리 { if (!CurrentVelocity.IsZero()) { FVector NewLocation = GetActorLocation() + (CurrentVelocity * DeltaTime); SetActorLocation(NewLocation); } }
-
코드 컴파일을 마친 후에는 언리얼 에디터 로 돌아가 플레이 를 누르면 됩니다. 폰 을 WASD 키로 제어할 수 있으며, 스페이스 바 를 길게 누르면 확장되고 떼면 줄어들어야 합니다.
완성된 코드
MyPawn.h
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/Pawn.h"
#include "MyPawn.generated.h"
UCLASS()
class HOWTO_PLAYERINPUT_API AMyPawn : public APawn
{
GENERATED_BODY()
public:
// 디폴트값 설정
AMyPawn();
protected:
// 게임 시작 또는 스폰 시 호출
virtual void BeginPlay() override;
public:
// 프레임마다 호출
virtual void Tick(float DeltaSeconds) override;
//함수 기능을 입력에 바인딩하기 위해 호출
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
UPROPERTY(EditAnywhere)
USceneComponent* OurVisibleComponent;
// 입력 함수
void Move_XAxis(float AxisValue);
void Move_YAxis(float AxisValue);
void StartGrowing();
void StopGrowing();
// 입력 변수
FVector CurrentVelocity;
bool bGrowing;
};
MyPawn.cpp
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "HowTo_PlayerInput.h"
#include "MyPawn.h"
// 디폴트값 설정
AMyPawn::AMyPawn()
{
// 이 폰이 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다.
PrimaryActorTick.bCanEverTick = true;
// 이 폰이 가장 낮은 숫자의 플레이어에 의해 제어되도록 설정
AutoPossessPlayer = EAutoReceiveInput::Player0;
// 어태치의 대상이 될 수 있는 더미 루트 컴포넌트를 생성합니다.
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
// 카메라 및 표시 오브젝트 생성
UCameraComponent* OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("OurCamera"));
OurVisibleComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("OurVisibleComponent"));
// 카메라 및 표시 오브젝트를 루트 컴포넌트에 어태치합니다. 카메라에 오프셋을 적용하고 회전합니다.
OurCamera->SetupAttachment(RootComponent);
OurCamera->SetRelativeLocation(FVector(-250.0f, 0.0f, 250.0f));
OurCamera->SetRelativeRotation(FRotator(-45.0f, 0.0f, 0.0f));
OurVisibleComponent->SetupAttachment(RootComponent);
}
// 게임 시작 또는 스폰 시 호출
void AMyPawn::BeginPlay()
{
Super::BeginPlay();
}
// 프레임마다 호출
void AMyPawn::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 'Grow' 액션을 기반으로 확장 및 축소 처리
{
float CurrentScale = OurVisibleComponent->GetComponentScale().X;
if (bGrowing)
{
// 1초에 걸쳐 크기를 2배로 확장
CurrentScale += DeltaTime;
}
else
{
// 확장 속도만큼 빠르게 반으로 축소
CurrentScale -= (DeltaTime * 0.5f);
}
// 시작 크기보다 작아지거나 2배 크기를 초과하지 않도록 설정
CurrentScale = FMath::Clamp(CurrentScale, 1.0f, 2.0f);
OurVisibleComponent->SetWorldScale3D(FVector(CurrentScale));
}
// 'MoveX' 및 'MoveY' 축을 기반으로 이동 처리
{
if (!CurrentVelocity.IsZero())
{
FVector NewLocation = GetActorLocation() + (CurrentVelocity * DeltaTime);
SetActorLocation(NewLocation);
}
}
}
//함수 기능을 입력에 바인딩하기 위해 호출
void AMyPawn::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
Super::SetupPlayerInputComponent(InputComponent);
// 'Grow' 키를 누르거나 뗐을 때 반응합니다.
InputComponent->BindAction("Grow", IE_Pressed, this, &AMyPawn::StartGrowing);
InputComponent->BindAction("Grow", IE_Released, this, &AMyPawn::StopGrowing);
// 2개의 이동 축 'MoveX' 'MoveY'의 값에 프레임마다 반응합니다.
InputComponent->BindAxis("MoveX", this, &AMyPawn::Move_XAxis);
InputComponent->BindAxis("MoveY", this, &AMyPawn::Move_YAxis);
}
void AMyPawn::Move_XAxis(float AxisValue)
{
// 앞으로 또는 뒤로 초당 100유닛 이동
CurrentVelocity.X = FMath::Clamp(AxisValue, -1.0f, 1.0f) * 100.0f;
}
void AMyPawn::Move_YAxis(float AxisValue)
{
// 오른쪽 또는 왼쪽으로 초당 100유닛 이동
CurrentVelocity.Y = FMath::Clamp(AxisValue, -1.0f, 1.0f) * 100.0f;
}
void AMyPawn::StartGrowing()
{
bGrowing = true;
}
void AMyPawn::StopGrowing()
{
bGrowing = false;
}
4 - 직접 해보기
여기에서 배운 것을 활용하여 다음과 같은 작업을 해보세요.
- 특정 시간 동안 누른 채로 유지하면 속도가 증가하는 방향성 제어를 구현합니다.
- 사용자가 축 매핑 을 누르기 시작한 직후에 액션 매핑 을 누르면 오브젝트가 전체 스케일로 즉시 확장되는 특별한 입력 시퀀스를 만듭니다.
이 튜토리얼에서 배운 내용을 더 알아보세요.
- 입력 에 대한 자세한 정보는 %making-interactive-experiences/Input:title% 페이지를 참조하세요.
- 추가 튜토리얼은 %programming-and-scripting/programming-language-implementation/cpp-in-unreal-engine/tutorials:title% 페이지를 참조하세요.