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

 

베르의 게임 개발 유튜브

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

www.youtube.com

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

이번에는 언리얼 C++에서 만든 함수를 블루프린트에서 호출할 수 있게 만드는 방법을 알아봅시다!

 

사용 엔진 버전 : 5.0.2

 

타임라인

0:00 인트로

0:10 프로젝트 및 클래스 생성

0:47 함수 복습

1:35 함수를 블루프린트에서 호출할 수 있게 만들기

2:53 C++ 클래스 기반으로 블루프린트 클래스 만들기

4:09 함수를 블루프린트에서 호출하기

5:15 아웃트로

스크립트

인트로

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

이번에는 C++ 클래스에서 만든 함수를 블루프린트에서 호출하는 방법을 알아보도록 하겠습니다.

프로젝트 생성 및 클래스 생성

그럼 이제 언리얼 엔진에서 함수를 만들어보기 위해서 언리얼 엔진을 실행하고 새 프로젝트를 생성합니다.

카테고리는 게임으로 하고, 다른 코드 없이 완전히 비어있는 기본 템플릿을 선택하겠습니다.

그리고 프로젝트 타입을 C++로 변경한 다음 프로젝트를 생성합니다.

프로젝트가 생성되면 Actor 클래스를 상속받아서 C++ 클래스를 하나 생성합니다.

클래스의 이름은 언리얼 엔진이 기본으로 추천해주는 MyActor를 그대로 쓰도록 하겠습니다.

함수 복습

클래스가 생성되고 나면 먼저 MyActor의 헤더 파일로 이동해서 저번 영상의 복습으로 간단한 함수를 만들어 보겠습니다.

일단 bool 타입으로 키가 눌렸음을 알리는 bPressedKey 변수를 선언합니다.

그리고 InputSpaceKey 함수와 MoveUp 함수를 선언합니다.

그 다음에는 소스 파일로 이동해서 함수를 구현해줍니다.

먼저 InputSpaceKey 함수에서는 매개변수를 가져와서 bPressedKey를 설정해줍니다.

그리고 MoveUp 함수에서는 키가 눌려서 bPressedKey 변수가 true가 되었을 때 액터의 위치 값을 가져와서 상승시키도록 계산한 뒤에 다시 액터의 위치로 설정하도록 코드를 작성합니다.

함수를 블루프린트에서 호출할 수 있게 만들기

함수를 완성한 다음에는 이 함수들을 블루프린트에서 호출해서 사용할 계획입니다.

다만 이렇게 그냥 만들어진 함수들은 기본적으로 블루프린트에서 호출하려고 해도 컨텍스트 메뉴에는 보이지 않습니다.

그럼 이제 C++ 클래스에서 직접 만든 함수들을 블루프린트 클래스에서 호출할 수 있도록 만들어 보겠습니다.

언리얼 C++ 변수 영상에서 프로퍼티를 언리얼 에디터에서 볼 수 있게 하기 위해서 UPROPERTY 매크로를 사용한 것을 기억할 겁니다.

그와 대응되는 기능으로 함수에서는 UFUNCTION 매크로를 사용하면 함수를 블루프린트에서 사용할 수 있게 설정하는 등의 기능을 지정자로 넣을 수 있습니다.

함수들에 UFUNCTION 매크로를 추가해주고 지정자는 BlueprintCallable과 Category="Move"로 입력하겠습니다.

UFUNCTION 매크로에서 BlueprintCallable 지정자를 넣어주면 해당 함수를 블루프린트에서 사용할 수 있게 됩니다.

그리고 블루프린트에서 사용할 모든 함수에는 카테고리를 할당해줘야만 정상적으로 호출 할 수 있게 됩니다.

그렇기 때문에 함수를 블루프린트에서 사용하려면 UFUNCTION 매크로에 반드시 카테고리 지정자를 넣어줘야 합니다.

UFUNCTION 매크로를 모두 작성하면 저장하고 언리얼 에디터로 돌아가서 명령 콘솔 창에 LiveCoding.Complie을 입력해서 수정 사항을 컴파일 해줍니다.

C++ 클래스 기반으로 블루프린트 클래스 만들기

그 다음에는 만든 함수가 블루프린트에 잘 호출되는지 확인하기 위해서 MyActor 클래스를 기반으로 블루프린트 클래스를 만들어야 합니다.

콘텐츠 브라우저에서 MyActor C++ 클래스 에셋을 우클릭하고 [MyActor 기반 블루프린트 클래스 생성]을 선택하면 블루프린트 클래스 추가 창이 뜹니다.

블루프린트 클래스의 이름을 BP_MyActor로 입력한 다음 블루프린트 클래스 생성 버튼을 누릅니다.

블루프린트 클래스가 생성되고 나면 생성된 블루프린트 클래스를 대상으로 블루프린트 에디터가 열립니다.

그 다음에는 이 액터가 키 입력을 받아서 움직이는 것을 잘 보이게 만들기 위해서 컴포넌트 패널의 [추가] 버튼을 누르고 Sphere 컴포넌트를 찾아서 추가해줍니다.

그리고 추가된 Sphere 컴포넌트를 DefaultSceneRoot에 드래그&드롭해서 루트 컴포넌트로 만들어 줍니다.

그리고 Sphere 컴포넌트를 선택하고 디테일 패널의 피직스 카테고리에서 피직스 시뮬레이트에 체크해줍니다.

블루프린트 세팅의 마지막 과정으로는 이 액터가 입력을 받을 수 있도록 설정할 차례입니다.

블루프린트 에디터에서 이벤트 그래프 패널로 이동합니다.

그 다음에는 Begin play 이벤트 근처의 그래프 빈 영역에 우클릭하고 컨텍스트 메뉴에서 Get Player Controller와 Enable Input 함수를 찾아서 노드를 배치해줍니다.

그리고 Begin Play 이벤트 노드와 연결해줍니다.

이렇게 하면 이 액터가 입력을 받을 수 있도록 설정됩니다.

함수를 블루프린트에서 호출하기

기본 세팅을 끝냈으니 이제 앞에서 만든 함수들을 블루프린트에서 호출해볼 차례입니다.

그래프의 빈 영역에 우클릭하고 컨텍스트 메뉴에서 스페이스 바 이벤트를 찾아서 추가해줍니다.

그 다음에 다시 빈 영역에 우클릭하고 컨텍스트 메뉴를 살펴보면 Move 카테고리에 우리가 만든 함수를 찾을 수 있습니다.

먼저 InputSpaceKey 함수 노드를 두 개 추가하고 각각 Pressed 핀과 Released 핀에 연결해줍니다.

그리고 Pressed 핀에 연결된 노드에는 Pressed가 true가 되게 체크해줍니다.

그 다음에는 MoveUp 함수 노드를 추가하고 틱 이벤트와 연결해줍니다.

블루프린트 작성이 모두 끝난 다음에는 블루프린트 에디터의 컴파일 버튼과 저장 버튼을 차례대로 누른 뒤 블루프린트 에디터를 닫아줍니다.

그 다음에는 테스트를 위해서 BP_MyActor를 레벨에 배치하고 게임을 플레이시켜줍니다.

이제 스페이스 바를 누르면 액터가 떠오르고 손을 떼면 다시 중력의 영향을 받아 떨어지는 모습을 볼 수 있습니다.

이런 방식으로 프로그래머가 블루프린트에서 호출할 수 있게 핵심 기능을 가진 함수를 만들면 디자이너는 블루프린트에서 함수를 조합해서 게임 기능을 만들면 됩니다.

아웃트로

이번 영상에서는 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

 

반응형

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

 

베르의 게임 개발 유튜브

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

www.youtube.com

 

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

이번에는 전에 C++로 구현해보았던 마우스 클릭 이동 기능을 블루프린트로 만들어봅시다!

 

사용 엔진 버전 : 4.27

 

타임라인

0:00 인트로

0:11 프로젝트 생성과 리소스 임포트

0:47 맵 세팅

2:27 Player Controller로 마우스 입력 받기

4:50 클릭한 위치로 이동시키기

6:35 캐릭터 구현

9:41 게임 모드 설정과 테스트

