개발단에 가입하여 베르의 게임 개발 유튜브를 후원해주세요! 

 

베르의 게임 개발 유튜브

안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다! 게임 개발에 도움이 되는 강좌들을 올리는 채널입니다! [투네이션 후원] https://toon.at/donate/637735212761460238 [유니티 어필리에이트

www.youtube.com

 

안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다!

이번에는 언리얼 프로그래밍에 입문하기 위해 기본적인 내용과 C++ 클래스 생성 방법, 그리고 언리얼 액터의 가장 기본이 되는 이벤트에 대해서 알아보겠습니다.

 

사용 엔진 버전 : 5.0.1

 

타임라인

0:00 인트로

0:29 개요

1:37 언리얼 C++ 프로젝트 생성

2:35 C++ 클래스 생성

3:53 생성한 C++ 클래스 살펴보기

5:59 기본 이벤트 함수 호출 확인

7:59 아웃트로

스크립트

인트로

안녕하세요. 여러분들과 함께 게임 개발을 공부하는 베르입니다.

이번 영상에서는 언리얼 엔진 5에서 프로그래밍 하는 방법에 대한 기초를 배워보겠습니다.

이번 영상의 내용은 전반적으로 지난 언리얼 엔진 4 프로그래밍 입문 영상과 대부분 같은 내용입니다.

그럼에도 언리얼 엔진 5 버전으로 다시 만드는 이유는 언리얼 엔진 5에 입문하는데 언리얼 엔진 4 강좌를 보면서 하기에는 세부적인 부분에서는 조금씩 달라진 부분이 있기 때문입니다.

그럼 이제 언리얼 엔진 5 프로그래밍을 배워보도록 하겠습니다.

개요

먼저 언리얼 엔진에서의 프로그래밍은 게임의 기능을 만드는 것을 의미합니다.

캐릭터가 어떻게 조작되어 움직이고, 어떤 규칙으로 목적을 달성해야 게임에서 승리하게 되는지 등 이런 전반적인 것을 프로그래밍 언어나 다른 여러 가지 방법으로 만드는 것입니다.

먼저 언리얼 엔진에서 게임 기능을 만드는 방법은 C++와 블루프린트, 이렇게 두 가지가 있습니다.

C++는 프로그래머가 직접 타이핑으로 코드를 작성해서 프로그래밍을 하는 방법이고, 블루프린트는 눈에 보이는 노드와 노드를 이어 기능의 흐름을 눈으로 보면서 만들어내는 비주얼 스크립팅입니다.

C++ 클래스로만 게임을 제작하거나 블루프린트 클래스만을 이용해서 게임을 제작하는 것도 충분히 가능하지만, 이 두 방법을 완전히 따로 사용하는 것보다는 프로그래머가 C++로 베이스가 되는 게임 기능을 만들고, 디자이너가 블루프린트를 이용해서 그 기본 게임 기능을 가지고 게임의 재미를 위한 기능들을 제작하는 방식으로 함께 사용할 때 시너지 효과를 발휘합니다.

앞으로 C++ 클래스와 블루프린트 클래스를 넘나들면서 언리얼 엔진에서 게임 기능을 만드는 방법을 배우게 될 겁니다.

참고로 언리얼 C++ 프로그래밍을 배우기 위해서는 먼저 C++나 다른 프로그래밍 언어를 어느 정도 익숙한 수준으로 다룰 줄 아는 편이 좋지만 이런 부분까지 포함해서 하나씩 배워나가 봅시다.

C++ 프로젝트 생성

그럼 제일 먼저 언리얼 엔진에서 C++ 프로젝트를 만드는 방법을 배워봅시다.

우선 언리얼 엔진을 실행하겠습니다.

그리고 새 프로젝트의 카테고리를 게임으로 선택합니다.

그 다음 템플릿에서는 여러 가지 게임 장르의 기반이 되는 다양한 템플릿을 볼 수 있는데 지금은 완전히 빈 프로젝트를 생성하는 기본 템플릿을 선택하겠습니다.

프로젝트 디폴트 세팅에서는 프로젝트 타입과 퀄리티, 타깃 플랫폼, 시작용 콘텐츠 포함 여부 등을 선택할 수 있는데 여기서 중요한 부분은 프로젝트 타입입니다.

기본으로 블루프린트가 선택되어 있는데 우리는 여기서 C++를 선택해야 합니다.

만약 여기서 블루프린트를 선택하고 프로젝트를 생성하면 해당 프로젝트에서는 C++ 클래스를 생성하지 못하고 블루프린트만을 이용해서 게임을 제작해야 합니다.

우리는 언리얼 C++ 프로그래밍을 배우면서 블루프린트를 함께 다룰 계획이기 때문에 C++를 선택하겠습니다.

그리고 프로젝트 이름을 입력하고 프로젝트를 생성합니다.

C++ 클래스 생성

프로젝트의 생성이 끝나고 나면 제일 먼저 C++ 클래스를 생성하는 방법을 배워보겠습니다.

새로운 C++ 클래스를 생성하는 방법은 여러 가지가 있습니다.

첫 번째 방법은 상단 메뉴 바에서 [툴 > 새로운 C++ 클래스] 항목을 선택하는 것이고 두 번째 방법은 콘텐츠 브라우저에서 C++ 클래스 폴더를 선택하고 추가 버튼을 누르거나 콘텐츠 브라우저의 빈 영역에 우클릭해서 새 C++ 클래스 항목을 선택하는 것입니다.

그렇게하면 추가할 클래스의 부모 클래스를 선택할 수 있는 창이 뜹니다.

여기서 부모 클래스를 선택하면 우리가 만드는 클래스는 선택한 부모 클래스의 변수와 함수를 상속받아 사용할 수 있게 됩니다.

언리얼 엔진에서 미리 구현해둔 여러 클래스들이 보이는데 일단 이중에서 Actor를 선택하도록 하겠습니다.

액터 클래스는 레벨에 배치할 수 있는 오브젝트를 의미합니다.

보통 레벨에 배치되어 동작할 오브젝트를 만들 때는 이 액터 클래스를 상속받아서 클래스를 구현하면 됩니다.

다음을 클릭하고 새 액터의 이름을 정한 뒤 클래스 생성 버튼을 클릭해주면 됩니다.

지금은 그냥 MyActor라는 이름 그대로 클래스를 생성하겠습니다.

생성한 C++ 클래스 살펴보기

C++ 클래스의 생성이 끝나고 나면 콘텐츠 브라우저의 소스 패널을 열고 [C++ 클래스 > (프로젝트 이름)]의 폴더를 열어보면 방금 생성한 MyActor 클래스가 보입니다.

그리고 이 MyActor 클래스를 스크립트 에디터에서 보면 MyActor 클래스에 대한 헤더 파일(.h)과 소스 파일(.cpp)이 생겨난 것을 확인할 수 있습니다.

이미 C++에 대해서 공부한 분들이라면 알고 있겠지만 모르는 분들을 위해서 설명하자면 헤더 파일은 클래스를 선언하고, 클래스에 속하는 변수와 함수의 원형을 선언하는 용도의 파일입니다.

헤더 파일의 클래스에서는 함수의 원래 형태만 알려주는 것이기 때문에 이렇게 함수의 내용은 없고 세미콜론으로 함수가 끝나는 것을 볼 수 있습니다.

이 함수의 몸체는 이렇게 소스 파일에서 구현됩니다.

처음 만들어진 액터 클래스에는 이렇게 클래스 이름과 같은 이름을 가진 생성자, BeginPlay 함수, Tick 함수가 생성되어 있습니다.

우선 클래스 이름과 같은 이름을 가진 생성자는 클래스의 객체가 생성될 때 한 번 호출되는 함수이며 주로 생성된 액터의 프로퍼티, 즉 변수의 기본 값을 설정해주는데 사용됩니다.

BeginPlay 함수는 액터가 배치된 월드에서 게임이 시작되거나 액터가 월드에 스폰되었을 때 한 번 호출되는 함수로 게임플레이 로직을 초기화시키는 데 사용됩니다.

그리고 Tick 함수는 매 프레임마다 한 번씩 호출되는 함수입니다.

이 Tick 함수에서는 매개변수인 DeltaTime을 통해서 Tick 함수가 지난 번에 호출된 이 후로 얼마의 시간이 경과한 뒤에 다시 Tick 함수가 호출되었는지에 대한 시간을 전달받을 수 있습니다.

그리고 이 Tick 함수는 Actor가 활성화되어 있는 동안 계속해서 호출되기 때문에 주로 게임의 로직을 처리하는 기능을 구현하는데 사용됩니다.

하지만 지금 만드는 클래스에 매 프레임 호출되는 Tick 함수가 필요하지 않다면 Tick 함수를 제거해서 게임의 퍼포먼스를 상승시킬 수도 있습니다.

Actor의 틱 함수를 비활성화하기 위해서는 헤더 파일과 소스 파일에서 Tick 함수를 제거하고 소스 파일의 생성자에서 PrimaryActorTick.bCanEverTick = true 코드를 제거해주면 됩니다.

