안녕하세요. '언리얼로 만들어보는 RPG'의 필자입니다. 글의 시작을 이렇게 하기로 정했더니 마음이 훨씬 편하네요. 오늘은 전반적인 UI의 설계부터 시작해서 인벤토리와 PlayerBase에 있는 정보를 참조할 수 있도록 작업을 해보려고 합니다.
그러고보니 이번포스팅을 시작하기 전에 너무 자연스럽게 넘어가 버린 부분이 있어서 짚고 넘어가야할 것 같습니다.
위 사진처럼 여러분의 ProjectName.Build.cs 파일에서 UMG, Slate, SlateCore 모듈을 추가해주시면 됩니다. 사실 UMG만 추가해주셔도 될 것 같은데 필자의 경우 인터넷의 바다를 돌다가 저렇게 3개 추가하라길래 추가한 것 뿐입니다. 아마 앞으로 사용할 것들에서 UMG모듈만 있어도 문제는 없지 않을까 감히 예상해 봅니다.
<전반적인 클래스 구성도>
필자가 직접 만드는 모든 UI들은 C++을 이용해서 작성을 할 예정이기 때문에 PlayerBase로 부터 정보를 가져와야 하기 때문에 위 사진처럼 CustomUI를 전부다 상속받아서 제작할 예정입니다. 물론 에디터나 블루프린트를 사용해야 하는 부분은 어쩔 수 없이 그대로 사용할 것입니다.
GameUI 클래스는 앞으로 인벤토리 말고도 장비창, 경험치, 체력, 마력등 다양한 UI를 포함하고 있을 메인 UI가 되어줄 클래스입니다. 이건 따로 작업을 해서 화면에 띄워줘야 하는데 그건 그때가서 작성하도록 하고 후딱후딱 코드를 작성하러 가보도록 하지요!
UCustomUI.h
CustomUI는 별거 없습니다. 생성자와 초기화를 담당해줄 Init함수 그리고 가장 중요한 PlayerBase* 변수만 있습니다.
UGameUI.h
앞으로 필요한 UI들을 다 책임지고 들고있어줄 GameUI클래스입니다. CustomUI를 상속받고 지금당장은 인벤토리 클래스 하나만 들고 있으면 됩니다. 다은 UI요소들과 마찬가지로 UPROPERTY 설정에서 BindWidget을 해줍니다. UI를 C++에서 생성하면 에디터상에서 조절할 수 없는 대신 이렇게 Bind시키면 코드에서 가져다 쓸 수 있으니 다행입니다.
UGameUI.cpp
GameUI의 초기화 함수는 별거 없습니다. 인벤토리의 Player포인터를 채워넣어주고 초기화 함수를 실행시켜줄 뿐입니다. 앞으로 있을 대부분의 초기화 코드도 이와 비슷한 방식으로 진행될 예정입니다. 보완의 여지가 없는것은 아니지만 우선은 구현을 우선시하겠습니다.
UInventory.h
UInventory클래스의 헤더입니다. 마찬가지로 CustomUI를 상속받고 Init함수를 오버라이딩 해주었습니다. DefTex의 경우 인벤토리가 비어있는 것 일때 채워줄 이미지이며 Slots는 코드 내부적으로 관리할 Slot들을 담아주려고 만들어둔 배열입니다.
UInventory.cpp
중요한 Init함수 구현코드입니다. 처음 있는 포문의경우 필자는 인벤토리 크기를 32로 고정할 것 이기때문에 저렇게 하드코딩했지만 이걸 보시는 여러분은 저렇게 하시면 안좋다는걸 알고 계시리라 믿습니다. SlotUI를 담아줄 배열의 크기를 32로 초기화 시켜주고 (&)WidgetTree로부터 현재 UI가 가지고 있는 모든 Widget을 받아옵니다.
이후에는 별거 없습니다. Slot의 Player포인터를 채워주고 타입을 지정해준다음 초기화 과정을 거칩니다. 이렇게 준비가 된 Slot을 Slots배열에 넣어서 나중에 필요할때 인덱스를 통해서 관리하기 위해 넣어줍니다. 아참 WidgetTree헤더를 추가해주셔야 사용하실 수 있습니다.
USlot.h
이제 대망의 Slot클래스로 왔습니다. 저번과 달라진 부분은 새롭게 설계를 하면서 필요한 함수들이 몇개 생겼다는 것입니다. 빨간색 박스로 테두리를 쳐두었으니 확실히 보이실겁니다.
USlot.cpp
위 사진의 함수 3가지는 별다른 내용은 없고 이름 그대로의 역할을 합니다. 이 Slot이 어떤 UI에서 사용되는 슬롯인지 지정해주고 Image Widget에서 사용할 브러쉬를 설정해주는 정도의 역할입니다. 가장 중요한건 초기화 함수에 있는 Refresh함수입니다. 이 함수의 경우에는 재사용성을 고려해서 작성했습니다.
USlot.cpp
우선 현재 Slot의 타입을 확인하여 플레이어가 가진 정보중 어디를 참조해야할지를 정합니다. Item이라면 인벤토리를 참조해야한다 뭐 이런식입니다. 코드에서 Texture가 null인지 확인하는 작업이 두번이나 있는데 안전 또 안전을 고려해야합니다. 절대로 생각안하고 막 짠것은 아닙니다.
이제 플레이어로부터 인벤토리의 정보를 받아올 수 있으니 여태까지 만든 클래스들을 부모클래스로 설정하여 UI를 만들고 제대로 그려지는지 확인할 시간이군요. 왜이렇게 하기가 힘이 드는지 벌써부터 글을 마무리 짖고 싶은 마음이 살살 올라오지만 끝까지 해보겠습니다.
<좌측부터 차례로 BpGameUI, BpInventory, BpSlot>
우선 저번 포스팅때 만든 WidgetBlueprint 세가지의 부모 클래스를 설정해줍니다. 이번에는 저번과 다르게 블루프린트를 통해서 확인하지 않고 게임이 시작될때 PlayerBase클래스에서 생성해줄 것입니다.
PlayerBase.h
얼마만에 다시보는 PlayerBase 클래스인지 모르겠습니다. 물론 이 글을 보시는 여러분은 얼마 안됐다고 하실지 모르겠지만 필자의 경우에는 몇주만에 보게되는 진귀한 장면이었습니다.
블루프린트에서 GameUIClass를 지정해주고 BeginPlay에서 생성해 줄것입니다.
PlayerBase.cpp
BeginPlay함수에서는 블루프린트에서 할당해준 GameUI위젯이 있는지 확인하고 있다면 CreateWidget으로 만들어줍니다. 이후에는 Player포인터를 채워주고 초기화후 ViewPort에 추가시킵니다.
BpPlayer(블루프린트)
빌드하신후에 에디터를 통해서 보시면 새롭게 지정한 변수가 잘 뜨는게 보실겁니다. 여기서 중요한 부분이 있는데 저 GameUIClass에 들어갈 클래스의 경우 C++로 만든클래스가 아니라 그걸 상속받은 BpGameUI를 넣어줘야 한다는 부분입니다.
BpInventory(UMG에디터 그래프모드)
확인하기에 앞서 마지막으로 BpInventory로 가셔서 그래프 모드를 선택하신다음에 DefTex에 원하시는 인벤토리 기본 이미지를 설정해 주시면 됩니다.
<정상적으로 그려지는 인벤토리UI>
자 여기까지 오시면 기본적인 작업이 드디어 끝난것입니다. 앞으로 남은내용이 얼마 없긴 하지만 필자의 체력은 더이상 남아있지 않군요. 정말로 하얗게 불태워 버렸습니다. 이 포스팅은 여기서 마무리하고 다음에 뵙도록 하겠습니다.
<불타버린 필자>
'프로그래밍 > 언리얼' 카테고리의 다른 글
언리얼로 만들어보는 RPG - 인벤토리 구현(5) (0) | 2019.02.11 |
---|---|
언리얼로 만들어보는 RPG - 인벤토리 구현(4) (0) | 2019.02.08 |
언리얼로 만들어보는 RPG - 인벤토리 구현(2) (0) | 2019.02.01 |
언리얼로 만들어보는 RPG - 인벤토리 구현(1) (1) | 2019.01.31 |
언리얼로 만들어보는 RPG - 캐릭터 구현(7) (2) | 2019.01.29 |