어떤 공간에서 위치를 찾고자 하는 것인지 기준을 잡기 위해서 축이라는 것을 사용하는데, X라는 하나의 축을 사용하면 수직선 상에서의 점의 위치를 찾아낼 수 있게 된다.
X축과 Y축, 2개의 축을 이용하면 평면 상의 중심으로부터의 점의 위치를 알 수 있다.
X축과 Y축 그리고 Z축까지 3개의 축을 사용하면 3차원 공간 상의 점의 위치를 알아낼 수 있게 된다.
유니티 엔진에서는 씬이라는 공간 안에서 오브젝트의 위치를 표현하기 위해서 좌표계를 이용한다.
수직선을 이용한 1차원 상의 공간을 사용하는 게임은 별로 없고 대부분은 2D 좌표계나 3D 좌표계를 사용한다.
2D 좌표계를 사용하는 게임으로는 슈퍼 마리오 브라더스를 예로 들 수 있고, 3D 좌표계를 사용하는 게임으로는 하프라이프를 예로 들 수 있다. 2D 좌표계를 사용하는 게임은 움직임이 상하좌우 또는 전후좌우로 움직임이 제한되지만 3D 좌표계를 사용하는 게임은 전후좌우 뿐만 아니라 상하의 움직임까지 가능하다.
왼손 좌표계와 오른손 좌표계
좌표의 축을 정하는 방법은 여러 가지가 있는데 그 중 대표적인게 바로 왼손 좌표계와 오른손 좌표계이다.
우선 오른손 좌표계는 엄지 손가락이 X축, 검지 손가락이 Y축, 중지 손가락이 Z축이라고 가정하고 엄지를 종이 위에 수직선을 그었을 때 양수의 방향, 즉 오른쪽을 향하게 하고 검지를 X축과 직교하는 위 방향으로 향하게 했을 때, 중지가 나를 바라보는 방향이 되게 XYZ축을 정의하는 방식이다. 일반적인 수학에서는 이 오른손 좌표계를 표준으로 사용한다.
그 다음 왼손 좌표계는 엄지와 검지의 방향을 오른손 좌표계와 같이 맞췄을 때 중지는 내가 바라보는 방향을 가리키게 되도록 XYZ축을 정의한다. 유니티에서는 이 왼손 좌표계를 기준으로 사용한다.
한마디로 왼손 좌표계와 오른손 좌표계의 차이는 Z축이 가리키는 방향이 달라진다는 것이다. 오른손 좌표계에서는 화면에서 바라보는 사람에게로 다가오는 방식으로 Z축이 가리키게 되지만, 왼손 좌표계는 화면을 바라보는 사람에게서 화면 방향으로 Z축이 가리키게 된다.
Y-Up과 Z-Up
좌표계를 정의할 때, X축은 기본적으로 첫 번째 수평 방향의 수직선을 기준으로 하기 때문에 대부분 같은 방향으로 고정되어 있다. 여기서 발생하는 문제는 두 번째 축인 Y축의 방향을 어떻게 정의하느냐이다.
여기에는 두 가지 관점이 있는데 위에서 내려다보는 시점으로 Y축을 앞으로 나가는 방향으로 정의하는 방식이 하나로, 이렇게하면 새로 추가되는 세 번째 축인 Z축이 높이 축이 되는 Z-Up 방식이 된다. 언리얼 엔진과 3D 모델링 툴인 3ds Max가 이 방식을 채택한다.
다른 방식으로는 옆에서 바라보는 시점에서 Y축을 위로 향하는 방향으로 정의하는 것이다. Y축이 높이 축이 되기 때문에 Y-Up이라고 하고 유니티 엔진은 이 방식을 채택한다.
월드 좌표와 로컬 좌표
바로 전 파트까지 좌표계란 무엇인지와 유니티 엔진에서는 어떤 방식의 좌표계를 채택했는지를 설명했다. 이번 파트에서 이야기할 내용은 월드 좌표와 로컬 좌표에 대한 이야기이다.
월드 좌표란 세상을 중심으로 어느 위치에 있느냐를 의미하는 것이고, 로컬 좌표는 나 혹은 어느 한 오브젝트를 중심으로 어느 위치에 있느냐 하는 것이다.
사실 실제 세상에서 세상을 중심으로 어떠한 객체가 어느 위치에 있느냐 하는 것은 그 세상의 중심이 어디인지는 사람마다 생각이 다르고 절대적이라고 할 수 있는 중심이 없기 때문에 세상의 중심을 기준으로 한 위치라는 것은 구할 수 없겠지만, 게임이나 유니티 엔진에서는 가능하다.
바로 씬 안의 의 위치가 바로 게임 안에서의 세상의 중심이 된다.
월드 좌표를 대상으로 봤을 때, 선택된 큐브는 {-6, 0, -4}의 위치에 존재한다.
그렇다면 로컬 좌표란 무엇인가? 왜 월드의 중심이 아닌 어느 한 오브젝트를 중심으로 위치를 측정해야하는 걸까?
위의 이미지를 보자. 스피어 오브젝트 하나가 월드 좌표를 설명할 때 사용했던 큐브 오브젝트보다 XZ좌표가 각각 1씩 월드의 중심에 가깝게 존재하고 있다. 큐브 오브젝트의 위치가 {-6, 0, -4}였으니, 스피어 오브젝트는 {-5, 0, -3}의 위치에 있다. 만약에 추가된 이 스피어 오브젝트를 큐브 스피어를 중심으로 공전하게 만들고 싶다면 어떻게 해야할까?
만약 월드 좌표만으로 처리하려고 한다면 위의 이미지와 같이 좌표가 복잡하게 바뀌는 것을 알 수 있다.
하지만 스피어 오브젝트를 큐브 오브젝트의 자식 오브젝트로 만들면 포지션이 월드의 중심 좌표를 기준으로한 월드 좌표인 {-5, 0, -3}이 아니라 큐브 오브젝트를 중심으로한 로컬 좌표 로 표시되는 것을 확인할 수 있다.
이렇게 하고 나면 간단하게 큐브 오브젝트를 회전시키는 것만으로도 궤도를 따라서 스피어 오브젝트가 간단하게 공전하는 것을 볼 수 있다. 물론 큐브도 함께 자전한다는 문제가 있기는 하지만 이런 문제는 간단하게 해결하고 스피어 오브젝트만 궤도를 따라서 공전하게도 만들 수 있다.
[유니티 어필리에이트 프로그램]
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.
MyPawn 클래스의 생성이 성공적으로 끝났다면, 게임이 시작되었을 때 MyPawn이 자동으로 플레이어의 입력에 반응하도록 설정해보자. Pawn 클래스에는 초기화 중에 자동으로 플레이어의 입력에 반응하도록 설정해주는 변수를 제공한다. MyPawn.cpp의 AMyPawn::AMyPawn() 생성자를 다음과 같이 수정하자.
AMyPawn::AMyPawn() { // 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;
AutoPossessPlayer = EAutoReceiveInput::Player0; }
컴포넌트의 기록 유지를 위해서[각주:2] 다음의 코드를 MyPawn.h 의 클래스 정의 하단부에 추가하자.
그리고 MyPawn.cpp로 돌아와서 폰에 카메라를 붙이고 위치와 회전을 설정하기 위해 다음과 같이 코드를 수정한다.
AMyPawn::AMyPawn() { // 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;
게임에서 특정한 키를 눌렀을 때, 특정 동작을 하도록 만드는 것을 언리얼에서는 입력 매핑이라고 한다. 이러한 입력 매핑에는 두 가지 종류가 있다.
액션 매핑(Action Mapping) - 마우스나 조이스틱, 패드, 키보드 버튼처럼 누르거나, 떼거나, 더블 클릭하거나, 특정 시간동안 누르고 있을 때 보고한다. 점프, 공격, 상호작용 등이 액션 매핑의 예시이며, X를 눌러서 조이를 표하는 것도 액션 매핑에 속한다.
축 매핑(Axis Mapping) - 연속적인 것으로 마우스의 위치나 조이스틱 막대의 기울기 같은 것으로 "일정량"의 입력으로 생각하면 된다. 움직이지 않더라도 매 프레임 값을 보고한다. 걷기, 달리기, 둘러보기, 탈 것의 방향조절 같은 것들이 주로 축 매핑으로 처리된다.
코드에서도 직접 입력 매핑을 할 수 있지만, 일반적으로는 에디터에서 정의하는 경우가 많으니, 이 튜토리얼에서는 그 방식을 따른다.
1. 언리얼 엔진 에디터에서 편집 드롭다운 메뉴에서 프로젝트 세팅 옵션을 선택한다.
2. 왼쪽의 엔진 섹션의 입력 항목을 선택하고 바인딩(Binding) 카테고리에 다음과 같이 하나의 액션 매핑과 두 개의 축 매핑을 추가한다.
3. 입력 환경 설정이 모두 끝났다면, 레벨에 MyPawn을 배치한다. 콘텐츠 브라우저에 있는 MyPawn 클래스를 레벨 에디터에 끌어다 놓으면 된다.
4. 레벨에 MyPawn을 배치한 뒤에는, 우리가 배치한 Pawn이 움직이는 것을 볼 수 있게 하기 위해서 OurVisibleComponent의 스태틱 메시(Static Mesh) 카테고리에 "Shape_Cylinder"를 넣어야 한다고 언리얼 튜토리얼 문서에 나와있다.
하지만 우리가 배치한 MyPawn의 OurVisibleComponent에서는 스태틱 메시 카테고리가 보이지 않는 것을 알 수 있다.
이 문제의 원인을 추측해보자면 언리얼 튜토리얼의 예시 코드에는 CreateDefaultSubobject() 함수로 컴포넌트를 생성할 때, 명시적인 컴포넌트 타입이 없었기 때문에 헤더에 추가한 OurVisibleComponent의 타입에 맞춰서 USceneComponent로 생성했기 때문에 발생한 문제로 보인다.
그렇다면 스태틱 메시 카테고리가 나오도록 하려면 어떻게 해야할까? 바로 CreateDefaultSubobject() 함수로 UStaticMeshComponent를 생성해서 OurVisibleComponent에 대입시켜 주면 될 것 같다. 언리얼 엔진 문서에 따르면 UStaticMeshComponent는 USceneComponent를 상속받고 있기 때문에 충분히 가능한 코드이다. 여기까지 유추했다면 코드를 다음과 같이 수정해보자.
UStaticMeshComponent가 USceneComponent를 상속받고 있기 때문에 충분히 대입이 가능할거라고 생각했는데 할당할 수 없다는 에러가 발생한다.
이 경우는 타이머를 배울 때, GetWorldTimerManager() 함수를 호출해서 기능을 사용하려고 했을 때를 생각해보자. 그 때 불완전한 형식은 사용할 수 없다는 에러가 떴었던 것과 그 문제를 해결하기 위해서 "TimerManager.h"를 포함시켜주었던 것을 기억할 수 있다.
그와 같이 MyPawn.cpp의 헤더 포함 전처리기 부분에 "Engine/Classes/Components/StaticMeshComponent.h"를 포함시키면 CreateDefaultSubobject()로 생성한 UStaticMeshComponent가 성공적으로 OurVisibleComponent에 대입되는 것을 확인할 수 있다.
// Fill out your copyright notice in the Description page of Project Settings.
축 입력 매핑에 대한 동작을 구현할 때, FMath::Clamp()함수를 사용했는데 이것은 입력된 값이 -1.0과 1.0 사이를 벗어나지 않도록 만들어 준다. 전 파트에서 우리가 축 매핑을 추가할 때, MoveX의 입력을 W와 S만을 추가했는데 만약 다른 입력 방식도 사용하기 위해서 위쪽 화살표와 아래쪽 화살표로도 MoveX 입력을 받도록 만들었을 때, 만약 Clamp로 입력의 범위를 제한하지 않았다면 W와 위쪽 화살표를 동시에 누른다면 캐릭터가 두 배의 속도로 빠르게 움직이는 버그가 발생할 것이다.
입력 함수의 정의와 구현을 모두 끝냈으니, 적합한 입력에 반응하도록 바인딩을 진행할 차례다. AMyPawn::SetupPlayerInputComponent() 함수 안에 다음 코드를 작성하자.
// Called to bind functionality to input void AMyPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent);