[Unreal 4] 기초 #1 - 인터페이스 분석, 유니티 vs. 언리얼, 로그와 디버깅, 게임플레이 프레임워크
기초 1 - 언리얼 인터페이스 분석
[조작방법]
우클릭하면서 WASD로 상좌하후 방향, QE로 깊이 조절할 수 있다.
alt + 좌/우
스크롤
[소품배치 방법]
w : 방향
e : 회전
r :스케일
영화 촬영에 비유
플레이 중 F8단축키로 빙의에서 빠져나올 수 있다.
유니티는 무에서 유를 쌓아 올리는 느낌으로 직관적이고 언리얼은 이미 잘 짜여진 것들을 활용하는 차이가 있다.
구조가 잘 짜여져있어 기본적으로 Default Pawn이라는 주인공이 월드 아웃라이너에 배치되어 있는데
언리얼토너먼트라는 FPS 게임을 만들기 위해서 엔진을 에픽게임즈가 후에 공개한것이므로 구조가 FPS로 기본적이게 만들어져 있는 것이다. 이는 세팅에서 게임모드로 선택할 수 있고 세팅이 None이면 디폴트로 정해져 있는 것이다.
구조에 대한 이해도가 확정이 되면 예를들어 PlayerController가 제공되기 때문에 더 쉽게 사용할 수도 있다.
그래서 언리얼에선 특히 상속 구조가 중요하다.
기초 2 - 유니티 vs. 언리얼
앞서 말했던 것처럼
유니티는 add empty를 하이어라키에 추가하면 트랜스폼만 있다 여기에 메쉬필터, 메쉬 컬라이더, 박스 컬라이더 컴포넌트 등을 추가해 하나씩 조립해가는 느낌이다. 카메라나 Directional Light도 빈 오브젝트에 camera 컴포넌트, light 컴포넌트를 추가한 것이다. 프로젝트에 C#코드로 동작하게끔 하고싶어도 직접 Create > C# Script를 추가하고 오브젝트에 컴포넌트로 추가하여 할 수 있다. "빈 깡통에 컴포넌트를 이어 붙인다."
반면 언리얼은 이미 Static Mesh Actor가 만들어져 있다.
콘텐츠 브라우즈 창에서 C++클래스 파일 하위의 추가할 위치를 선택하고 우클릭으로 새 C++ 클래스를 선택하면 부모클래스를 선택할 수 있는 창이 뜬다.
오늘은 이 중 Character, Pawn, Actor에 대해서 살펴보자.
위 설명을 보면 Actor - Pawn - Character 순으로 상속하고 있음을 알 수 있다. 모든 클래스 표시 체크박스를 체크해서 보면 더 자세히 상속구조를 볼 수 있다. 모든 클래스들의 최상위 클래스는 Object 클래스이다.
위창에서 모든 클래스 표시로 아까 봤던 SteticMeshActor가 뭔지 검색해보면 부모가 Actor인 것을 알 수 있었다.
의자를 하나 만들어보자.
새 Actor클래스를 하나 추가해보자.
자동으로 VS에도 추가된다. Cpp코드를 보면 유니티와 유사하게 생성자, BeginPlay, Tick이 있어서 유니티의 Start, Update와 같은 역할을 한다. (무대에서 생성될 때 최초 실행, 매 프레임마다 실행)
그런데 만약에 클래스 이름을 잘못 만들어 수정하고 싶다면 유니티에선 c#파일 이름 수정하고 오브젝트도 우클릭으로 바꿀 수 있으며 삭제도 간단했다. 반면 언리얼은 우클릭해도 비활성화 되어있다. 그럼 에디터를 끄고 vs의 솔루션 탐색기에서 제거 한 후 실제 cpp파일이 있는 파일경로로 가서 직접 삭제해야한다. 경우에 따라 더 깔끔하게 하고 싶다면 Binaries, Intermediate, Saved, DerivedCache와 솔루션을 지우고 언리얼 엔진 파일을 우클릭해서 project file을 다시 만들어야 한다.
엔진이 무거워 로딩하는데 오래걸리기도 하고 번거로우므로 언리얼의 양날의 검이 된다.
다시 언리얼 엔진 에디터를 열어 Actor를 부모클래스로 MyClass라는 클래스를 만들고 뷰에 드래그앤드랍해서 배치할 수 있다. MyActor에 메쉬를 추가해보자.
- MyActor를 VS로 열어서 헤더 파일을 보면 클래스 정의 부분 위에 UCLASS()라고 써있는 걸 볼 수 있다. 전에는 C++엔 Reflection(컴파일러가 읽을 수 있는주석)이 없었다가 C++20에 추가 된것으로 UCLASS()의 괄호 안에 무엇인가 추가하면 런타임 동안에 그 클래스를 살펴봐 기능을 추출할 수 있다. 언리얼을 처음 만들 땐 이 리플렉션 기능이 없어서 언리얼에서 자체적으로 추가한 것이다. UCLASS()는 언리얼 클래스인것을 의미하는 언리얼 자체의 리플렉션이다. 언리얼에서 빌드를 할 때 일반 Cpp처럼 바로 빌드하는 것이 아니라 언리얼에서 부가적인 메타데이터를 같이 기입해 빌드 툴을 직접 만들어 쓰는데 이때 쓰이게 된다.
- 헤더파일에서 클래스 정의부분을 보면 class AMyActor : public AActor라고 써있는 대신에 class TESTUNREALENGINE_API : AMyActor : public AActor라고 써있는데 TESTENGINEUNREALENGINE_API 이건 모듈과 관련이 있는데 빌드할 때 모듈을 가지고 사용한다는 의미이다. 후에 다시 살펴볼 예정. 언리얼의 네이밍 컨벤션을 보면 템플릿은 T로 Actor를 상속받으면 A로 UObject를 상속받으면 U로 시작 등을 권장한다.그래서 MyActor로 만들어도 AMyActor가 되곤 한다.
이 헤더파일에 UStaticMeshComponent* Mesh; 이렇게 추가하고 위에 언리얼 프로퍼티(컴포넌트)라는 뜻으로 UPROPERTY()를 써주자.(엔진에선 컨벤션으로 변수 이름을 대문자로 시작하니 보통 지키는게 좋다.) UPROPERTY는 모든 변수에 붙이는 건 아니구 리플렉션 기능을 해서 특수한 컴포넌트이므로 언리얼에서 직접 관리하는 메모리라 붙인것이다. 따라서 생성자에서 Mesh = new UStaticMeshComponent() 이렇게 안하고 Mesh = CreateDefaultSubObject<UStaticMeshComponent>(TEXT('Mesh')); 이렇게 사용한다. (참고로 TEXT를 붙이는 이유는 문자열 방식이 현재는 UTF-8형식이지만 다른 인코딩으로도 가능하도록 크로스플랫폼을 염두에 두기 때문이다.)
또 참고로 MyActor헤더파일에 보면 #include "CoreMinimal.h" 이렇게 포함되어있는데 언리얼엔진마다 버전이 다르지만 "CoreMinimal.h"안에 UStaticMeshComponent가 있어서 사용가능했던 것으로 만약 없어서 컴파일이 안된다면 혹은 직접작성하고 싶다면 MyActor.h에서는 class UStaticMeshComponent* Mesh; 이렇게 전방선언 하고 MyActor.cpp에서는 #include "Components/StaticMeshComponet.h"를 해서 사용할 수도있다.
어쨋든 CreateDefaultSubObject 를 사용하면 이 메쉬를 직접적으로 메모리를 관리할 필요가 없는 일종의 스마트포인터라는 것이다. 언리얼에서는 일반적으로 스마트포인터를 사용하지 일반 포인터를 사용하지 않는다. 여기서도 볼 수 있듯이 유니티는 빈 깡통에 컴포넌트를 붙여가는 식이라면 언리얼은 우선 태생(부모클래스)을 정한 다음에 컴포넌트 방식으로 붙이는건 비슷하다.
프로젝트 빌드는 비주얼스튜디오에서도 가능하고 에디터에서도 컴파일 버튼을 누르면 가능하다. 이렇게 메쉬컴포넌트를 붙이고 나면 에디터의 디테일 탭에서도 볼수 있다. 그런데 다른 세팅이 불가능한데 이는 UPROPERTY()의 괄호 안에 설정을 통해 수정가능하게 만들 수 있다.
https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/GameplayArchitecture/Properties/
프로퍼티
게임플레이 클래스에 대한 프로퍼티 생성 및 구현 관련 레퍼런스입니다.
docs.unrealengine.com
이렇게 프로퍼티를 설정하는 게 많은데 이중에서도 VisibleAnywhere를 추가하면 포엔터가 있는곳에선 어디든 보고 수정할 수 있다는 것이다. UPROPERTY(VisibleAnywhere) 이렇게 수정한 후 다시 컴파일하면 에디터에서에서도 뷰에 배치된 MyActor 클래스의 객체에 메쉬를 직접 선택할 수 있게 된다.
그치만 이렇게 에디터에서 메쉬 정보를 바꾸면 문제가 있는데 바로 모든 MyActor의 객체에 대해서 적용되는게 아니라 뷰에 배치된 객체 하나에만 적용된다는 것이다. 따라서 또 MyActor를 드래그드롭으로 뷰에 또 배치하면 그 오브젝트엔 메시가 없어 또 붙여넣기 해야한다. 그래서 AMyActor 가 정의된 cpp 파일 내에서
AMyActor::AMyActor()
{
// 기본으로 작성되어 있는 코드. Tick()을 사용할건지 여부이다.
PrimaryActorTick.bCanEverTick = true;
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MESH"));
RootComponent = Mesh;
static ContstructorHelpers::FObjectFinder<UStaticMesh> SM(TEXT("[사용하고 싶은 메쉬가 있는 경로]"));
if(SM.Succeeded())
{
Mesh->SetStatic(SM.Object);
}
}
FObjectFinder로 읽어드려 로드하게 해야한다. 초기에 한번만 하면 되니깐 static을 붙이는 것도 일반적이다.
스태틱메시를 로드하는데 성공했다면 선언했던 Mesh에 연결해준다.
다시 MyActor를 뷰에 배치하면 코드상에서 지정한 메쉬를 가지고 있는체로 배치된다. 그 후에 이 오브젝트에 다른 메쉬를입히고 싶다면 에디터에서도 메쉬를 바꿀 수 있는데 이 오브젝트에만 적용된다. 즉, 코드상에서 설정한 것이 먼저적용되고 그 후 에 에디터에서 수정한 정보가 덮어씌우는 순서이다.
그리고 컴포넌트를 오브젝트에 추가하면 RootComponent로 선언해야하는 규칙이 있다.
컴포넌트 외에도 게임내에서 사용할 정보들이 있다. HP, MP등등 이들도 클래스에 추가한 후 에디터에서 보기위해선 리플렉션 UPROPERTY(VisibleAnywhere)를 위에 써줘야한다. 에디터에서 보는 카테고리를 바꾸고 싶다면 UPROPERTY(VisibleAnywhere, Category = BattleStat) 을 추가해서 보이는 카테고리 이름을 바꿀수도 있다.
그런데 이런 int32 타입의 변수들은 VisibleAnywhere라고 하면 보이기만 하고 수정할수 없으므로 EditAnywhere를 해야 수정할수 있다.
자동으로 메모리가 언리얼에서 관리하는 것은 CreateDefaultSubobject로 생성됐을 때이고 UPROPERTY는 에디터에서 사용하는 언리얼에서 만든 리플렉션 기능일 뿐이다..!
기초 3 - 로그와 디버깅
위에서 만든 AMyActor의 BeginPlay와 Tick를 수정해서 콘텐츠를 추가해보자.
대표적으로 로그를 찍는 방법으로는 대표적으로 UE_LOG함수가 있다. 인자로는 아래와 같다.
// 카테고리(임시로 아무이름 가능), 로깅수준(Warning 등에 따라 찍히는 색이 다르다. enum으로 ELogVerbosity로 검색하면 볼수 있다.), 형식, 나머지 인자
UE_LOG(LogTemp, Warning, TEXT("BeginPlay %d"), 3) ;
를 BeginPlay 에 넣고
UE_LOG(LogTemp, Error, TEXT("Tick %f"), DeltaTime);
를 Tick에 넣어보자. 에디터에서 실행하면 출력로그창에서 볼 수 있다. Warning이면 노란색으로 Error면 빨간색으로 뜨는 걸 볼 수 있다.
또 ELogVerbosity 문서를 보면 log파일을 남기게도 할 수 있다.
그렇담 디버깅을 하는 방법을 알아보자..! 유니티는 중단점을 VS에서 찍고 실행하면 바로 가능하나 언리얼은 다르다.
솔루션 구성이 아마 Devleopment Editor나 Debug Editor로 되어있을 것이다. 둘 다 상관 없는데 Development Editor 에 두고 언리얼 에디터를 끈 다음에 VS 에서 솔루션을 실행하면 에디터가 켜지고 중단점이 걸리게 된다. 그런데 이 상태에선 디버깅을 중단할 경우 에디터도 꺼지는 단점이 있다. 결국 디버깅하는 방법은
1. VS와 에디터를 키고 VS에서 로그를 찍어 (핫리로드로 실행해) 에디터의 출력로그창으로 확인하는 방법
2. VS만 키고 중단점을 걸은 뒤 실행해 직접 실행해가며 확인하는 방법
두 가지로 정리 할 수 있다.
언리얼엔진을 위한 VS 솔루션 구성은 5가지가 있다.
DebugGame
Development 위와의 차이는 Debug와 Release의 차이다. Debug는 디버깅 심볼이 많고 최적화가 덜 되어 있으며 개발단계에 용이하다. Development는 디버깅이 쉽지 않다.
DebugGame Editor
Development Editor Editor가 붙어있으면
Shipping 최종적으로 출시를 할 때 사용하는 것으로 최적화가 잘 되어 있고 디버깅은 더 어렵다.
그렇담 Editor가 붙은것과 안붙은 것의 차이는...?
Editor가 붙어 있지 않은 DebugGame과 Development로 빌드를 하면 무슨 Shader missing..?이런 메시지 박스가 뜬다..
파일탐색기에서 폴더열기로 프로젝트 파일들을 열어서 Binaries에 가면 빌드한 게임들이 나온다. 보면 실행파일들이 생기는데 아트 리소스들이 패키징되어서 들어간게 아니라 위와 같은 에러가 뜨게된다. 즉, 말그대로 실행파일만 만들어주는것으로 경우에 따라 아트 리소스의 경로에 있는지를 맞춰서 만드는 방법도 있다.
에디터 상에서 돌아가는 dll 파일을 만들어주는게 Editor가 붙어있는 빌드 방법이다. 에디터를 띄워서 그 위에서 dll이 돌아가는 방법이기 때문이다.
그리고 이 빌드 방식을 보여주는 콤보박스가 너무 작아서 잘 안보일수 있는데 콤보박스쪽 우클릭>사용자지정>명령탭의
도구모음에서 표준으로 누른 후 솔루션 구성의 선택사항 수정을 누른 후 너비를 한 200 정도로 하면 적당하게 된다.
앞으로 프로젝트는 초기에 설정되어있는 Development Editor로 진행할 예정이다.
C++이 C#에 비해 빌드 속도가 느리기도 하지만 엔진이 무거워 복잡하게 되어있기도 하다.
+) 추가로 오브젝트를 회전시켜보자.
MyActor에
UPROPERTY(EditAnywhere, Category = BattleStat)
float RotateSpeed = 30.f;
로 추가해준다.
Tick에서
AddActorLocalRotation(FRotator(0.f, RotateSpeed * DeltaTime, 0.f)) ;
첫번째 인자로 회전하고싶은 각도를 넣어준다.
FRotator를 이용하는 방법과 Quaternion을 이용하는 방법이 있다. 이 중에서 FRotator를 사용했다.
Pitch, Yaw, Roll 을 넣어준다. 순서대로 Y, X, Z
https://docs.unrealengine.com/4.27/en-US/API/Runtime/Core/Math/FRotator/
FRotator
Implements a container for rotation information. All rotation values are stored in degrees.
docs.unrealengine.com
게임에서는 30만큼 움직이고 싶다고 해도 바로 30을 넣는게 아니라 당연히 델타타임을 곱해서 넣어준다.
컴퓨터마다 게임이 돌아가는 속도가 다르다. 200프레임일수도 있고(1초동안 틱이 호출 횟수가 200) 40프레임일수도 있다. 틱이 많이 호출될 수록 당연히 부드럽게 돌아가는데 항상 이동하는부분엔 틱 사이에 경과시간을 나타내는 델타타임을 넣어 같이 움직일수 있도록 해야한다.
👉 거리는 속도에다가 시간을 곱한 것이므로 다른 프레임이더라도 시간흐름에 따라 같은 거리를 이동할 수 있도록 하는 것이다.
이렇게 하고 실행해보면 (참고로 실행방법 여러가지를 설명했지만 알고있듯이 에디터를 킨 상태에서 컴파일 버튼을 눌러 핫리로드를 통해 바뀐 부분만 dll을 바꿔치기해 수정하는 방법이 에디터를 끄고 키는걸 반복하는것보다 빠르다.) 언리얼 X축을 기준으로 (유니티 Y축) 회전하는 걸 볼 수 있다.
참고로 Pitch(= 유니티 X, 언리얼 Z), Yaw(= 유니티 Y, 언리얼 Y), Roll(= 유니티 Z, 언리얼 X)
왼손좌표계를 외우는 방법으로는 엄지손가락이 나를 가리키게 왼손을 핀 후 중지를 손바닥에서 수직이 되도록 핀 후 약지와 새끼를 접으면 권총이 하늘로 간 모양이 된다.
이때 엄지, 검지, 중지 순으로 X,Y, Z이다.
그리고 엔진에서 사용하는 Pitch, Yaw, Roll은 언리얼기준 Z,Y, X 이다.( 유니티 기준 X,Y,Z)
유니티에서 사용하느 오른손좌표계는 왼손과 대칭되게 한 후 중지부터 순으로 X,Y,Z라고 한다.
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=taeyo79&logNo=220447720072
오른손 좌표계와 왼손 좌표계(언리얼과 맥스축이 달라서 생기는 문제 공유)
얼마전에 맥스에서 제작한 캐릭터를 언리얼에 등록하면서 Prop에 붙은 무기가 언리얼 소켓에 달리면서 Y...
blog.naver.com
기초 4 - 게임플레이 프레임워크
조금씩 rpg요소들을을 추가해가기 전에 에디터에서 기본으로 생성된 프로젝트에 대해 더 살펴볼 예정이다.
새 프로젝트를 만들어서 보면 이미 PlayerStart가 있고 여기에 카메라가 달려있다. 카메라의 위치에 따라 FPS처럼 1인칭일수도 있고 플레이어에서 조금 더 떠있다면 숄더뷰인 TPS일수도 있으며 멀리 하늘에 있다면 RPG가 될 것이다. 결국 컨트롤하고있는 플레이어에 따라 달라진다.
월드세팅 탭에서 게임모드가 있는데 처음에 Default Pawn Class에 따라 플레이어가 정해진다.
또 기본 UI를 HUD라고 한다.
게임 자체를 관재하는 규칙을 관리하는 클래스가 있을 것인데 이것이 게임모드이다. 게임모드를 처음에 기본으로 만들어지는 [프로젝트이름]StartModeBase가 만들어져 있어 이를 그대로 사용해도된다. 직접 만들려면 새 클래스를 만들어도 되는데 새 C++클래스> 부모를 GameModeBase로 해서 여기에 새로운 규칙들을 정의해 사용할 수 있다.
새 맵을 만들기 위해서는 우선 콘텐츠 브라우즈에서 콘텐츠쪽에 새로운 폴더를 추가해 Maps을 만들고 Ctrl + N으로 새로운 맵을 만들 수 있다 이걸 저장할 때 Ctrl + S를 눌러 저장한다..
에디터나 게임을 시작할 때 새로 만든 맵을 바로 띄우길 원한다면 세팅>프로젝트 세팅> 맵&모드에서
맵을 지정해 줄 수 있다.
DefaultPawnClass를 지정하기 위해선 Actor클래스는 불가능하고 Pawn을 상속받아야(예) Character클래스)만 가능하다.
Pawn을 부모로 하여 MyPawn클래스를 만들고 UStaticMeshComponent를 추가해서 가지고 있는 에셋중 스태틱메쉬의 경로를 생성자에서 정해 준 후 월드에 배치해본다.
Actor와 Pawn을 비교해보면 APawn은 AActor를 상속받고 있다.
그밖에도 Pawn은 AIPossed, AIControllerClass 등이 있다. 또 실질적으로 빙의하여 입력을 받을 수 있도록 SetupPlayerInputComponent 함수가 있다는 것을 볼 수 있다.
// Called to bind functionality to input
void AMyPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
다시 만들어진 새로운 MyGameModeBase로 돌아가서 #inlude "MyPawn.h" 를 추가한 후 생성자를 만들고 생성자 안에서 DafaultPawnCalss = AMyPawn::StaticClass(); 로 지정해줄 수 있다. 이는 GameModeBase클래스가 이미 DefaultPawnClass라는 멤버를 가지고 있기 때문이다. 실제로 보면
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Classes)
TSubclassOf<APawn> DefaultPawnClass;
를 가지고 있다.
저장한 후 실행해보면 F8을 눌러 빙의를 해제하면 카메라가 앞써 만들었던 MyPawn의 객체에 붙어있는 걸 알수 있다.
이제 이 MyPawn클래스를 입력을 받아 이동시켜보자.
유니티라면 Tick함수 내에서 if(INPUT) 이런식으로 GetMouseButtonDown 마우스 입력이 있다면, 또는 키보드 입력이 있다면 동작하는 식으로 돌아갈 것이다.
이처럼 만약 입력받는 코드를 하나씩 다 넣다보면 이 MyPawn클래스와 입력받는 코드 사이에 종속성이 생기게된다.그래서 간단하게 만들면 모를까 규모가 커지면 관리하기 힘들게 된다.
언리얼은 구조적으로 만들어져있어 입력받는 것도 별도의 컴포넌트로 뺀 다음에 (UInputComponent) 처음에 SetupPlayerInputComponent에 들어오면 어떤 키로 눌렀을 때 어떤 함수를 호출할지 맵핑하는 식으로 돌아간다.
인자로 받은 PlayerInputComponent->를 하면 BindAxis와 BindAction이 있다 축과 액션의 차이는 콘솔게임에서 조이스틱이 축이고 버튼이 액션이라고 생각하면 된다. 즉, 축은 눌렀다 때는 개념이 아니라 (TRUE/FALSE) 정도 차이가 있는 입력이다.
Axis입력이 들어올 때 호출할 함수를 만들어보자. MPawn클래스 내에 반드시 float값을 인자로 받아야한다.
void UpDown(float Value);
void LeftRight(float Value);
그리고 각 함수 내에 들어오는지 확인하기 위해 UE_LOG(LogTemp, Warning, TEXT("UpDown %f"), Value); 이런식으로 추가하였다.
묶는 방법은 SetupPlayerInputComponent내에서
void AMyPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis(TEXT("UpDown"), this, &AMyPawn::UpDown);
PlayerInputComponent->BindAxis(TEXT("LeftRight"), this, &AMyPawn::LeftRight);
}
이렇게 한 후 컴파일 한 후
엔진에서 세팅한 값과 같게 해줘야한다. 프로젝트 세팅> 입력에서 축 맵핑 두개를 추가해서 이름을 맞춰 UpDown, LeftRight을 추가한 후 UpDown에 W는 1, S는 -1을 스케일로, LeftRight은 왼쪽으로 A를 1로 D를 -1로 해줬다..
컴파일하고 실행해보면 노란색으로 LogTemp : UpDown/ LeftRight과 입력된 값의 합이 들어온다. 축맵핑의 값은 키입력이 없어도 계속 들어온다. 그래서 0의 값이 찍혀 if(Value == 0) return; 을 추가해줬다. 또 참고로 플레이 후 마우스로 한번 클릭을 해줘야 입력을 받는다..!
그런데 이 SetupPlayerInputComponent함수의 내용은 나중에 PlayerControlerClass에 이전하여 분리할 수 있다.
이번엔 진짜 이동을 해보자. 유니티였다면 tranform.posotion이런식으로 바로 좌표를 바꿨을 테지만 그러면 유동적으로 다양한 케이스에 대해 생각해야한다. 물위인지 공중인지 등에 따라 달라지는 연산을 다양하게 된다. 따라서 이를 언리얼에선 새로운 컴포넌트로 빼서 관리한다.
UPROPERTY(VisibleAnywhere)
class UFloatingPawnMovement* Movement;
를 추가하고 생성자에서
Movement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("MOVEMENT"));
이렇게 추가햤다. UFloatingPawnMovement는 움직이는 방법이 여러가지 있는데 간단한 이동을 제공하는 기능을 한다.
근데 이 컴포넌트는 CoreMinimal.h에 포함되어 있지않기 때문에 언리얼 엔진 버전에 따라 달라 빌드가 안될수 도 있다. 그래서 전방선언으로 위처럼 하고 UFloatingPawnMovement가 포함되어 있는
#include "GameFramework/FloatingPawnMovement.h" 를 추가했다.
그리고 UpDown에는
AddMovementInput(GetActorForwardVector(), Value); //월드디렉션(방향)에 액터의 전방을 가져오는 벡터를 넣고 1이면 앞으로 -1이면 뒤로 움직인다.
LeftRight에는 AddMovementInput(GetActorRightVector(), Value);
이렇게 추가해주면 컴파일 후 실행햇을 때 W,A,S,D입력을 했을 때 움직이게 된다.
Engine.h를 CoreMinimal.h대신 가지고 있다면 FloatingPawnMovement.h를 포함하고있어서 UFloatingMovement 컴포넌트를 포함하지 않아도 바로 AddMovementInput함수를 바로 사용할 수있다.
다음엔 캐릭터 메쉬를 추가하고 형태를 갖춰보자..!