10:43 아웃트로

스크립트

인트로

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

지난 강좌에서 C++로 구현했던 마우스 클릭을 이용한 캐릭터 이동 기능을 이번에는 블루프린트로 만들어 보겠습니다.

프로젝트 생성과 리소스 임포트

먼저 언리얼 엔진을 실행하고 새 프로젝트를 생성합니다.

이번에는 블루프린트로 기능을 만들 생각이므로 블루프린트 타입으로 프로젝트를 만듭니다.

프로젝트 생성이 완료된 이후에는 영상 하단의 링크에서 캐릭터 모델링과 애니메이션을 다운로드 받아서 압축을 풀어줍니다.

그리고 언리얼 에디터에서 콘텐츠 브라우저의 소스 패널을 열고 콘텐츠 폴더에 우클릭한 다음 [탐색기에서 표시] 항목을 선택해서 프로젝트의 폴더를 열어줍니다.

그 다음에는 방금 다운로드 받아서 압축 해제된 파일들을 이 폴더 안으로 옮겨줍니다.

맵 세팅

프로젝트 세팅이 끝난 다음에는 캐릭터가 움직일 맵을 구성해봅시다.

맵 세팅 작업에 들어가기 전에 맵에 배치할 기둥이나 벽을 대신할 간단한 박스 메시 하나를 영상 하단의 링크에서 다운로드 받은 뒤 압축을 해제합니다.

그리고 언리얼 에디터로 돌아가서 Props 폴더를 만든 뒤 빈 콘텐츠 브라우저 패널에 우클릭하고 [/Game/Props에 임포트]를 선택해서 다운로드 받은 Box.FBX 파일을 프로젝트에 임포트합니다.

그 다음 레벨을 살펴보면 빈 평면만 보입니다.

여기에 방금 임포트한 박스를 이용해서 캐릭터의 이동을 방해할 수 있게 적절하게 배치해줍니다.

이렇게 저처럼 배치해도 되고 여러분들이 원하는 대로 편하게 배치해도 됩니다.

장애물을 모두 배치한 다음에는 액터 배치 패널의 볼륨 탭에서 내비 메시 바운드 볼륨을 찾아서 레벨의 한가운데에 배치하고 맵의 오브젝트들을 전부 덮도록 만들어줍니다.

이 내비 메시 바운드 볼륨은 볼륨의 영역 안에 있는 오브젝트들을 찾아서 계산한 뒤에 이동 경로를 찾아줄 내비 메시를 만들어내는 역할을 합니다.

내비 메시 바운드 볼륨 설치를 마친 다음 단축키 [P]를 누르면 캐릭터가 이동할 수 있는 범위가 초록색으로 표시됩니다.

이렇게 맵 세팅을 마치면 콘텐츠 브라우저에서 Maps 폴더를 만들고 상단 메뉴 바에서 [파일 > 현재 레벨 저장] 항목을 선택해서 현재 레벨을 Maps 폴더 안에 ClickMoveTestMap이라는 이름으로 저장합니다.

맵을 저장한 뒤에는 프로젝트 세팅 창을 열고 [맵 & 모드]에서 에디터 시작 맵을 방금 저장한 ClickMoveTestMap으로 설정해줍니다.

이렇게 해주면 다음에 프로젝트를 열었을 때, 설정해준 맵이 제일 먼저 열리게 됩니다.

Player Controller로 마우스 입력 받기

이 다음 작업으로는 플레이어 컨트롤러로 클릭 입력을 받아서 컨트롤러가 소유한 폰을 클릭한 위치로 이동시키는 블루프린트를 작성하겠습니다.

코드를 작성하기 전에 프로젝트 세팅 창에서 입력을 선택하고 입력 매핑을 세팅해줍니다.

이번 영상에서는 클릭한 지점으로 캐릭터를 이동시키는 기능만 목표로 하여 만들 생각입니다.

그러므로 LeftClick이라는 이름으로 왼쪽 마우스 버튼만 액션 매핑에 추가해줍니다.

입력 세팅을 마쳤다면, 콘텐츠 브라우저 패널에 Blueprints라는 이름으로 폴더를 만든 뒤, 우클릭하고 블루프린트 클래스 항목을 선택합니다.

그러면 부모 클래스 선택 창이 뜨는데 여기서 PlayController 클래스를 부모 클래스로 선택해줍니다.

블루프린트 클래스의 이름은 BP_ClickMovePlayerController로 해주겠습니다.

클래스가 생성되고 나면, 블루프린트 에셋을 더블 클릭해서 블루프린트 에디터를 열어줍니다.

블루프린트 에디터가 열리고 나면 컴포넌트 패널에서 최상위 오브젝트를 선택하고 디테일 패널에서 [Show Mouse Cursor] 프로퍼티를 찾아야 합니다.

이 강좌에서 우리는 마우스로 캐릭터를 이동시킬 계획이기 때문에 마우스 커서를 보이게 만들기 위해 이 프로퍼티를 true로 변경합니다.

이 다음에는 내 블루프린트 탭의 변수 카테고리에 있는 [+] 버튼을 눌러서 변수를 추가하고 bool타입으로 설정한 다음 이름은 bClickLeftMouse로 정합니다.

이 변수는 왼쪽 마우스 버튼을 누를 때 true가 되고 뗄 떼 false로 값이 바뀌도록 동작하게 만들 예정입니다.

그 다음에는 함수 카테고리의 [+] 버튼을 눌러 InputLeftMouseButtonPressed 함수를 추가하고 이 함수에서는 bClickLeftMouse 변수의 값을 true로 변경하도록 블루프린트를 작성합니다.

그리고 이번에는 InputLeftMouseButtonReleased 함수를 추가하고 bClickLeftMouse의 값을 false로 변경하도록 작성해줍니다.

이 함수들은 입력 매핑과 바인딩 되어서 입력을 받으면 bClickLeftMouse 변수의 값을 바꿔주게 될 겁니다.

그 다음은 이벤트그래프로 이동해서 빈 그래프 영역에 우클릭하고 앞에서 만든 LeftClick 입력에 대한 이벤트를 검색해서 추가해줍니다.

그리고 Pressed 진행 핀에 앞에서 만든 InputLeftMouseButtonPressed 함수를 연결해주고 Released 진행 핀에는 InputLeftMouseButtonReleased 함수를 연결해줍니다.

이제 왼쪽 마우스 버튼을 누를 때 InputLeftMouseButtonPressed 함수가 호출되서 bClickLeftMouse 변수가 true가 되고, 왼쪽 마우스 버튼을 떼면 InputLeftMouseButtonReleased 함수가 호출되서 bClickLeftMouse 변수가 false가 되도록 세팅되었습니다.

클릭한 위치로 이동시키기

이 다음 작업으로는 마우스를 클릭하면 클릭한 위치로 캐릭터를 이동시키는 블루프린트를 작성할 차례입니다.

먼저 함수 카테고리의 [+] 버튼을 눌러서 SetNewDestination 함수를 추가합니다.

이 함수는 새로운 목표 위치를 받아서 컨트롤러가 소유한 폰을 그 위치로 이동시키는 역할을 하도록 만들겠습니다.

이 함수는 매개변수로 목적지로 지정할 위치를 받아야하기 때문에 디테일 패널의 입력 카테고리의 [+] 버튼을 눌러서 입력 매개변수를 만들고 Vector 타입으로 지정한 다음 이름을 NewDestination으로 변경해줍니다.

이 함수에서는 먼저 GetPawn 함수를 이용해서 컨트롤러가 소유하고 있는 폰을 가져오고 폰과 목적지 사이의 거리를 측정해서 그 거리가 120 언리얼 유닛보다 큰 지 검사합니다.

그리고 이 결과값이 true라면 SimpleMoveToLocation 함수를 사용해서 새로운 목적지로 이동하도록 만들어줍니다.

이 SimpleMoveToLocation 함수는 프로그래머가 목적지로 폰을 이동시키기 위한 코드를 일일이 작성하는 대신에 간단한 함수 호출로 그 모든 일을 할 수 있도록 도와줍니다.

