본문 바로가기

언리얼5

[언리얼5] 오브젝트/스크립트 생성

게임 제작의 첫걸음. 액터 생성과 C++ 스크립트 생성을 정리해보자. 연결에 대해선 다음 포스팅에서 다루겠다.


용어

우선 언리얼에서 사용하는 용어를 짚고 넘어가보자. 유니티와 미묘하게 달라서 정리해두는 게 좋다.

 

액터

오브젝트 중 화면에 배치되는 것들을 뜻한다. 유니티로 따지면 Transform을 갖는 오브젝트.

 

클래스

유니티의 스크립트. 특정 액터나 오브젝트의 행동을 정의한다. C++나 블루프린트로 생성된다.

 

레벨

유니티의 씬. 오브젝트가 배치되고 게임이 돌아가는 하나의 화면, 맵을 뜻한다.

 

뷰포트

뷰포트

유니티의 씬 뷰. 화면의 왼쪽에서 오브젝트의 배치 현황을 살펴보고, 또 배치할 수 있는 공간을 뜻한다.

 

콘텐츠 브라우저

유니티의 프로젝트 창(에셋 폴더). 다양한 에셋이 저장된다. 언리얼5 기본 레이아웃에선 숨겨져 있지만, 아래쪽의 콘텐츠 드로어 버튼을 누르면 나타난다. 왜 이름이 다른지는 모르겠다. 임시 브라우저라 그런가?


오브젝트(액터) 생성

 

오브젝트의 생성 방법은 간단하다. 재생 버튼 왼쪽에서 큐브와 +가 그려진 버튼을 누르고, PLACE ACTORS 탭에서 원하는 오브젝트를 끌어오면 된다.

 

여기선 액터를 하나 배치해보겠다.

 

 

 

액터가 하나 배치되며 3방향으로 축이 생겼다.

 

 

 

폰도 액터와 같은 아이콘으로 배치되며

 

 

 

캐릭터는 주황빛 원형으로 생성된다. 선이 잘 안 보여 Unlit 모드로 변경해 찍었다.

 

 

 

언리얼4 레이아웃을 사용한다면 왼쪽의 Place Actors 윈도우를 사용하면 된다.


클래스(스크립트) 생성

완전 빈 화면은 또 아니다.

언리얼 프로젝트를 Blank - C++로 제작했을 때 나오는 화면이다.

 

 

 

Tools - New C++ Class를 눌러주면 새 액터를 생성할 수 있다.

혹은 콘텐츠 브라우저 - C++ Classes에서 우클릭을 해도 된다.

 

 

 

언리얼은 상황에 맞는 다양한 C++ 클래스를 생성할 수 있게, 이렇게 템플릿을 제공한다. 화면에 나온 3가지, 캐릭터, 폰, 액터에 대해 가볍게 살펴보자.


액터(Actor)

먼저 가장 밑에 있는 액터부터 살펴보자. 왜 액터냐 하면 얘가 가장 기본이다.

액터의 설명을 읽어보면 "월드에 배치되거나, 소환될 수 있는 오브젝트"라고 되어 있다. 그렇다. 모양을 가진 오브젝트를 언리얼에선 액터라고 한다.

플레이어나 적 오브젝트처럼 움직이는 애들 뿐 아니라 나무, 돌 같은 배경도 액터로 취급된다.

 

 

 

액터를 선택하면 위와 같이 이름, 경로를 작성할 수 있다. 나는 저대로 MyActor를 생성해보겠다.

 

 

 

좌: 소스 파일, 우: 헤더 파일

생성이 끝나면 비주얼 스튜디오에 위와 같은 화면이 뜬다. 자신의 환경에 따라 VS Code나 Rider가 나오기도 한다.

 

액터 클래스를 생성했다고 맵(월드, 씬)에 액터가 생성되진 않는다.

 

잘 보면 알겠지만, 유니티처럼 스크립트 하나로 퉁치는 게 아닌, 헤더 파일소스 파일로 나뉘어 생성된 걸 볼 수 있다. 이는 언리얼 스크립트가 C++ 기반으로 작성되기 때문이다.

 

코드 작성법은 따로 다룰 예정이니, 간단하게 이미 작성된 코드만 살펴보자.

 C++을 배운 적 없거나, 간단하게만 배웠다고 가정하고 설명하겠다.

 

