언리얼 엔진 시작하기 1

2016. 11. 28. 17:27기술/언리얼 스터디

반응형

이 글의 작성 목적은 게임 개발 방법을 아는 상태에서 언리얼 엔진을 익히고자 하는 것입니다.

어떻게 언리얼 엔진에 접근해야 할지 난감한 분들이 보시기에 좋을것 같습니다.

그리고 제가 어떻게 언리얼 엔진을 익혀가는지 보실 수 있습니다.


저는 현재 언리얼 엔진은 다루어 본 적이 없는 경력 4년 5개월 유니티 경력 2년 되는 클라이언트 프로그래머 입니다.


시작 시간 2016-11-28일 오후 4시 30분



언리얼 런처는 설치되어 있습니다.

언리얼 홈페이지에서 학습자료 > 문서 > 프로그래밍 퀵스타트를 눌러서 들어갑니다.

엔진이 4.12.5 버전 이었는데 4.14.0 버전이 새로 나와서 새로 설치 하겠습니다.
설치하는 동안에 문서를 읽어 봅니다.


다음으로 학습자료 > 비디오 튜토리얼 > FBX와 캐릭터 컨트롤에 대해서 알아 볼 것입니다.



학습자료 > 문서 > UE4 시작하기 > 프로그래밍 부분도 보아야 할 것 같습니다. 외에도 홈페이지에 무엇이 있는지 살펴 보고 있었습니다.


34분 걸려서 5시 4분에 언리얼 엔진 4.14.0 버전이 설치 완료가 되었습니다. 엔진을 시작하면 한 1분 정도 걸려서 새 프로젝트 창이 열립니다.



집 컴퓨터 사양은 다음과 같습니다. 

컴퓨터 사양이 좋지 않고 프로젝트의 위치가 SDD가 아니라서 컴파일이 느릴 것입니다.



프로젝트 만들기를 하면 3분 정도 걸려서 프로젝트가 만들어 집니다. ( Visual Studio도 함께 )



메뉴얼에서 하라는 데로 파일 > 새 C++ 클래스 추가 > Actor > 이름 : FloatingActor로 하고 

클래스 생성 버튼을 누릅니다.

메뉴얼에 나와 있는 코드를 작성 후 언리얼 엔진의 컴파일 버튼을 누릅니다.

그리고 에디터 콘턴츠 브라우저에 있는 FloatingActor를 에디터 창에 드래그 앤 드롭 한 후 월드 아웃라이너 창에서

해당 액터에 원뿔 컴포넌트를 추가하고 위치를 -200, 0, 200으로 맞추어 줍니다.


그리고 언리얼 에디터의 플레이 버튼을 누르면 원뿔이 위, 아래로 움직이는 것을 확인할 수 있습니다.


그러면 코드를 분석해 보도록 하겠습니다.


일단은 전체적인 설계 부터 분석 하겠습니다. 예제에서 만든 AFloatingActor는 AActor 클래스에서 상속 받았습니다.

홈페이지에서 찾아 보다가 레퍼런스 문서를 찾을 수 없어서 검색란에 AActor로 검색 하였습니다. ( 현재 시간 5시 51분 )



AActor를 상속하는 자식 클래스가 156개 정도 있는 것을 알 수 있습니다.

AActor의 상위 클래스로는 UObject, UObjectBaseUtility, UObjectBase 클래스가 있습니다.


검색란에 Tick을 검색 해 보면 AActor에 있는 것을 확인할 수 있습니다.


Function called every frame on this Actor. ( 이 엑터의 매 프레임에 함수가 호출 됩니다. ) 입니다.



Super::Tick( DeltaTime ); 상위 클래스의 Tick 함수를 호출 해 줍니다.


GetActorLocation(); 은 Returns the location of the RootComponent of this Actor 

( 이 엑터의 루트 컴포넌트의 위치를 반환합니다. )


FVector는 검색해 보면 구조체인 것을 알 수 있습니다. 함수를 확인하다 보면 주요 함수중에 Dist 라는 함수가 있습니다.

두 벡터의 길이를 반환합니다. 주로 벡터와 벡터 연산에 대한 기능들이 있는 것으로 추측 됩니다. ( 내적, 외적 )


Dist 함수가 있으면 적과 플레이어 캐릭터의 거리를 알 수 있을 것입니다. 공격 거리 안에 있으면 공격 함수를 호출하고 데미지 계산을 해 주면 됩니다. ( 3D Action RPG 프로젝트에서 처리 했던 방식 )