그 다음에는 함수 카테고리의 [+] 버튼을 눌러서 MoveToMouseCursor 함수를 추가합니다.

이 함수에서는 PlayerController를 이용해서 찾은 Get Hit Result Under Cursor by Channel 함수로 마우스 커서 아래에 레이 트레이스를 쏴서 그 위치를 SetNewDestination 함수에 전달하는 역할을 합니다.

이제 MoveToMouseCursor 함수를 실시간으로 호출하기 위해서 이벤트그래프로 돌아가서 Tick 이벤트에 bClickLeftMouse 변수의 상태에 따라서 MoveToMouseCursor 함수를 호출하도록 블루프린트를 작성합니다.

컨트롤러 블루프린트 작성이 끝나면 컴파일 버튼을 누르고 블루프린트를 저장해줍니다.

캐릭터 구현

캐릭터가 움직일 맵과 캐릭터를 컨트롤할 플레이어 컨트롤러를 모두 만들었으니 이제 맵 위에서 움직일 캐릭터를 만들 차례입니다.

콘텐츠 브라우저 패널에 우클릭하고 블루프린트 클래스 항목을 선택합니다.

그리고 부모 클래스로는 Character 클래스를 선택하고 클래스의 이름은 BP_ClickMoveCharacter로 정해줍니다.

클래스 생성이 완료되고 나면 블루프린트 에셋을 더블 클릭해서 블루프린트 에디터를 엽니다.

블루프린트 에디터가 열리고 나면 먼저 컴포넌트 패널에서 Capsule Component를 선택하고 캡슐의 높이와 지름을 수정해줍니다. (96 42)

그리고 제일 상위 오브젝트를 선택하고 폰 카테고리에서 Use Controller Rotation Yaw를 false로 설정해서 캐릭터가 카메라의 회전을 따라서 회전하지 않도록 해줍니다.

그 다음에는 Character Movement 컴포넌트를 선택하고 캐릭터 무브먼트(회전 세팅) 카테고리에서 Orient Rotation to Movement를 체크하고 RotationRate를 (0, 0, 640)으로 변경해서 캐릭터를 이동시키기 전에 이동 방향과 현재 캐릭터가 바라보는 방향이 다르면 캐릭터를 이동 방향으로 초당 640도의 회전 속도로 회전시키도록 만들어줍니다.

플레이너 무브먼트 카테고리에서 Constrain To Plane을 true로 변경해서 캐릭터의 이동을 평면으로 제한하고, Snap To Plane At Start를 true로 변경해서 시작할 때 캐릭터의 위치가 평면을 벗어난 상태라면 가까운 평면으로 붙여서 시작되도록 만들어줍니다.

그 다음으로는 컴포넌트 패널에서 Capsule Component를 선택하고 컴포넌트 추가 버튼을 누른 뒤 스프링 암을 검색해서 Capsule Component 아래에 추가해줍니다.

그리고 카메라 세팅 카테고리에서 Inherit Pitch, Inherit Yaw, Inherit Roll을 체크 해제해줍니다.

이 프로퍼티를 해제함으로써 스프링 암의 회전이 루트 컴포넌트와 상위 컴포넌트를 따르지 않고 월드 좌표계의 회전을 따르도록 해줍니다.

그리고 TargetArmLength를 800으로 설정해서 카메라와 캐릭터의 거리를 띄워주고 트랜스폼의 회전 값을 수정해서 스프링 암을 회전시켜 위에서 캐릭터를 내려다보도록 설정합니다.

그리고 Do Collision Test 프로퍼티는 카메라가 벽에 닿으면 충돌 계산을 통해 카메라와 캐릭터의 거리를 좁혀 카메라가 벽을 뚫지 않게 만들어주는 프로퍼티지만, RPG 게임처럼 위에서 내려다보는 시점에는 사용하지 않기 때문에 false로 설정합니다.

그 다음에는 스프링 암 컴포넌트 아래에 카메라 컴포넌트를 생성하고 Use Pawn Control Rotation을 false로 변경해서 카메라의 회전이 폰의 회전에 영향을 주지 않도록 설정합니다.

이제 캐릭터의 모델을 추가할 차례입니다.

컴포넌트 패널에서 Mesh 컴포넌트를 선택합니다.

그리고 디테일 패널의 메시 섹션에서 Skeletal Mesh를 SK_Mannequin으로 설정하고 애니메이션 섹션에서 Anim Class를 ThirdPerson_AnimBP로 설정해줍니다.

이 작업을 끝내고 나서 블루프린트 에디터의 뷰포트 패널을 보면 캐릭터의 메시가 캡슐 콜라이더를 벗어나고 캐릭터의 방향 역시 캡슐 콜라이더에 표시된 화살표와 다른 방향을 바라보고 있는 것을 알 수 있습니다.

위치와 방향을 맞춰주기 위해서 메시 컴포넌트의 위치를 {0, 0, -90}으로 수정하고, 회전을 {0, 0, -90}으로 수정해줍니다.

작업이 끝나면 블루프린트를 컴파일하고 저장한 뒤 블루프린트 에디터를 닫아줍니다.

게임 모드 설정

플레이어 컨트롤러와 캐릭터의 설정이 모두 끝났으니 이제 게임이 우리가 만든 플레이어 컨트롤러와 캐릭터를 사용하도록 만들 차례입니다.

콘텐츠 브라우저 패널에서 우클릭한 뒤 블루프린트 클래스 선택하고 부모 클래스로 GameModeBase를 선택해서 블루프린트 클래스를 생성해줍니다.

게임 모드 블루프린트가 생성되면 더블클릭해서 블루프린트 에디터를 열고 디테일 패널에서 Player Controller Class와 Default Pawn Class를 우리가 만든 것으로 교체해줍니다.

그리고 블루프린트를 컴파일하고 저장한 뒤에 블루프린트 에디터를 닫아줍니다.

그 다음에는 툴바에서 [세팅 > 월드 세팅] 항목을 선택해서 월드 세팅 패널을 엽니다.

월드 세팅에서는 게임 모드 오버라이드를 방금 만든 게임 모드 블루프린트로 변경합니다.

모든 과정을 마친 뒤 플레이 버튼을 눌러서 PIE 모드로 들어가면 캐릭터가 마우스 클릭 지점으로 이동하고, 그 과정에서 적절하게 장애물을 회피하는 모습을 볼 수 있습니다.

아웃트로

이번에는 지난 강좌 중 하나에서 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

 

반응형

언리얼 엔진 4 프로그래밍에 입문해봅시다! 그리고 언리얼 C++ 클래스를 생성하는 방법과 클래스를 처음 생성했을 때 볼 수 있는 생성자와 BeginPlay, Tick과 같은 이벤트 함수에 대해서 알아봅시다.

 

타임라인

0:00 개요

0:09 언리얼 엔진 4 강좌를 만드는 이유

1:59 C++ 클래스와 블루프린트 클래스

3:03 새 프로젝트 만들기

4:02 C++ 클래스 생성하기

4:46 새로 생성한 C++ 클래스의 기본 형태

6:59 언리얼 C++ 클래스의 생성자, BeginPlay, Tick의 실행 테스트

8:40 마무리

 

[투네이션]

 

-

 

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

 

반응형

C++ 코드 생성자에서 콘텐츠 브라우저의 클래스와 리소스 불러오기

 

작성 기준 버전 :: 4.21.1

 

게임을 제작하는 과정에서 객체를 초기화할 때, 프로젝트에 포함된 다른 클래스나 오브젝트, 리소스를 가져와야하는 경우가 종종 생긴다.

 

 

그런 경우 블루프린트의 이벤트 그래프에서 작업하는 경우라면 위의 이미지와 같이 콘텐츠 브라우저에 있는 리소스나 블루프린트 클래스 등을 곧바로 선택할 수 있지만, C++ 코드에서는 직접 경로를 지정해서 코드를 작성해야 한다.

 