1. AMyActor::AMyActor()

AMyActor는 내가 생성한 클래스다.

내가 작성한 이름은 MyActor인데 왜 AMyActor인가? 라고 묻는다면, 언리얼은 액터 클래스명 앞에 A를 붙여 그것이 액터임을 구분하기 때문이다. 우리의 액터가 AMyActor로 이름이 바뀐 게 아니니 걱정말자.

 

::은 네임스페이스를 연결하는 연산자다. 아마 C++을 대학 과제 풀이 정도로만 써봤다면, using namespace std;를 선언하고 코드를 작성해왔을 것이다.

using 구문을 제거하면 입출력인 cout를 사용할 때도 std::cout로 사용해야 한다. 그래야 그것이 std 파일 내의 cout 함수임을 알 수 있기 때문이다.

 

언리얼 개발에선 using namespace를 사용하는 경우보다, 위처럼 ::를 이용해 범위를 지정해주는 경우가 더 많다고 한다. 여튼 위 구문을 해석해보면, AMyActor 클래스 내의 AMyActor() 생성자 정도 된다.

 

생성자는 객체가 생성될 때 실행되는 특별한 함수이다. 이는 언리얼의 특별한 함수가 아닌, C++의 기본 문법으로 클래스를 만들어봤다면 익숙할 것이다.

 

생성자 내에 작성된 PrimaryActorTick.bCanEverTick = true;는 주석에도 적혀있듯, 아래 Tick 함수가 매 프레임 실행되게 하는 함수다.

 

변수 초기화 같은 초기 세팅에 주로 사용된다.

 

2. void AMyActor::BeginPlay()

BeginPlay는 게임이 실행될 때, 혹은 액터가 스폰될 때 한 번 실행된다. 유니티의 Start()와 같은 역할이다.

 

현재는 Super, 부모 클래스의 BeginPlay를 받아 그대로 실행하는 명령만 존재한다.

 

3. void AMyActor::Tick(float DeltaTime)

Tick은 매 프레임 실행되는 함수로, 게임이 진행되는 동안 계속 실행될 함수다. 유니티의 Update()와 같다.

PrimaryActorTick.bCanEverTick가 true일 때만 실행되며, false일 땐 실행되지 않는다.

 

인자로 전달된 DeltaTime은 유니티의 Time.deltaTime과 동일하다. 서로 다른 프레임 환경에서도 동일한 속도를 보장하기 위한 보정 값이다.

 

예를 들어, 이 Tick 함수가 DeltaTime 없이 동작하며, 내부에 액터를 x축 방향으로 1만큼 이동하는 구문이 있다고 가정하자.

 

30 fps인 컴퓨터에선 액터가 1초에 30씩 이동할 것이다. Tick 함수가 1초에 30번 실행되기 때문이다. 하지만 60 fps 환경에선 60만큼, 120 fps 환경에선 120만큼 이동할 것이다. 같은 게임인데도 이동 속도가 차이나는 것이다.

DeltaTime은 이를 보정해 어떤 환경에서도 동일한 이동 속도를 갖도록 해준다.

 

역시나 부모 클래스의 Tick 함수를 실행하는 것만 존재한다.


폰(Pawn)

폰의 설명은 "빙의되거나, 컨트롤러의 입력을 받을 수 있는 액터"이다. 설명 그대로 입력을 받는 액터다.

플레이어의 제어를 받을 수도, AI의 제어를 받을 수도 있다. 다만 하나의 컨트롤러가 여러 폰을 조종하진 못 한다.

 

 

 

추천받은대로 MyPawn을 생성해보겠다.

 

 

 

액터와 크게 다를 건 없지만, SetupPlayerInputComponent 함수가 새로 생성되어 있다.

이 함수는 컨트롤러의 입력을 받는 함수다.

 

헤더 파일에도 마찬가지로 선언되어 있다.


캐릭터(Character)

캐릭터는 "걷는 기능이 포함된 폰" 타입이다.

 

 

 

이번에도 MyCharacter를 생성해보자.

 

 

 

클래스는 폰과 다를 게 없다.


 

여기까지 오브젝트와 클래스를 배치, 생성하는 방법을 알아보았다. 다음엔 클래스와 오브젝트를 연결하는 방법에 대해 알아보자.


참고 문헌