이 코드는 이 Actor가 Tick 함수를 매 프레임 호출하도록 설정하는 코드입니다.

처음 생성한 C++ 클래스는 이렇게 생성자, BeginPlay 이벤트 함수, Tick 이벤트 함수를 가진 채로 생성됩니다.

이제 여기서 어떤 변수와 함수를 만들어 나가느냐에 따라서 클래스의 기능이 결정될 겁니다.

이벤트 함수 호출 확인

그럼 마지막으로 언리얼 C++ 클래스의 생성자와 기본 이벤트 함수들이 언제 호출되는지 확인해보기 위해서 간단한 로그를 남겨봅시다.

언리얼에서 로그를 남기기 위해서는 UE_LOG라는 매크로 함수를 사용합니다.

첫 번째 매개변수에는 LogTemp, 두 번째 매개변수에는 Log, 세 번째 매개변수에는 출력할 문자열을 넣어주면 됩니다.

생성자에서는 "Constructor"라는 문자열을 출력하게 만들고 BeginPlay 이벤트 함수에서는 "BeginPlay"를, Tick 이벤트 함수에서는 "Tick"이라는 문자열을 출력하게 코드를 작성합니다.

로그 출력 코드를 모두 작성하면 코드를 저장하고 변경한 코드의 내용이 언리얼 에디터에 적용될 수 있게 컴파일을 진행해야 합니다.

언리얼 엔진 4에서는 메인 툴바에 컴파일 버튼이 있었지만 언리얼 엔진 5부터는 컴파일 버튼이 사라지게 되었습니다.

언리얼 에디터를 종료하지 않고 컴파일 하기 위해서는 콘솔 명령창에서 LiveCoding.Compile 명령어를 입력하거나 [Ctrl + Alt + F11] 단축키를 눌러 라이브 코딩 기능을 사용해야 합니다.

라이브 코딩 기능으로 컴파일을 진행하면 이렇게 라이브 코딩 창이 뜨면서 진행상황을 알려줍니다.

가끔 컴파일 후에도 바로 변경사항이 적용되지 않을 때가 있는데 그럴 때는 언리얼 에디터를 종료하고 비주얼 스튜디오의 상단 메뉴 바에서 [Build > Rebuild Solution] 항목을 선택해서 프로젝트를 다시 빌드하고 실행하면 됩니다.

변경된 코드의 내용이 에디터에 적용되고 나면 출력되는 로그를 보기 위해서 하단 툴바의 출력 로그 버튼을 눌러 출력 로그 패널을 열고 [레이아웃에 고정] 버튼을 눌러 줍니다.

그 다음에는 MyActor 하나를 씬에 배치하고 게임을 플레이 시킵니다.

그러면 게임이 시작되자마자 빠르게 Tick로그가 출력되는 것을 볼 수 있는데 게임을 일시정지시키고 로그를 거슬러 올라가면 Play In Editor 모드가 초기화될 때 MyActor의 생성자가 실행되어서 Constructor 로그가 출력되고 PIE 모드가 시작되면서 BeginPlay 로그가 출력된 것을 볼 수 있습니다.

그리고 그 아래로는 매 프레임 Tick 로그가 출력된 것을 확인할 수 있습니다.

아웃트로

이렇게 이번 영상에서는 언리얼 엔진 5 C++ 프로그래밍에 입문하기 위한 대략적인 내용과 C++ 클래스를 생성하는 방법 그리고 C++ 클래스의 생성자와 언리얼 엔진의 기본 이벤트 함수인 BeginPlay와 Tick의 동작에 대해서 알아보았습니다.

이제 이 다음 영상에서는 C++ 클래스의 변수와 함수 등 좀 더 깊은 내용에 대해서 배우게 될 겁니다.

이 강좌는 시청자 여러분들의 시청과 후원으로 제작되었습니다.

이상 베르의 게임 개발 유튜브였습니다. 감사합니다.

 

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Player Controller

 

작성 기준 버전 :: 4.21.1

 

언리얼 엔진에서 Player Controller는 Pawn과 그것을 제어하려는 사람 사이의 인터페이스로서, 플레이어의 의지를 대변하는 클래스이다. Player Controller는 Controller 클래스를 상속받는데, Possess() 함수로 Pawn의 제어권을 획득하고, UnPossess() 함수로 제어권을 포기한다.

 

컨트롤러에 대한 언리얼 문서

 

플레이어 컨트롤러에 대한 언리얼 문서

 