단, C++ 코드에서 직접 경로를 지정해서 리소스나 블루프린트 클래스를 가져올 때, 주의할 점은 리소스나 블루프린트 클래스의 경로나 파일명이 자주 바뀌는 상황을 피하는 게 좋다. 경로를 지정한 이후에 경로가 바뀌지 않을 것이 확실하다면 C++ 코드로 경로를 지정해서 가져오는게 낫겠지만 자주 바뀌는 상황이라면 바뀐 리소스를 불러오는 모든 코드를 일일이 찾아서 수정하고 컴파일하는 문제가 발생한다.

 

그렇기 때문에, 경로나 리소스의 파일명이 자주 바뀔 상황이라면 위의 이미지처럼 블루프린트를 이용해서 초기화를 진행하거나, 별도의 기능을 만들어서 일일이 경로를 지정하고 바꾸는 작업을 자동화시키는 것이 좋다.

 

우선 C++ 코드에서 콘텐츠 브라우저의 리소스나 블루프린트 클래스를 가져오기 위해서는 다음의 헤더를 전처리기로 포함시켜주어야 한다.

 

#include "UObject/ConstructorHelpers.h"

 

ConstructorHelpers는 생성자에 도움을 주는 클래스로 생성자에서 콘텐츠 브라우저의 리소스나 블루프린트 클래스를 불러오는 작업을 도와주는 기능들을 가지고 있다. ConstructorHelpers는 생성자에서 사용되는 기능이기 때문에 생성자 이외의 장소에서 ConstructorHelpers를 사용하려고 시도하면 컴파일 에러가 발생하게 된다.

 

 

C++ 코드에서 블루프린트 클래스 가져오기

 

콘텐츠 브라우저 패널에 Blueprints 폴더 안에 TestBlueprintClass라는 이름의 APawn 클래스를 상속받은 블루프린트 클래스가 있다고 가정할 때, 그것을 C++ 코드에 가져오기 위해서는 다음 예시와 같이 코드를 작성하면 된다.

 

static ConstructorHelpers::FClassFinder<APawn> BPClass(TEXT("/Game/Blueprints/TestBlueprintClass"));
if (BPClass.Succeeded() && BPClass.Class != NULL)
{
    // 가져온 BPClass.Class를 통한 작업
}

 

FString 경로를 통해서 불러오는 것이니 만큼, 오타나 변경된 경로나 파일명으로 인해서, 클래스가 제대로 불러와지지 않는 경우가 발생할 수 있기 때문에, Succeeded() 함수와 Class의 NULL 체크를 통해서 성공적으로 클래스가 불러와졌는지 체크하고 사용해야 한다.

 

클래스 탐색자(Class Finder)는 성공적으로 블루프린트 클래스를 가져온 경우, Class 멤버 변수 안에 TSubclassOf<T> 타입으로 해당 클래스를 가지고 있게 된다. 이것을 이용해서 필요한 작업을 진행하면 된다.

 

 

C++ 코드에서 리소스 가져오기

 

이번에 알아볼 것은 C++ 코드에서 콘텐츠 브라우저 패널의 리소스를 가져오는 과정이다. 리소스의 종류는 여러가지가 될 수 있는데 대표적인 것으로는 스태틱 메시나 텍스처를 예로 들 수 있다.

 

아래의 예시코드는 드롭된 아이템의 메시가 아이템의 종류에 따라서 달라진다는 가정하에 만들어졌다. 콘텐츠 브라우저의 Item/StaticMesh 폴더 안에 SM_Helmet 이라는 이름을 가진 헬멧 모양의 스태틱 메시가 있을 때, FObjectFinder를 통해서 가져올 수 있다.

 

DropItemStaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DropItemMesh"));
RootComponent = DropItemStaticMeshComponent;

 

static ConstructorHelpers::FObjectFinder<UStaticMesh> HelmetStaticMesh(TEXT("/Game/Item/StaticMesh/SM_Helmet"));
if (HelmetStaticMesh.Succeeded() && HelmetStaticMesh.Object != nullptr)
{
    DropItemStaticMeshComponent->SetStaticMesh(HelmetStaticMesh.Object);
}

 

FObjectFinder를 통해서 가져온 오브젝트 역시 Succeeded() 함수와 Object 변수의 null 체크를 통해서 리소스가 제대로 불러와졌는지 체크를 한 뒤 사용해야 한다.

 

 

 

 

 

C++ 코드에서 C++ 클래스 가져오기

 

C++ 코드에서 블루프린트 클래스가 아닌 직접 작성한 C++ 클래스를 가져와서 사용하고 싶을 수도 있다. 예를 들어 게임 모드 클래스에서 기본 폰이나 기본 플레이어 컨트롤러를 설정하려고 할 때, C++로 작성한 폰 클래스나 플레이어 컨트롤러 클래스를 기반으로 블루프린트 클래스를 생성해서 넣어주는게 아니라 C++ 클래스를 곧바로 코드에서 넣어주고자 한다면 다음 예시 코드와 같이 작성하면 된다.

 

AYourProjectGameMode::AYourProjectGameMode()
{
    PlayerControllerClass = AYourCustomPlayerController::StaticClass();
}

 

StaticClass() 함수를 이용하면 런타임 중에 해당 클래스를 나타내는 UClass를 얻어낼 수 있다.

 

[투네이션]

 

-

 

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

 

반응형

데이터 테이블(Data Table) 사용하기

 

작성 기준 버전 :: 4.21.1

 

게임을 제작할 때 레벨업에 필요한 경험치량이나 스킬의 계수 등 추후에 밸런스 수정 작업이 필요한 값들은 함부로 코드에 상수로 넣어서는 안된다. 이런 부분은 기획자가 손쉽게 접근이 가능해야 하기 때문에, 기획자들이 주로 사용하는 엑셀이나 스프레드시트의 데이터를 언리얼 엔진으로 임포트해서 사용하는 방식을 지원한다. 이것을 데이터 주도형 접근법이라고 한다.

 

언리얼 엔진에서는 기획자들이 주로 사용하는 엑셀이나 스프레드시트에서 손쉽게 만들어낼 수 있는 .CSV 파일이나 서버 프로그램에서 주로 사용되는 JSON 파일을 손쉽게 임포트하는 기능을 제공한다.

 

 

데이터 테이블 임포트
 

데이터 테이블은 유용한 방식으로 짜여진 표를 의미한다. .CSV 파일을 임포트하기 위해서는 우선 프로그래머가 데이터를 엔진이 인식할 수 있게 Row 컨테이너를 만들어서 엔진에 데이터 해석 방식을 알려줘야 한다.

 

우리가 예시로 사용할 .CSV 파일은 다음 레벨업까지 필요한 경험치의 양에 대한 것이고 그 내용은 다음과 같다.

 

Name,ExpToNextLevel,TotalExp
1,0,0
2,100,100
3,200,300
4,300,500
5,400,700
6,500,900
7,600,1100
8,700,1300
9,800,1500
10,1600,2400

 

LevelUpTable.csv
다운로드

 

이런 컨테이너를 만드는 방법은 두 가지가 있는데 블루프린트를 이용하는 방식과 C++ 코드를 통해 만드는 방식이 있다.

 

 

블루프린트

 

데이터 테이블 로우를 만들기 위해서는 구조체를 생성해야 한다. 구조체의 이름은 BP_LevelUpTableRow로 한다.

 

 

블루프린트 구조체가 생성되면 더블클릭해서 블루프린트 구조체 에디터를 열고 변수를 추가한다. 추가하는 변수의 이름은 ExpToNextLevel과 TotalExp로 각 열의 이름과 순서가 일치해야 한다. 제일 첫 열인 Name은 게임 내에게 각 행에 접근하는 이름이 되는 것으로 따로 변수를 추가하지 않아도 된다.

 

 

변수를 모두 추가한 뒤에는 구조체를 저장하고 에디터를 닫는다. 그리고 콘텐츠 브라우저 패널에서 파일 창에 우클릭하여 /Game에 임포트... 를 선택한다.

 

 

CSV 파일을 임포트한다.

 

 

데이터 테이블 옵션 창이 뜨면 데이터 테이블 행 유형 선택을 방금 추가한 구조체로 설정하고 확인을 누른다.

 

 

추가된 데이터 테이블을 열어보면 .CSV 파일의 내용이 훌륭하게 임포트된 것을 확인할 수 있다.

 

 

 