FMath는 검색해 보면 모든 수학 함수를 도움을 주는 구조체( Structure for all math helper functions )라고 나와 있습니다.

FMath::Sin (RunningTime + DeltaTime)


Tick의 DeltaTime은 마지막 프레임에서 흐른 게임 시간

Game time elapsed during last frame modified by the time dilation ) 이라고 되어 있습니다.


60 프레임이라면 모니터가 초당 60번 깜박이는 것을 말하고 그렇다면 1 프레임에 0.0167의 값이 들어 옵니다.

그래서 RunningTime += DeltaTime은 현재 흐르고 있는 시간을 의미 합니다. 0.0167값이 누적 됩니다.


Sin( RunningTime + DeltaTime ) - Sin( RunningTime ) 을 해주면 RunningTime은 이전 프레임 시간과 같으므로

현재의 높이를 얻을 수 있습니다.



Sin 그래프는 -1 ~ 1 사이의 값을 갖는데 각도 대신에 시간 흐름이 사용 되었습니다. 


※참고

Sin 그래프 강의 보기 (http://blog.naver.com/dewsalang/110119012297)

y = asin(bx) 공식에서 a가 2 라면 사인 그래프가 위 아래로 2배 늘어납니다. b가 2라면 좌우로 그래프가 절반 찌그러 집니다.


그리고 sin(a+b) = sin(a)cos(b) + cos(a)sin(b) 공식과 헷갈려 하실 수 있는데 프로그래밍 에서는 sin(a+b)를 위 공식과 같이 풀어서 계산 하지 않습니다.

위 공식과 같이 계산해 주려면 sin(a)cos(b) + cos(a)sin(b) 공식을 코딩해 주어야 합니다.

FMath::sin(RunningTime) * FMath::cos(DeltaTime) ... 이런식으로 말이죠.


만약 a가 30이고 b가 1 이라고 하면 프로그래밍 에서는 그냥 30+1를 먼저 계산 한다음 sin(31) 값을 반환합니다.

프로그래밍에서 계산 순서

Sin( RunningTime + DeltaTime ) - Sin( RunningTime )

1프레임

= Sin(30+1) - Sin(30)

= Sin(31) - Sin(30) = 0.5

2프레임

RunningTime이 증가해서 31이 된다면

Sin(31+1) - Sin(31)

= Sin(32) - Sin(31) = 0.51

이동값 = 0.01


결론적으로 DeltaTime 에 움직인 값이 됩니다. 그냥 그래프에서 Sin(31)부분의 값과 Sin(30) 부분의 값을 뺄셈 해 보면 ` 이런 모양이 나오겠죠? 그리고 Sin(32) 부분의 값과 Sin(31) 부분의 값을 뺄셈 해 보면 ` 모양이 나오겠죠. 1 프레임에 그 모양만큼 움직인다고 보시면 됩니다. 아마 `를 0.01 기준으로 본다면 ` 모양이 길어 질 때, 0.011 값이 커져서 조금더 빨리 움직이는 것처럼 보이겠죠.


아마 180도가 넘어가는 부분에서 -값 이니까 원 뿔이 아래로 내려갈 것입니다.


NewLocation.Z += DeltaHeight * 20.0f;

GetActorLocation()으로 얻은 이 엑터의 위치 값 Z에 크기 값으로 20을 곱해서 대입해 줍니다. 20만큼 곱해주는 이유는 에디터 상의 20의 위치를 생각해 보면 됩니다. ( 에디터 상에서 100만큼 이동 시켰을 때 물체의 위치가 ----- 이정도면 20만큼 움직이면 - 이정도 되겠지요. )


SetActorLocation(NewLocation);은 이 오브젝트의 위치를 설정하는 함수인 것을 알 수 있습니다.

그렇다면 이 NewLocation 위치 벡터에 컨트롤러의 방향값을 계산해 주면 캐릭터의 위치를 움직일 수 있게 될 것입니다.


이렇게 해서 Sin 그래프 모양데로 원뿔이 위, 아래로 움직일 수 있게 됩니다.


현재 시간이 오후 7시 19분 입니다. ~ 오후 8시 8분까지 뉴스 좀 보고 저녁 식사와 휴식 시간을 가졌습니다.



1 - Introduction

2 - Building The Base Level

그럼 이제 언리얼 시작하기에서 위 튜토리얼을 진행해 보겠습니다.



새 프로젝트를 만들고 C++, 삼인칭을 선택 합니다.

데스크탑/콘솔 대신에 모바일/태블릿을 선택 하면 조이스틱 컨트롤러가 추가 된다고 합니다.

튜토리얼 동영상에서 하라는 데로 진행 합니다. 진행하다 보면 리소스가 필요하다고 합니다.

새로 생성된 프로젝트를 실행 해 보면 캐릭터가 뛰어 다니고 점프하는 것을 볼 수 있습니다.

맵 위에 캐릭터 존재하기, 그림자 처리등은 기본적으로 되어 있습니다.


영문을 읽어보면 import needed assets from Content Examples ( 콘텐츠 예제의 에셋들 추가가 필요합니다. )

런처 > 학습 > 콘텐츠 예제를 찾을 수 있습니다. 



프로젝트 생성 버튼을 누르면 해당 폴더에 다운로드를 시작합니다.


다운로드 되는 동안 동영상을 마저 봅니다. 보통 셋팅하는 것이 대부분인 것 같습니다.

다운로드가 다 되면 런처에서 프로젝트를 하나 더 생성 하든지 아니면 열려 있는 언리얼 에디터에서 프로젝트 열기를 해서

ContentExamples를 하든지 합니다. 그리고 필요한 에셋 3가지를 BatteryCollector/Contents 폴더로 이주 시킵니다.

콘텐츠 브라우저에서 BP_DemoRoom(맵), SM_Battery_Medium(오브젝트), P_electricity_arc(파티클)


그러니까 어떤 맵을 하나 만들어 놓고 이주를 시키면 다른 프로젝트에서도 그 맵을 그데로 사용할 수 있는 기능이네요. 연관되어 있는 메쉬, 머터리얼, 텍스쳐들도 함께 옮겨집니다.

하나씩 이주 시켜야 하므로 이주할게 많아질 경우 번거롭겠네요.


다 이주 시켰으면 BatteryCollector 프로젝트를 다시 엽니다.

파일 > 새 레벨 > Default로 새 레벨을 만들어 주고 콘텐츠 브라우저에서 콘텐츠 폴더 아래에 Maps 폴더를 만든 후 CollectionLevel이라는 이름으로 저장합니다.


상단에 세팅 버튼 > 프로젝트 > 맵 & 모드에서 Default Map을 CollectionLevel로 설정해 주면 에디터가 열릴 때 항상 이 맵이 열리게 된다고 합니다.


월드 아웃라이너에서 기본 맵 오브젝트를 지우고 콘텐츠 폴더에서 BP_DemoRoom을 검색해서 레벨에 드래그 앤 드롭 하면 월드상에 맵이 생깁니다. 위치는 0, 0, 0으로 맞춰줍니다. 맵 오브젝트가 이미 구성 되어 있는 것을 확인할 수 있습니다.

실무에서 만들 때는 아마도 매쉬, 재질, 텍스쳐, 라이트, 환경 오브젝트 등등을 설치해서 맵을 하나 만들 것입니다.


이미 작성되어 있는 Room Properties가 있습니다. 간단한 설정으로 맵을 실시간으로 변하게 할 수 있네요.

좋은 기능인것 같습니다. 하나의 맵으로 다양함을 줄 수 있습니다.


그리고 월드 아웃라이너에서 Player Start를 선택한 다음 맵의 가운데에 놓고 언리얼 에디터의 플레이 버튼을 누르면 플레이어가 맵만 바뀐 상태에서 뛰어다니고 점프를 할 수 있습니다. 맵 구성은 디자이너 몫이겠죠. 산이면 산 집이면 집 그냥 구성되어 있는 맵 오브젝트가 있으면 됩니다. ( 물론 깊이있게 파고 들면 할게 많아 지겠지요. )


현재 시간이 오후 9시 41분 입니다. 일단 오늘은 여기에서 마무리 하도록 하겠습니다.


2016-11-28 언리얼 홈페이지 둘러보기, 튜토리얼 따라하기, 웹페이지 작성 하는데 대략 총 4시간 12분 걸렸습니다.

시간을 체크하는 이유는 대략적으로 이정도 진행 했을 때 걸리는 시간을 계산하기 위해서 입니다.

이후로 모바일 컨트롤러 사용방법을 익히고 맵 위에 몬스터 추가해 주고 전투 하는 것을 구현하면 될 것 같습니다.


참고로 라이트닝 빌드를 하려면 언리얼 에디터의 빌드 버튼에서 라이트 빌드를 하면 됩니다.


3 - Making Your First Pickup Class

현재 시간이 2016-11-29 오전 7시 10분 입니다. 아침 식사를 하기 전 잠깐 들렸습니다.


동영상에서는 아주 기본적인 아이템을 만들 것인데, 월드에 놓이는 모양이 있고 캐릭터와 상호작용할 수 있도록 하는 방법을 생각해 보면 Actor를 만들어야 한다고 설명합니다. 

액터의 특징 한 가지가 나오는데요, 액터는 월드의 어느 위치에서든 만들어지고 놓일 수 있다고 합니다. 일단은 Actor를 부모 클래스로 하여 PickUp이라는 객체를 만들어 봅니다. ( 11분정도 아침식사 )


헤더 파일을 선언할 때, 유의사항에 대해서 알려줍니다. #include "Pickup.generated.h"를 무조건 제일 아랫줄에 선언하라고 합니다. 그 이유는 BuildTool이 제일 마지막으로 찾아보는 파일이기 때문입니다.


UCLASS()는 에디터가 가비지 콜렉션, 키워드 설정, UObject의 특성들을 갖도록 하는 것들을 할 수 있도록 알려주는 매크로라고 합니다.


GENERATED_BODY는 보일러플레이트 텍스트로 엔진 링킹 등을 담당합니다. 생성자는 기본값을 설정하는 곳이고 BeginPlay()는 씬에 이 객체가 불려 졌을 때 실행되는 함수입니다. Super::BeginPlay()를 호출하는데 씬에 등록시키는 것 같은 역할을 하는가 봅니다.


생성자에 PrimaryActorTick.bCanEverTick = false;로 하면 Tick 함수를 호출하지 않는다고 합니다. 함수를 지워도 되지만 어차피 호출되지 않을 것이므로 그냥 두어도 된다고 합니다.


스태틱 메시를 하나 선언할 것인데 이 클래스의 내부에서만 사용할 것이기 때문에 private로 선언 합니다.

메시를 static으로 만드는 이유는 아마 메시를 모든 오브젝트에서 공유해서 사용하려고 하기 때문일 것입니다.

private:

class UStaticMeshComponent* PickupMesh; 이라고 선언해 주면 됩니다.

C#을 2년정도 하다가 오랫만에 포인터가 나옵니다. 포인터는 4byte 변수로 주소값을 대입시킬 수 있습니다.

포인터는 그 주소를 가리키고 있습니다. pickup-> 을 하면 그 메모리 주소에 있는 값에 접근할 수 있습니다.

그 주소에 메모리 할당이 되어 있지 않으면 null pointer exception이라는 에러 메시지가 출력 되겠죠.

포인터는 그냥 C언어의 기초 입니다.


UCLASS처럼 UFUNCTION과 UPROPERTY 매크로가 있다고 합니다. 여기에서는 UPROPERTY 매크로를 사용할 것인데

VisibleAnywhere, BlueprintReadOnly, Category = "pickup", meta = (AllowPrivateAccess = "true"


여기에서 private에 대해 잠깐 설명 하겠습니다.

C++에서 코드 작성시 변수는 대부분 private로 먼저 생각 합니다. 외부에서 이 변수에 접근해서 값을 할당하는 것을 방지하기 위해서 입니다. 값은 생성자에서 초기화 하며 대부분 함수를 통해 할당하거나 변경합니다.

대신에 외부 클래스에서 ( 프로그래밍 협업시 ) 이 변수의 값을 사용해야 할 때가 있을 텐데요, 그래서 Get 함수를 만들어 줍니다.


다시 말해서 readonly와 비슷한 개념이라고 보시면 됩니다. 값을 쓸 수는 없으나 읽어서 사용은 가능 하다는 것입니다.


FORCEINLINE class UStaticMeshComponent* GetMesh() const { return PickupMesh; }


FORCEINLINE은 inline 함수가 가능하도록 하는 매크로 인 것 같습니다. 

( 아주 정확한 내용은 추후 더 알아 보도록 하겠습니다. )

포인터 형으로 반환하며 const 를 선언해 준 것을 알 수 있습니다. 값 변경이 외부에서는 불가능 하다는 것이죠.

매우 중요한 개념 입니다.


여담이지만 회사를 다니면서 거의 모든 변수를 public으로 풀어 놓은 프로젝트를 보았습니다. 설계 모양이 점점 거미줄 모양으로 바뀌고 어디에서 에러가 나는지 찾기 힘들어 집니다. 같이 일하는 프로그래머가 힘들어지죠.


APickup() 생성자에서 매시 컴포넌트를 메모리에 생성해 주고 RootComponent로 지정합니다.

RootComponent로 지정하는 이유는 이 클래스의 위치를 확인하기 위해서 입니다.


4 - Adding Variables and Functions

프로그래밍 협업을 할 때, 다른 프로그래머가 bIsActive 변수에 접근해서 다른 용도로 사용하는 것을 방지하기 위해

protected로 선언해 줍니다. Pickup이라는 클래스를 상속해서 사용할 때는 사용 가능합니다.


UFUNCTION을 설정할 때 Blueprint에 관한 설명이 나옵니다. 이 부분은 블루 프린트를 하고 나서 이해하도록 하겠습니다.

일단 이 주울 수 있는 아이템이 켜져 있는가 꺼져 있는가를 알아보기 위한 코드 작성을 합니다.

bool bIsActive 변수 선언과 bool IsActive() 함수 선언과 void SetActive(bool bOn) 이런 함수를 선언하고 cpp에 코드르 작성합니다.

코드를 다 작성했으면 언리얼 에디터로 돌아와서 컴파일을 한 번 합니다.


그리고 이 오브젝트에 블루 프린트를 만듭니다.

블루 프린트에서 Static Mesh를 설정해 줄 수 있습니다. 풀 블루프린트 에디터 열기를 누르면 에디터가 열립니다.

마우스 우클릭을 하면 코드에서 UFUNTION으로 설정해준 블루 프린트 함수를 추가할 수 있습니다.

Pickup 카테고리를 찾아서 추가해 주어도 되고 검색란에 함수명을 검색해서 추가해도 됩니다.


BlueprintPure 형과 BlueprintCallable 형이 있습니다.

실행 순서 핀을 받고 받지 않고가 다르다고 합니다. 현재 이 설명만 듣고는 알 수가 없습니다. 차츰 알아가도록 하겠습니다.

여기에서는 이런게 있구나~ 정도만 알고 블루 프린트에 대해서 자세히 다루는 부분에서 자세히 다룰 것입니다.

현재는 전체적으로 언리얼 엔진에 적응하는 시간을 갖도록 하겠습니다.

일단 위 그림과 같이 연결을 하면 BeginPlay가 호출 되었을 때 SetActive 함수가 호출이 되고 On 매개변수에 true가 작동되도록 설정할 수 있다는 것입니다. 오른쪽 두 블루프린트 함수를 지워도 됩니다.

위 그림에서 화살표가 굉장히 중요하다고 생각되는 이유는 함수 흐름을 UI로 볼 수 있다는 것입니다.

정말 좋은 기능인 것 같습니다. ( 현재 시간 오전 8시 46분, 잠깐 10분정도 쉬도록 하겠습니다. )


뉴스도 보고 창문 밖도 조금 보고 왔습니다. 나라는 어지럽고 창 밖은 햇 빛 때문에 눈이 부십니다. ( 현재 시간 9시 14분 )


5 - Extending the Pickup Class

6 - Creating a Spawning Volume

7 - Defining What to Spawn

8 - Setting Timers for Spawning

위에서 작성한 Pickup 클래스는 주울 수 있는 모든 아이템 클래스에서 상속 받을 수 있습니다.

이제 베터리 클래스를 만들 차례입니다. 다음은 배터리 클래스에 필요한 기능 입니다.

  1. 하늘에서 떨어지는 물리 성질
  2. 충전량
  3. 먹은 다음에 없어지기
BatteryPickup 클래스를 만들 것인데 새 클래스 만들기 버튼을 클릭 후 모든 클래스 표시 체크 한 후 Pickup 클래스를 검색해서 베이스 클래스로 두면 됩니다.
또는 Pickup 객체를 선택하고 이 객체를 부모로 하는 자식 클래스 만들기를 해도 됩니다.

새 클래스를 만들었으면 코드를 작성해 주면 되는데 동영상에서는 생성자에서 설정해 주도록 하고 있습니다.
그렇게 해봤는데 현재 엔진에서는 코드가 적용되지 않았습니다. 생성자에서 하지 않고 BeginPlay() override 함수를 재정의 해서 사용하였습니다. 코드 내용은 간단합니다. 

PickupMesh->SetSimulatePhysics() 함수를 호출해서 이 배터리 객체에 물리효과를 주는 것입니다.
그리고 언리얼 에디터로 돌아와서 새로 생성된 베터리 객체에 블루 프린트를 만들어 주고 베터리 메쉬를 선택해 줍니다.
맵 상에 베터리를 선택한 후 디테일에서 Physics의 Simulate Physics를 체크해 주어도 물리가 적용이 됩니다.

그 다음 충돌박스를 만들어 주어야 베터리가 바닥과 충돌처리가 됩니다.
Static Mesh의 배터리 모양을 더블 클릭 한 후 에디터 모드로 들어가서 콜리전 > 18면체 단순화 콜리전 추가를 하면 됩니다.


발로 차고 다닐 수 있는 베터리 오브젝트가 생성 되었습니다.

여기까지 사실 코드 작성은 별로 해준게 없습니다. 코드 작성을 할 수 있는 기반을 만들어 주었습니다.


도중에 만들어 놓은 클래스를 지우는 방법에 대해 알아 보았습니다. ( 조금 번거로운거 같습니다. )

Visual Studio에서 해당 .h .cpp 파일을 지우고 물리적인 파일도 지우고 Visual Studio와 언리얼 에디터를 끈 다음

Visual Studio 프로젝트 폴더에 가서 Binaries/Win64 폴더 안에 있는 파일을 모두 지운 후 BatteryCollector.uproject 더블 클릭 하여 언리얼 에디터를 새로 열면 됩니다.


여기까지 언리얼 에디터와 유니티 에디터를 비교해 보자면 언리얼 에디터는 C++ 컴파일이 조금 느린게 단점인 것 같습니다. 엔진은 언리얼이 훨씬 고급스럽게 느껴지고 손쉽게 캐릭터가 뛰어다니면서 베터리를 발로 차는 프로젝트를 만들 수 있었습니다. 이 부분은 유니티 엔진 보다 훨씬 쉽다는 느낌입니다.


( 현재 오전 10시 46분인데 10분간 쉬고 오도록 하겠습니다... 11시 4분 다시 시작 )


이번에는 출현(Spawn) 관련한 클래스를 생성합니다. SpawnVolume이라는 Actor를 만들면 됩니다.


SpawnVolume 코드로 가서 기본적으로 사용할 Box 오브젝트를 만들어 줍니다. .h에서 선언 하고 생성자 cpp에서

CreateDefaultSubobject<UBoxComponent>(TEXT("WhereToSpawn")); 코드를 호출해 주면 됩니다.

이 컴포넌트가 루트라는 것을 명시해 줍니다. RootComponent = pWhereToSpawn; 나중에 컴포넌트가 추가될 때 이 컴포넌트가 루트가 됩니다.


FVector GetRandomPointInVolume() 함수를 만들어 주는데

pWhereToSpawn UBoxComponent의 Bounds.Origin과 Bounds.BoxExtent 값에 접근을 할 수 있습니다.


UKismetMathLibrary에 바운딩 박스에서 무작위 위치를 반환해 주는 함수가 이미 있습니다.



6, 7, 8 동영상에서는 주로 코드 작성를 작성하고 설명합니다.


동영상에서 설명한 코드를 작성한 것입니다. 주로 UPROPERTY 매크로를 사용해서 에디터에서 접근할 수 있도록 해줍니다.

TSubclassOf<class APickup> 은 C++ 탬플릿 입니다. APickup 클래스에서 상속받은 클래스들을 사용하기 위함 입니다.

그리고 시간을 사용하기 위해서 FTimerHandle을 사용합니다. ( C#에서도 Time 관련 클래스가 있듯이 Timer 관련 핸들러 입니다. )

나머지는 그냥 설정용 변수 입니다.



주요 함수 작성을 보면

UWorld는 언리얼 엔진에서 World 객체 입니다.

FActorSpwanParameters는 출현(Spawn)에 필요한 파라메터 입니다.

FRotator는 회전을 시켜주기 위한 변수 입니다.  FMath::에서 무작위 값을 가져와서 360도를 곱해주고 있습니다.

WHERE

중요한 함수는 pWorld->SpawnActor<APickup>( ... ) 이 부분 입니다. 월드에 pWhatToSpawn 객체를 추가해 주겠다는 함수 입니다.

함수 매개변수로 필요한 값들을 넣어 줍니다.


WHEN

SpawnRandomTimer()는 타이머를 설정해 주는 함수인데 시간 딜레이 값을 무작위로 하나 만들어서

GetWorldTimerManager().SetTimer( ... ) 함수를 호출해서 (타이머 핸들은, 이것, 호출할 함수, 딜레이 값, 루프여부)를 지정해서 호출해 줍니다.

그럼 SpawnPickup 함수가 무작위 시간마다 호출이 되어 계속적으로 아이템이 생성 되는 것을 볼 수 있습니다.



언리얼 에디터에서 SpawnVolume을 에디터에 놓고 W 단축키를 눌러서 이동 시키고 R 단축키를 눌러서 크기를 조절하면 그 영역에서 오브젝트가 생성됩니다. 그다음 우측 Spawning에서 필요한 변수들을 설정해 주면 됩니다. 이런 설정 부분은 유니티에서도 유사하므로 어렵지 않게 익힐 수 있었습니다.



위 그림과 같이 해당 박스 범위 안에서 오브젝트가 회전하여 출현(Spawn)하는것을 확인할 수 있습니다.

베터리들은 자기들끼리 부딪혀서 굴러다닙니다.

이 코드를 활용한다면 특정 위치에 캐릭터가 도달 하였을 때, 몬스터를 출현시킬 수 있습니다. (5~10명)

또는 맵 상에 무언가 출현 시키고자 하는 객체가 있다면 활용 할 수 있을 것입니다. ( 현재 시간 오후 12시 43분 점심 시간 입니다... 시작 시간 3시 입니다. )


9 - Extending the Character Class

10 - Collecting Pickups

이 두 장에서는 캐릭터가 아이템을 줍는 기능을 구현 합니다.



일단은 Pickup.h에 위 2개의 함수를 작성합니다.

UFUNCTION(BlueprintNativeEvent)를 선언하는 이유는 https://docs.unrealengine.com/latest/KOR/Programming/Introduction/index.html 에 나와 있습니다.

블루 프린트 코드에 이 함수가 보이고 함수명_Implementation()이라고 선언한 함수를 호출해 줍니다.


WasCollected_Implementation() 함수는 가상함수로 선언된 것을 알 수 있습니다.

이 클래스는 상위 클래스로 아이템을 얻었을 때, 해당 아이템마다 다른 효과를 주기 위함입니다.

아마 위 함수명이 틀리다면 LNK_2001 링크 에러 메시지를 볼 수도 있을 것입니다.



위 함수는 먹었을 때, 로그를 출력해주는 함수입니다. GetName() 함수를 통해 이름을 얻어 올 수 있고 string은 FString을 사용하는 것을 알 수 있습니다.

로그는 UE_LOG 매크로를 사용합니다.


충돌 처리를 하기 위해 BatteryCollectorCharacter.h에 기능을 추가해야 합니다. 일단 구체를 하나 만듭니다.

.h에 선언해 주고


.cpp에서 구현해 줍니다


구체를 하나 생성하고 RootComponent에 붙입니다.  구체의 반경은 200.f 입니다.


그다음 CollectPickups를 선언해 주고 구현을 해 주는데 중요한 부분 입니다.


TArray는 언리얼에서 사용하는 배열 자료구조인 것 같습니다.

CollectionSphere->GetOverlappingActors( ... ) 함수를 호출하면 그 구체에 충돌한 객체를 얻어올 수 있는데 CollectedActors에 담습니다.


배열의 크기만큼 for문을 돌면서 형변환을 합니다. APickup 말고 다른 객체가 충돌할 수도 있습니다. APickup으로 형변환이 안됀다면 TestPickup이 NULL 이 되기 때문에 if 문에 걸리지 않습니다.


그래서 TestPickup && 즉 NULL이 아니라면 (객체가 APickup 이라면)

그리고 IsPendingKill()을 당하지 않았고 TestPickup의 active가 true 라면

TestPickup의 WasCollected() 함수를 호출합니다. 위에서 로그를 출력 해 주던 함수입니다.



그다음 언리얼에서 버튼 입력에 대한 기능 추가 입니다. SetupPlayerInputComponent에 그 기능들이 구현이 되어 있습니다. Collect 이름을 주고 ABatteryCollectorCharacter::CollectPickups 함수를 호출 시키도록 합니다.

즉 C 키를 눌렀을 때, 충돌체크를 하고 해당 오브젝트들을 Destroy() 하는 함수를 호출 하는 것입니다.


C키를 추가하는 것은 언리얼 엔진 세팅 > 프로젝트 세팅에서 키보드의 C키를 추가해 주고 이름을 Collect로 설정해 주면 됩니다.


실행을 누르고 C키를 누르면 아이템이 파괴되는 것을 볼 수 있습니다.


여기까지 진행을 하면 충돌처리 부분에 대한 것을 알 수 있습니다. 

공격 범위를 메쉬로 만든 다음 몬스터가 공격범위 안에 있는가?를 체크할 수 있습니다. 주로 기본 공격 범위를 메쉬로 만들겠죠.

멀리 발사하는 마법 구체에 충돌 박스를 만든 다음 충돌 했는가?를 체크할 수 있습니다.


그 다음 어떤 키를 눌렀을 때, 어떤 함수를 호출하는 방법에 대해 알 수 있습니다. ( 모바일에서는 주로 화면상에 버튼을 두기 때문에 PC 게임에서 중요할 것 같습니다. ) ( 현재 시간이 오후 5시 57분 입니다. 조금 쉬고 오도록 하겠습니다.... 현재 시간 오후 7시 31분 저녁식사 하고 뉴스 좀 보고 쉬고 왔습니다. )


11 - Adding Power to the Game

이 장에서는 캐릭터에 베터리 파워를 추가해 주는 작업을 합니다.

BetteryPickup.h에 float BetteryPower 변수를 추가해 주고 BatteryCollectorCharacter.h에 float InitialPower, float CharacterPower를 추가해 줍니다.

이번 장에서 UPROPERTY (언리얼 프로퍼티) 선언에 대해서 조금 더 알아낸 부분이 있는데

EditAnywhere를 선언하면 어디에서든 수치 입력이 가능하고 VisibleAnywhere를 선언하면 수치를 볼 수는 있으나 수정은 불가능 합니다.

Category로 이름을 지정해 줄 수 있습니다.


여기에서 변수를 private와 protected로 나누는데 그 이유는 다른 클래스에서 이 변수의 값을 변경하지 못하게 하기 위해서 입니다. 다른 사람이 이 클래스에 접근해서 수치를 막 바꿔 버리면 참 난감할 수 있겠죠?

그대신 값을 가져다 쓸 수는 있게 만든 것이 public으로 float GetInitialPower() 라는 함수를 만든 것입니다.

UpdatePower( float PowerChange ) 함수를 만들고 CharacterPower에 매개변수 수치를 더해 줍니다.


이미 구현해둔 함수를 수정해야 합니다. BatteryCollectorCharacter의 CollectPickups()로 와서

float CollectedPower = 0.f;를 선언하고

for문을 도는데서 C++의 다형성을 사용합니다. 즉, ABatteryPickup 변수를 하나 두고 캐스팅을 하여 NULL이 아니면 CollectedPower 변수에 해당 아이템의 BetteryPower를 더해 주는 것이죠.


for문을 다 돌고 나와서 CollectedPower 변수가 0값 이상이면 아이템을 먹었다는 것이 됩니다.


if (ColletedPower > 0) UpdatePower (CollectedPower); 해주면 값이 더해지게 됩니다.


캐릭터를 검색해서 디테일을 봅니다. Power의 CharacterPower 부분이 증가 하는 것을 확인할 수 있습니다.


앞으로 블루 프린트 사용법과 파티클 이펙트 사용 그리고 UMG를 이용한 UI를 구현할 것 같습니다.

현재 시간 오후 8시 47분 오늘 공부는 여기에서 마치도록 하겠습니다.

( 휴식 시간을 제외하고 총 9시간 10분 공부 하였습니다. )







반응형

'기술 > 언리얼 스터디' 카테고리의 다른 글

언리얼 엔진 시작하기 6  (0) 2017.01.02
언리얼 엔진 시작하기 5  (0) 2016.12.30
언리얼 엔진 시작하기 4  (0) 2016.12.12
언리얼 엔진 시작하기 3  (0) 2016.12.03
언리얼 엔진 시작하기 2  (0) 2016.11.30