이러한 설명이 언리얼 문서 상에서의 Player Controller에 대한 내용의 대부분이고, 실제적인 C++ 코드에서의 사용법은 레퍼런스에서 찾아서 멤버 변수와 함수 등을 직접 찾아서 읽어야 하는 불편함이 있기 때문에 입문자는 Player Controller의 사용법을 제대로 익히기가 쉽지 않다.

 

그럼 이제부터 Player Controller의 기본적인 기능과 사용법에 대해서 알아보도록 하자.

 

 

마우스 인터페이스

 

플레이어 컨트롤러가 기본적으로 제공하는 기능 중에는 마우스 인터페이스에 관련된 기능이 있다.

 

플레이어 컨트롤러를 상속받는 블루프린트 클래스를 만들고 에디터를 열어보면 디테일 패널의 Mouse Interface 카테고리에서 플레이어 컨트롤러가 기본적으로 제공하는 마우스 인터페이스와 관련된 기능들을 한눈에 볼 수 있다. 디테일 패널에서 보이는 것들은 아주 기본적인 내용으로 이보다 심도깊은 기능에 대해서 배우고자 한다면 APlayerController 클래스의 언리얼 레퍼런스를 확인하는게 좋다.

 

 

bEnableClickEvents = true;
bEnableTouchEvents = true;
bEnableMouseOverEvents = true;
bEnableTouchOverEvents = true;

 

C++ 코드 상에서는 UPlayerController 클래스의 위와 같은 프로퍼티들을 통해서 옵션을 바꿀 수 있다.

 

Show Mouse Cursor

 

Show Mouse Cursor 옵션은 말 그대로 게임 도중에 마우스 커서를 보이게 할 것인가에 대한 옵션이다. 예를 들어보자면, 전통적인 PC 플랫폼에서의 탑다운뷰 RPG 게임은 캐릭터를 이동시킬 때 주로 마우스를 이용한다. 때문에 커서가 항상 보이도록 이 옵션을 true로 해줘야 한다. 하지만 FPS 게임은 화면 중앙의 크로스헤어에 적을 일치시키고 공격하는 방식이기 때문에 커서가 보일 필요가 없다. 혹은 위 두 가지 사례가 융합된 장르들은 전투나 이동 같은 플레이 중일 때는 커서를 비활성화시킨 상태로 크로스헤어를 사용하고, 인벤토리나 지도를 열면 커서를 활성화시키는 방식으로 사용하기도 한다.

 

bShowMouseCursor = true;

bShowMouseCursor = false;

 

C++ 코드 상에서는 UPlayerController 클래스의 bShowMouseCursor 프로퍼티를 통해서 옵션을 바꿀 수 있다.

 

Event

 

그 다음 네 가지 옵션은 월드에 배치된 액터/컴포넌트가 마우스나 터치에 대해서 이벤트를 발생시킬지에 대한 프로퍼티이다.

 

Click Event / Touch Event는 액터/컴포넌트에 대한 클릭/터치 이벤트이고, Mouse Over Event / Touch Over Event는 액터/컴포넌트에 커서나 터치가 올라가 있는 상태에 대한 이벤트이다.

 

간단하게 예시를 보자면 언리얼 프로젝트에 테스트용 C++ 클래스를 하나 만들고 NotifyActorOnClicked() 함수를 덮어씌워서 다음과 같이 구현하고 프로젝트를 다시 빌드한다.

 

virtual void NotifyActorOnClicked(FKey PressedButton = EKeys::LeftMouseButton) override;

 

void AClickEventTestActor::NotifyActorOnClicked(FKey PressedButton)
{
    UE_LOG(LogTemp, Log, TEXT("NotifyActorOnClicked"));
}

 

그리고 새 Player Controller를 하나 만들어서 Show Mouse Cursor와 Enable Click Event를 true로 설정한 뒤, 프로젝트 설정의 맵 & 모드에서 기본 플레이어 컨트롤러로 설정한다.

 

그 다음 아까 만든 액터를 레벨에 배치하고 클릭할 수 있게 스태틱 메시 컴포넌트를 추가해주자.

 

레벨 에디터에서 플레이 버튼을 누르고 추가한 액터를 클릭해보면 출력 로그 패널에서 "NotifyActorOnClicked"라고 출력되는 것을 확인할 수 있다.

 

 

액터나 컴포넌트에 Click Event를 추가했더라도 Player Controller에서 Enable Click Event를 false로 설정했다면, NotifyActorOnClicked() 이벤트는 호출되지 않는다.

 

Default Mouse Cursor

 

Default Mouse Cursor 프로퍼티는 기본적인 마우스 커서 모양을 정하는 프로퍼티이다. 커서 모양의 종류는 언리얼 문서에서 확인할 수 있다.

 