C++ 코드

 

행 컨테이너를 블루프린트 구조체로 만들 경우, C++ 코드에서는 사용할 수 없다는 단점이 있다. C++ 코드에서 사용하기 위해서는 USTRUCT로 만들어야 되는데 언리얼 구조에 대한 설명은 C++ / USTRUCT 사용자 정의 구조체 만들기 문서에서 참고할 수 있다.

 

우선 Actor 클래스를 상속받아서 CustomDataTables라는 더미 클래스를 생성한다.

 

 

클래스가 생성되면 전처리기와 클래스 선언 사이에 구조체를 선언하는 코드를 추가해준다. 행 컨테이너로 사용되는 구조체는 FTableRowBase를 상속받아야만 한다.

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Engine/DataTable.h"
#include "CustomDataTables.generated.h"

USTRUCT(BlueprintType)
struct FLevelUpTableRow : public FTableRowBase
{
    GENERATED_BODY()

public:

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LevelUp")
        int32 ExpToNextLevel;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LevelUp")
        int32 TotalExp;
};

UCLASS()
class DATATABLETEST_API ACustomDataTables : public AActor
{
    GENERATED_BODY()
   
};

 

코드를 완성하고 프로젝트를 빌드한 뒤, 에디터로 돌아가서 .CSV 파일을 임포트해서 데이터 테이블 옵션의 데이터 테이블 행 유형 선택 드롭다운 메뉴를 열어보면 우리가 방금 추가한 LevelUpTableRow가 있는 것을 확인할 수 있다.

 

 

이를 통해서 .CSV 파일을 임포트하면 아까 블루프린트 구조체를 통해서 임포트했을 때와 동일하게 DataTable이 생성되는 것을 볼 수 있다.

 

 

 

 

 

데이터 테이블 사용하기

 

이번 파트에서는 작성한 데이터 테이블을 사용하는 방법에 대해서 알아보자.

 

블루프린트에서 데이터 테이블 사용하기

 

블루프린트에서 데이터 테이블을 사용하기 위해서는 블루프린트 그래프의 빈 자리에 우클릭해서 컨텍스트 메뉴를 열고 "데이터 테이블 행 구하기"를 검색해서 이 노드를 배치하면 된다.

 

 

GameModeBase를 상속받는 블루프린트 클래스를 하나 생성한다. 이벤트 그래프의 BeginPlay 이벤트로부터 다음과 같이 블루프린트 그래프를 구성하자.

 

 

위 그래프는 LevelUpTable에서 각 행을 가져와서 Total Exp 값을 화면에 출력하는 역할을 한다.

 

블루프린트를 저장하고 에디터로 가서 월드 세팅의 Game Mode를 방금 추가한 게임 모드로 바꿔준다.

 

 

그 다음 에디터에서 플레이 버튼을 눌러보면 화면에 각 레벨의 Total Exp가 연속으로 출력되는 것을 볼 수 있다.

 

 

 

C++ 코드에서 데이터 테이블 사용하기

 

C++ 코드에서 데이터 테이블을 사용하는 과정은 블루프린트에서 노드 하나만 생성하면 되는 것에 비해서는 조금 복잡하다.

 

우선 프로젝트에 DataTableTestGameModeBase라는 이름으로 새 게임 모드 클래스를 만들고 헤더에 다음 멤버 변수와 함수를 추가한다.

 

public:
    ADataTableTestGameModeBase();

    virtual void BeginPlay() override;

private:
    class UDataTable* LevelUpDataTable;

 

그리고 DataTableTestGameModeBase라는 .cpp로 가서 다음 전처리기를 추가한다.

 

#include "CustomDataTables.h"
#include "UObject/ConstructorHelpers.h"

 

ADataTableTestGameModeBase::ADataTableTestGameModeBase() 생성자 함수와 BeginPlay() 함수를 다음과 같이 구현한다.

 

ADataTableTestGameModeBase::ADataTableTestGameModeBase()
{
    static ConstructorHelpers::FObjectFinder<UDataTable> DataTable(TEXT("/Game/LevelUpTable"));
    if (DataTable.Succeeded())
    {
        LevelUpDataTable = DataTable.Object;
    }
}

void ADataTableTestGameModeBase::BeginPlay()
{
    Super::BeginPlay();

    if (LevelUpDataTable != nullptr)
    {
        for (int32 i = 1; i <= 10; i++)
        {
            FLevelUpTableRow* LevelUpTableRow = LevelUpDataTable->FindRow<FLevelUpTableRow>(FName(*(FString::FormatAsNumber(i))), FString(""));
            UE_LOG(LogTemp, Log, TEXT("Lv.%d :: ExpToNextLevel(%d) TotalExp(%d)"), i, (*LevelUpTableRow).ExpToNextLevel, (*LevelUpTableRow).TotalExp);
        }
    }
}

 

코드 작성이 완료되면 프로젝트를 빌드하고 에디터로 넘어간다.

 

그 다음 월드 세팅에서 게임 모드를 방금 만든 것으로 교체한다.

 

 

플레이 버튼을 눌러보면 게임이 시작되면서 데이터 테이블의 값들을 가져와서 로그를 출력하는 것을 확인할 수 있다.

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

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

 

반응형
  1. Game 2021.02.17 13:38

    감사합니다

  2. 익명 2022.05.11 07:32

    비밀댓글입니다

    • 익명 2022.05.18 08:51

      비밀댓글입니다

C++ / UENUM 사용자정의 열거형 만들기

 

작성 기준 버전 :: 4.21.1

 

C++ enum class 문서에서 확인할 수 있듯이, 열거형은 정수형 상수를 사람이 알아보기 쉽게 만들어준다. 이러한 열거형도 언리얼 구조체에서의 문제와 같이 표준 열거형은 코드 내부에서만 사용이 가능하고, 언리얼 에디터의 디테일 패널이나, 블루프린트에서 사용이 불가능하다는 문제가 있다.

 

이 열거형 역시 언리얼 구조체와 마찬가지로 언리얼 에디터에서 사용하고자 한다면 언리얼 열거형으로 만들어야만 한다.

 

언리얼 열거형 만들기

 

언리얼 전용의 열거형을 만드는 방법은 다음과 같다. 예시 코드와 같이 새로 만드는 열거형 앞에 UENUM() 매크로를 붙여주면 언리얼 에디터에서도 사용 가능한 언리얼 열거형이 만들어진다.

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "EnumTestActor.generated.h"

UENUM(BlueprintType)
enum class ETestEnum : uint8
{
    TE_OptionA UMETA(DisplayName = "Option A"),
    TE_OptionB UMETA(DisplayName = "Option B"),
};

UCLASS()
class ENUMTEST_API AEnumTestActor : public AActor
{
    GENERATED_BODY()
   
public:   
    // Sets default values for this actor's properties
    AEnumTestActor();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:   
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    UPROPERTY(EditAnywhere)
    ETestEnum TestEnum;
};

 

단, 여기서 주의해야할 점이 있는데, 언리얼 열거형을 만들때 반드시 일반적인 enum이 아닌 enum class로 만들어야 한다는 점이다. 만약 enum class로 만들지 않고 일반적인 enum으로 만들어서 UENUM() 매크로를 붙이고 컴파일을 하면 에러가 발생해서 컴파일에 실패한다. 그리고 UENUM은 uint8만을 지원하기 때문에 이 부분도 빠뜨리지 않고 넣어주어야 한다.

 

코드를 작성하고 컴파일한 후 레벨에 EnumTestActor를 배치하고 디테일 패널을 살펴보면 추가한 열거형이 보이는 것을 확인할 수 있다.

 

 

UENUM을 "BlueprintType"으로 선언하면 블루프린트에서도 사용할 수 있게 된다.

 

 

만약 새롭게 정의한 UENUM이 한 클래스에서 사용되는 것이 아니라 다른 코드 전반에서 사용되기를 원한다면, 언리얼 구조체 문서에서 설명한 것과 같이 열거형을 정의하기 위한 빈 클래스를 하나 추가해서 그 헤더에 UENUM을 선언하고 사용하고자 하는 곳에 그 헤더를 포함시키는 방법을 쓸 수 있다.

 

[투네이션]

 

-

 

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

 

 

반응형
  1. 익명 2019.02.15 00:31

    비밀댓글입니다

    • wergia 2019.02.24 16:46 신고

      enum class ETestEnum : uint8에서 uint8의 의미는 해당 열거형이 가질 수 있는 값의 범위를 uint8, 즉 8비트의 부호없는 정수형으로 제한한다는 뜻입니다. 언리얼 엔진에서 사용하는 열거형은 0-255 사이의 값, 총 256가지의 상태를 가질 수 있습니다.

  2. woong 2019.04.17 05:55

    PrimaryActorTick.TickGroup 의 enum ETickingGroup을 보면
    enum class가 아닌 단순한 enum인데 UENUM(BlueprintType)도 붙어있더라구요
    이건 왜그런건가요?

    • wergia 2019.04.17 10:58 신고

      언리얼 엔진의 블루프린트 에디터에서 사용할 수 있게 만들기 위해서 입니다.

캐스팅(Casting)

 

작성 기준 버전 :: 4.21 - 4.26

 

프로그래밍 작업을 할 때 캐스팅, 즉 형 변환은 상당히 중요하다. 특히 여러 클래스가 상속으로 엮여있는 상황이라면 더더욱 중요해진다. 언리얼 엔진에서는 대부분의 클래스가 AActor 클래스를 상속받고 있고, 개발자가 만들어내는 클래스 역시 상당수는 AActor를 상속받게 된다.

 

C++

 

그렇기 때문에 몇몇 함수들은 이렇게 메인이 되는 부모 클래스를 매개변수로 받거나 돌려준다. 간단한 예를 들자면 다음 함수가 있다.

 

void AActor::NotifyActorBeginOverlap(AActor* OtherActor)
{

}

 

NotifyActorBeginOverlap() 함수는 액터의 콜리전에 콜리전을 가진 다른 액터가 들어오기 시작했을 때 호출되는 함수로, 매개변수를 통해서 자신의 콜리전과 접촉한 액터를 알려준다. 언리얼 엔진에서는 레벨에 배치되는 모든 오브젝트는 AActor 클래스를 상속받기 때문에, 콜리전과 접촉한 액터가 어떤 클래스던지 상관없이 무조건 AActor 클래스로 보내주는 것이다.

 

만약 콜리전 체크를 하는 액터가 겹침 이벤트가 발생할때마다 데미지를 입는 클래스인데 데미지를 입힐 수 있는 클래스가 AProjectile 클래스라고 가정했을 때, 위의 예시 코드처럼 별도의 검사를 하지 않는다면, 액터가 아무 물체에나 스칠 때마다 데미지를 입어버릴 것이다.

 

그래서 필요한 것이 바로 캐스팅이다. 언리얼 엔진에서는 Cast<T>() 라는 함수로 기본적인 캐스팅을 제공한다.

 

void AActor::NotifyActorBeginOverlap(AActor* OtherActor)
{
    AProjectile* Projectile = Cast<AProjectile>(OtherActor);
    if (Projectile)
    {
        // Damage Process
    }
}

 

바로 위의 예시 코드처럼 캐스팅을 진행하면 된다. 만약 콜리전에 검출된 액터가 AProjectile 클래스가 아니라면 캐스팅에 실패할 것이고 Projectile 변수의 값을 nullptr이 되기 때문에 if문 안으로 진행하지 못해서 Damage Process가 진행되지 않는다.

 

블루프린트

 

블루프린트 작업에서도 캐스팅이 가능하다.

 

 

 

블루프린트 컨텍스트 메뉴에서 "형변환"이나, 캐스팅하고자 하는 타입의 클래스 명을 검색하면 해당 클래스로 형변환할 수 있는 노드를 추가할 수 있다.

 

[투네이션]

 

-

 

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

 

반응형
  1. 시우 2019.05.28 08:51

    좋은 자료 감사합니다

  2. scarecrow1992 2020.08.10 19:46 신고

    감사합니다~

제대로 따라가기 (5) C++ 프로그래밍 튜토리얼 :: UMG와 유저 인터페이스

 


작성버전 :: 4.21.0

언리얼 엔진 튜토리얼인 UMG와 유저 인터페이스에서는 언리얼 모션 그래픽(UMG)을 사용해서 여러가지 화면과 버튼이 있는 기본적인 메뉴 시스템을 만드는 법을 배울 수 있다.

 

튜토리얼대로 하면 문제가 발생해서 제대로 따라갈 수 없는 부분으로 동작이 가능하게 수정해야하는 부분은 빨간 블럭으로 표시되어 있다.


이번 튜토리얼에서 새로 배우게 되는 내용은 글 제일 끝에 "이번 섹션에서 배운 것"에 정리된다.

 

 

1. UMG용 모듈 종속성 구성(문서)

 

C++탭에서 기본 코드를 선택하고 "HowTo_UMG"라는 이름으로 프로젝트를 새로 생성한다.

 

 

언리얼 모션 그래픽(UMG)을 사용하는 코드를 작성할 것이기 때문에 비주얼 스튜디오(Visual Studio)에 들어가서 기본으로 포함되어 있지 않은 몇 가지 모듈을 포함시키도록 하자.

 

언리얼 에디터의 파일 드롭다운 메뉴에서 Visual Studio 열기를 선택해서 비주얼 스튜디오를 연다.

 

 

UMG는 몇 가지 모듈에 종속되어 있는데, 그 모듈을 "HowTo_UMG.Build.cs"에 추가해야 한다.

 

 

"HowTo_UMG.Build.CS"에서 포함된 퍼블릭 모듈 목록에 "UMG"를, 포함된 프라이빗 모듈 목록에 "Slate" 와 "SlateCore" 를 추가해야 한다.

 

PublicDependencyModuleNames에 UMG를 다음과 같이 추가한다.

 

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG" });

 

비어있는 PrivateDependencyModuleNams를 주석 처리하고 "Slate"와 "SlateCore"가 있는 부분을 주석 해제 한다.

 

//PrivateDependencyModuleNames.AddRange(new string[] {  });

// Uncomment if you are using Slate UI
PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

 

UMG 구성이 완료되었다면, 프로젝트의 커스텀 게임 모드에 코드를 추가하여 게임 메뉴를 만들고 표시할 수 있다.

 

 

2. 게임 모드 확장(문서)

 

게임이 시작되면 유저 위젯(User Widget)을 새로 만들어 표시하거나, 나중에 제거할 수 있도록 하기 위해서 Game Mode(게임 모드) 클래스에 함수와 프로퍼티를 추가하자. 각 프로젝트에는 커스텀 게임 모드 클래스가 딸려오므로, HowTo_UMGGameMode.h에 다음 코드를 추가하면 된다.

 

버전이 바뀌면서 GameMode 클래스 파일의 이름이 "ProjectNameGameMode.h", "ProjectNameGameMode.cpp"에서 "ProjectNameGameModeBase.h", "ProjectNameGameModeBase.cpp"로 바뀌었다. 비주얼 스튜디오의 솔루션 탐색기에서 HowTo_UMGGameModeBase.h를 열어서 작업하자.

 

public:
    UFUNCTION(BlueprintCallable, Category = "UMG_Game")
    void ChangeMenuWidget(TSubclassOf NewWidgetClass);

protected:
    virtual void BeginPlay() override;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UMG_Game")
    TSubclassOf StartingWidgetClass;

    UPROPERTY()
    UUserWidget* CurrentWidget;

 

TSubclassOf 클래스를 사용할 때, [클래스 템플릿 "TSubclassOf"에 대한 인수 목록이 없다]는 에러가 발생한다. 전체 코드를 보고 유추해보건데, BeginPlay() 함수에서 StartingWidgetClass의 내용물을 CurrentWidget 변수에 넣어주거나 ChangeMenuWidget() 함수가 동작할 때, 매개변수로 받은 NewWidgetClass를 CurrentWidget에 대입해주는 방식으로 동작할 것임을 알 수 있다. 그렇기 때문에 여기에서는 TSubclassOf의 템플릿 인수로 UUserWidget 타입을 넣어주는 것이 올바른 해결책일 것이다.

 