DefaultMouseCursor = EMouseCursor::Default;

 

C++ 코드 상에서는 UPlayerController 클래스의 DefaultMouseCursor 프로퍼티를 통해서 옵션을 바꿀 수 있다.

 

Default Click Trace Channel

 

Default Click Trace Channel은 클릭 이벤트가 발생했을 때, 플레이어가 클릭한 대상을 3D 공간에서 찾기 위해서 트레이스를 통해 광선 같은 개념의 선을 쏘는데 어떤 채널에 속하는 객체가 맞았을 때 찾았다고 판정할 지를 정하는 프로퍼티이다. "Visibility"가 기본값으로 되어 있는데, 이는 화면에 보이는 객체가 맞으면 무조건 맞았다고 판정한다는 의미이다.

 

이 값의 종류에는 Visibility 이 외에도, Pawn만 찾아내는 Pawn, 월드에 스태틱으로 배치된 액터만 찾아내는 WorldStatic 등이 있다.

 

DefaultClickTraceChannel = ECollisionChannel::ECC_Visibility;

 

C++ 코드 상에서는 UPlayerController 클래스의 DefaultClickTraceChannel 프로퍼티를 통해서 옵션을 바꿀 수 있다.

 

Trace Distance

 

Trace Distance 프로퍼티는 클릭 이벤트 등이 발생했을 때, 화면으로부터 얼마나 멀리 떨어진 지점까지 대상을 탐색할 것인가에 대한 것이다.

 

HitResultTraceDistance = 10000.0f;

 

그 외의 유용한 마우스 관련 함수

 

1. GetHitResultUnderCursor()

 

GetHitResultUnderCursor() 함수는 함수 이름 그대로 화면에서 마우스 커서 위치에 트레이스를 쏴서 그 결과를 가져오는 함수이다. 일반적인 함수의 사용법은 아래와 같으며, 이 함수는 레벨에 배치된 오브젝트를 선택하거나, RPG 식으로 클릭한 위치로 캐릭터를 이동시킬 때, 유용하게 사용할 수 있다.

 

FHitResult Hit;
GetHitResultUnderCursor(ECC_Visibility, false, Hit);

if (Hit.bBlockingHit)
{
    // 충돌 결과가 있을 때의 처리
}

 

 

플레이어 틱(Player Tick)

 

플레이어 컨트롤러는 액터 클래스에서 상속받는 Tick() 함수 외에 PlayerTick() 이라는 별도의 틱 함수를 하나 더 가진다. 일반 Tick() 함수는 어디서나 작동하는 반면에, PlayerTick() 함수는 Player Controller에 PlayerInput 객체가 있는 경우에만 호출된다. 따라서 로컬로 제어되는 Player Controller에서만 플레이어 틱이 호출된다. 이 말인 즉슨, 만약 멀티플레이 게임이라면 자기 자신의 플레이어 컨트롤러에서만 플레이어 틱이 호출된다는 것이다.

 

virtual void PlayerTick(float DeltaTime) override;

 

void ATestPlayerController::PlayerTick(float DeltaTime)
{
    Super::PlayerTick(DeltaTime);

}

 

플레이어 틱 함수를 덮어쓰기 위해서는 위와 같이 코드를 작성하면 된다.

 

 

폰(Pawn)

 

플레이어 컨트롤러는 기본적으로 자신이 빙의(Possess)하여 컨트롤하는 폰을 레퍼런스로 가지고 있게 된다. 일반적으로 프로젝트 세팅의 맵 & 모드나 월드 세팅에서 디폴트 폰을 None이 아닌 특정 폰으로 설정해둔 상태라면, 게임이 시작되면 폰이 생성되고 플레이어 컨트롤러는 생성된 폰에 자동으로 빙의된다.

 

디폴트 폰이 None이어서 자동으로 빙의되는 상황이 아니라면, Possess() 함수를 이용하여 빙의하고자 하는 폰에 컨트롤러를 빙의시키면된다.

 

Possess(TargetPawn);

 

이 반대의 경우로 현재 빙의하고 있는 폰에서 제어권을 포기하고 탈출하려고 한다면 UnPossess() 함수를 사용하면 된다.

 

UnPossess();

 

컨트롤러가 빙의 중인 폰을 가져와서 사용하기 위해서는 다음과 같이 GetPawn() 함수를 사용하면 된다.

 

APawn* MyPawn = GetPawn();

 

만약에 컨트롤러가 현재 컨트롤 중인 폰이 없는 상태라면 GetPawn() 함수는 nullptr를 반환한다.

 


 

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

+ Recent posts