다음과 같이 코드를 수정해주자.

 

public:
    UFUNCTION(BlueprintCallable, Category = "UMG_Game")
    void ChangeMenuWidget(TSubclassOf<UUserWidget> NewWidgetClass);

protected:
    virtual void BeginPlay() override;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UMG_Game")
    TSubclassOf<UUserWidget> StartingWidgetClass;

 

이제 HowTo_UMGGameModeBase.cpp로 가서, 선언한 두 함수의 구현을 해야 한다. 우선 BeginPlay()를 덮어쓰는 것으로 시작하자.

 

void AHowTo_UMGGameModeBase::BeginPlay()
{
    Super::BeginPlay();
    ChangeMenuWidget(StartingWidgetClass);
}

 

부모 클래스의 함수를 덮어쓸 때는, 여기 BeginPlay에서 하듯이 해당 함수의 부모 클래스 버전을 호줄하는 것이 중요한 경우가 많다. 우리가 구현하는 함수의 버전은 기존의 절차의 끝 부분에 한 단계를 추가하기 위한 것이므로, 함수 첫 줄에 Super::BeginPlay()를 호출한다.

 

그리고 여기서는 UUserWidget을 사용하기 위해서, HowTo_UMGGameModeBase.cpp 상단에 "Blueprint/UserWidget.h"를 포함시켜 주어야 한다.

 

#include "Blueprint/UserWidget.h"

 

계속 해서, 메뉴 간의 전환 방식을 구현해야 한다. 뷰포트에 활성화된 유저 위젯이 있다면 제거하고 난 다음에 유저 위젯을 새로 만들어 뷰포트에 추가해주도록 구현한다.

 

void AHowTo_UMGGameModeBase::ChangeMenuWidget(TSubclassOf NewWidgetClass)
{
    if (CurrentWidget != nullptr)
    {
        CurrentWidget->RemoveFromViewport();
        CurrentWidget = nullptr;
    }

    if (NewWidgetClass != nullptr)
    {
        CurrentWidget = CreateWidget(GetWorld(), NewWidgetClass);
        if (CurrentWidget != nullptr)
        {
            CurrentWidget->AddToViewport();
        }
    }
}

 

함수 구현할 때도, 변경된 선언에 맞춰서 매개변수 타입을 아래와 같이 수정하자.

 

void AHowTo_UMGGameModeBase::ChangeMenuWidget(TSubclassOf<UUserWidget> NewWidgetClass)

 

이 코드는 제공된 위젯 인스턴스를 만들어 화면에 넣는다. 언리얼 엔진은 한 번에 다수의 위젯을 표시하고 상호작용처리가 가능하며, 한 번에 하나만 활성화 되도록 제거를 할 수도 있다. 하지만 위젯을 직접 소멸시킬 필요는 없는데, 뷰포트에서의 제거 밑 레퍼런싱하는 모든 변수 소거(또는 변경) 작업은 언리얼 엔진의 가비지 컬렉션 시스템에서 해주기 때문이다.

 

마지막으로 Player Controller 클래스에 입력 모드를 설정해야 한다. 그러기 위해서 Player Controller를 기반으로 새로운 C++ 클래스를 추가하자. 이 클래스 안해서 게임이 시작될 때 함수 하나를 추가로 호출해주기만 하면 UI 요소와 상호작용이 가능하도록 할 수 있다.

 

 

 

HowTo_UMGPlayerController.h에서 클래스에 다음 오버라이드를 추가하고 .cpp에 구현한다.

 

public:
    virtual void BeginPlay() override;

 

void AHowTo_UMGPlayerController::BeginPlay()
{
    Super::BeginPlay();
    SetInputMode(FInputModeGameAndUI());
}

 

메뉴 생성 및 표시와 필요없어진 메뉴를 제거하기 위한 코드 프레임워크를 완성했다. 이제 언리얼 에디터로 돌아가 메뉴 에셋을 디자인 해보자.

 

 

 

 

 

3. 메뉴 위젯 블루프린트 생성(문서)

 

언리얼 에디터에서 컴파일 버튼을 누르면 수정된 코드가 빌드된다. 이를 통해서 유저 위젯을 메뉴로 사용할수 있게 된다.

 

 

이제 게임 모드가 메뉴로 사용할 유저 위젯을 생성해보자. 콘텐츠 브라우저의 "신규 추가" 버튼을 누르고 유저 인터페이스 카테고리에서 위젯 블루프린트(Widget Blueprint) 클래스를 선택해서, "MainMenu"와 "NewGameMenu"라는 이름으로 두 개의 유저 위젯을 만든다.

 

 

방금 만든 "MainMenu" 위젯을 더블클릭하면 블루프린트 디자이너 창이 열리며, 여기서 메뉴 레이아웃을 만들 수 있다.

 

팔레트 패널의 일반 섹션에서 버튼(Button)과 텍스트(Text)를 끌어 그래프에 배치한다. 이 버튼은 새 게임 메뉴를 여는데 사용될 것이다.

 

 

버튼의 위치와 크기를 다음과 같이 수정하고, 함수성 연결을 해줄 때 알아보기 쉽게 하기 위해서 이름을 "NewGameButton"으로 변경한다.

 

 

그리고 이 버튼이 무슨 버튼인지 보여주기 위해서 텍스트 블록(Text Block)을 버튼 위로 끌어다 놓고 디테일을 다음과 같이 수정한다.

 

- Text를 "New Game"으로 변경

 

- Visibility를 Hit Test Visibility로 변경한다. 그러면 버튼을 누르려는 클릭을 텍스트 블록이 막지 않는다.

 

- 이름을 "NewGameText"로 변경한다. 필수는 아니지만 나중에 계층구조에서 어떤 UI인지 찾기 쉬워지기 때문에 들여두면 좋은 습관이 된다.

 

 

 

두 번째 버튼과 텍스트 블록을 만들어서 "Quit"(종료) 기능을 만든다. 버튼 이름은 "QuitButton", 버튼 위치는 (600, 100), 텍스트 블록 이름은 "QuitText"로 설정한다.

 

그 다음은, 버튼을 클릭했을 때, 코드가 실행되도록 버튼에 이벤트를 추가하는 작업을 해야한다. 디테일 패널에서 적합한 이벤트의 이름 옆에 "+"버튼을 찾아서 누르면 되는데 이 경우에는 "OnClicked" 이벤트를 추가하면 된다.

 

 

NewGameButton의 OnClicked 이벤트를 다음과 같이 구성한다.

 

 

QuitButton의 OnClicked 이벤트를 다음과 같이 구성한다.

 

 

 

메인 메뉴를 만들었으니, 레벨이 시작되면 메인 메뉴를 로드하는 게임 모드 애셋을 구성하면 된다.

 

 

4. 게임 모드 환경설정(문서)

 

콘텐츠 브라우저에서 프로젝트의 게임 모드에 맞는 블루프린트 클래스를 두 개 추가할 것이다. 그러면 그 두 클래스에 노출된 변수를 원하는 값으로 설정하는 것이 가능하다.

 

콘텐츠 브라우저에서 신규 추가버튼에서 블루프린트 클래스를 클릭한다.

 

 

부모 클래스로 HowTo_UMGGameModeBase를 선택해서 "MenuGameMode" 블루프린트 클래스를 만든다.

 

 

그리고 게임 내에서 마우스 커서를 보이게 하기 위해서, 플레이어 컨트롤러의 블루프린트 클래스도 만들어 주어야 한다. 콘텐츠 브라우저에서 블루프린트 클래스를 클릭하고 Player Controller 클래스 상속받아서 "MenuPlayerController"라는 이름으로 클래스를 생성하자.

 

"MenuPlayerController" 클래스가 생성되었으면, 콘텐츠 브라우저에서 블루프린트 파일을 더블클릭해서 블루프린트 에디터를 연다. 그리고 디테일 창에서 "Show Mouse Cursor" 박스를 체크한다.

 

 

다음은 "MenuGameMode"를 편집한다.

 

Starting Widget Class를 "MainMenu" 애셋으로 설정해서 게임 시작시 메뉴가 뜨도록 만든다.

 

Default Pawn Class를 Default Pawn이 아닌 Pawn으로 설정해서 플레이어가 메뉴에 있을 때, 이리저리 날아다니지 않도록 만든다.

 

Player Controller Class를 방금 만든 "MenuPlayerController" 애셋으로 설정해서 메인 메뉴에서 마우스 커서가 표시되도록 만든다.

 

 

우리가 만든 게임 모드 블루프린트를 사용하려면, 레벨 에디터 창으로 돌아와 세팅 버튼을 통해 현재 레벨에 대한 월드 세팅을 변경해야 한다.

 

 

프로젝트 세팅 메뉴의 맵 & 모드에서도 기본 게임 모드 설정이 가능하다. 이 방법을 사용하면 따로 덮어쓰지 않는 한 모든 레벨에서 기본 게임 모드로 설정된다. 어느 방법을 사용할지는 프로젝트 구성에 따라 달라질 수 있다.

 

월드 세팅 패널에서 Game Mode Override 항목을 "MenuGameMode" 애셋으로 설정한다.

 

 

이제 레벨에 메인 메뉴를 로드하고, 마우스 커서를 표시하는 플레이어 컨트롤러를 사용하도록 환경설정된 커스텀 게임 모드 애셋이 적용되었다. 이제 게임을 실행하면 Quit 버튼은 정상적으로 작동하지만, 아직 New Game 버튼은 빈 메뉴 화면으로 이동한다. 다음 단계에서는 New Game Menu를 구성해주자.

 

 

 

 

 

5. 2차 메뉴 제작(문서)

 

콘텐츠 브라우저에서 아까 만든 "NewGameMenu" 애셋을 연다. 이 메뉴는 이름을 입력할 수 있는 텍스트 박스와, 이름을 입력하기 전에는 누를 수 없는 '게임 플레이' 버튼, 메인 메뉴로 돌아가는 버튼으로 구성된다.

 

이름 입력 박스를 만들기 위해, 레이아웃에 Text Box(텍스트 박스)를 배치한다.

 

 

텍스트 박스의 설정은 다음과 같다.

 

 

이전 메뉴에서 버튼을 만들었던 것과 같은 방식으로 텍스트 블록 라벨이 있는 게임 플레이 버튼을 만든다.

 

버튼 : 이름은 PlayGameButton, 위치는 200, 300, 크기는 200, 100으로 변경한다.

 

텍스트 블록 : 이름은 PlayGameText, Visibility는 Hit Test Visible로, 내용은 Play Game으로 변경한 다음 PlayGameButton위에 배치한다.

 

게임 플레이 버튼의 경우, 만약 플레이어 이름 입력란이 비어있다면 작동하지 않도록 특수한 기능을 추가한다. UMG의 바인드 기능을 사용하여 (Behavior섹션 아래) "Is Enabled" 칸에 새로운 함수를 만들면 된다.

 

 

텍스트 박스가 공백이 아니어서 버튼이 활성화 될 수 있는 상태인지 확인하려면, 텍스트 박스에서의 텍스트를 스트링으로 변환한 다음 길이가 0보다 큰지 검사하면 된다.

 

 

이제 메인 메뉴로 돌아갈 수 있도록 버튼을 하나 추가해보자. 메인 메뉴에서 게임 플레이 버튼과 비슷하지만, 위치 기준이 좌상단이 아닌 우하단 구석이 될 것이다. 그러기 위해서는 디테일 패널에서 "앵커" 드롭다운을 클릭한 다음, 팝업 메뉴에서 우하단 부분을 나타내는 모양을 선택한다.

 

버튼 이름을 MainMenuButton으로 설정한다.

 

위치를 -400, -200으로 설정한다.

 

크기를 200x100으로 설정한다.

 

 

이제 NewGameMenu 위젯의 버튼들에도 OnClicked 이벤트들을 추가하자

 

 

메인 메뉴 버튼의 경우, 다시 메인 메뉴 위젯을 열어주지만, 게임 플레이 버튼은 누르면 메뉴를 비활성시킨 후, 게임에서 더 이상 아무것도 할 수 없게 만든다. 보통 이 시점에서 첫 레벨을 로드하고, 오프닝 동영상을 재상하거나 폰을 스폰시켜 빙의하는 등의 처리를 하게 된다.

 

모든 작업을 마치고 플레이 해보면 다음 스크린샷과 같은 장면을 얻을 수 있다.

 

 

 

 


 

이번 섹션에서 배운 것

 

1. 언리얼 모듈 종속성

언리얼 엔진의 기능은 다수의 모듈로 나누어져 있고, 그 중에 필요한 모듈을 묶어서 사용하는 방식이다. 이번 섹션 처음 부분에 build.cs파일에서 모듈 종속성을 구성할 때도 보았겠지만, 기본적으로 언리얼은 Core, CoreUObject, Engine, InputCore 모듈을 사용하고 있었고, UI와 관련된 기능을 사용하기 위해서 UMG 모듈과 Slate, SlateCore 모듈을 구성에 추가해주었다.

 

추후의 일이지만, 언리얼 엔진을 커스터마이징하고자 할 때, 새롭게 추가하는 기능을 이러한 모듈로 만들어 덧붙이게 될 것이다.

 

2. TSubClassOf<T>

UClass 타입 안정성을 보장하는 템플릿 클래스. TSubClassOf에 전달된 인수가 템플릿 인자로 받은 타입과 일치하거나 템플릿 인자로 받은 타입을 상속받은 타입인지를 런타임 중에 확인하도록 도와주는 클래스이다.

 
3. UUserWidget
UUserWidget* UserWidget;

Widget Blueprint를 통해서 확장할 수 있는 사용자 위젯.

UserWidget->AddToViewport();

유저 위젯을 뷰 포트에 추가하는 함수.

UserWidget->RemoveToViewport();

유저 위젯을 뷰 포트에서 제거하는 함수.

 

4. AActor::GetWorld()

GetWorld();

UWorld 객체를 가져오는 함수. UWorld는 액터나 컴포넌트들을 포함하는 맵이나 샌드박스의 최상위 객체이다.

 

5. CreateWidget()

CreateWidget(GetWorld(), newWidget);

위젯을 생성하는 함수

 

6. APlayerController::SetInputMode()

SetInputMode(FInputModeGameAndUI());

플레이어 컨트롤러의 입력 모드를 설정하는 함수. Game 입력만 받을지, UI 입력만 받을지, 아니면 둘 다 받을지를 정할 수 있다.

 

7. UPROPERTY()

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UMG_Game")

클래스의 멤버 변수에 붙이는 매크로. 에디터의 노출 수준을 정할 수 있다.

 

EditAnywhere는 에디터 디테일 패널에 노출이 가능하다.

 

BlueprintReadOnly는 블루프린트에서 수정을 불가능하고 읽기만 가능하다.

 

Category는 블루프린트에서 불러올 때, 카테고리를 분류해서 찾기 쉽게 만들어준다.

 

8. UFUNCTION()

UFUNCTION(BlueprintCallable, Category = "UMG_Game")

함수에 붙이는 매크로.

 

BlueprintCallable은 블루프린트에서 호출만 가능하다는 의미이다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

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

 

반응형
  1. 흑흑 2020.03.12 20:22

    덕분에 잘 배워갑니다.
    댓글 하나 남기면
    TSubClassOf<UUserWidget> StartingWidgetClass;
    여기에서 TSubclassOf 인데 class 가 Class로 오타가 나있어서 저대로 하면 컴파일이 안돼요
    혹시 저같은 초보자분 계시면 똑같이 헤멜까봐 남겨요

    • wergia 2020.03.12 20:30 신고

      앗...아 감사합니다. 얼른 수정했습니다!

  2. rickey 2020.07.02 14:15

    마지막 정리쪽에

    5. CreateWidge() 이라고 되어있는데 t가 빠진거 같아요

    • wergia 2020.07.13 11:33 신고

      정말 그렇네요! 오타 제보 감사드립니다 ㅎㅎ
      수정했습니다

+ Recent posts