안녕하세요 '언리얼로 만들어보는 RPG'의 필자입니다. 이번 글에서는 QuickSlot 사용을 위한 플레이어의 입력처리와 저번 글에서 언급한 Drag&Drop에 관련된 함수의 위치를 변경할 것입니다.


작업을 시작하기에 앞서 필자는 QucikSlot을 최대 8개로 만들어 두었고 입력을 Action Mapping을 이용해서 하려 했습니다. 다만 한가지 문제점이 있었는데 필자가 알고 있는 한계에서는 Action Mapping을 통한 (&)Bind Action에는 값이 들어오지 않는다는 것이었습니다.


몇번의 검색을 통해서 해결방법을 알아냈는데 Bind Action에 (&)Delegate함수를 Bind 시킬 수 있다는 것이었습니다. 자세한 내용은 아래의 출처를 따라가 보시면 됩니다.


프로젝트 셋팅(입력)

<QuickSlot 사용을 위한 준비 나머지도 똑같다>


PlayerBase.h

int변수 하나를 받는 Delegate 하나와 실질적으로 사용될 함수 UseQuickSlot함수를 선언합니다.


PlayerBase.cpp

<BindAction의 Delegate 연결과 UseQuickSlot함수 코드>


이후에는 GameUI를 타고 내려가 QuickSlot UI에서 함수를 호출해 사용할 수 있게 합니다.


GameUI.cpp


QuickSlot.cpp

플레이어의 입력준비를 다 해놨으니까 이제 Drag&Drop 부분을 변경할 시간입니다.


PlayerBase를 거쳐서 작업했던 부분들을 다 삭제합니다.

ㄴ GameUI::void RefreshInventory

ㄴ PlayerBase::DraggingSwap

ㄴ PlayerBase::SwapInven


SlotDrag.h

<대격변이 일어난 Slog DragDropOperation>


Drag&Drop과 관련된 코드가 PlayerBase를 거쳐가는게 매우 이상했습니다. Drag&Drop을 할때 사용하는 클래스가 있는데도 불구하고 그런다는건 더더욱 이상한 일이지요. 그래서 다 옮겨버렸습니다. 옮기는김에 구조도 좀 바꾸었구요.


여전히 Player를 통해 Inventory 배열에는 접근해야 하기 때문에 Player포인터를 가지고 있습니다. 또 저번에는 Slotnum과 Type을 저장했었는데 Index라는 별도의 변수가 생기고 구조가 바뀌면서 Slot 자체를 받아오기로 했습니다.


SlotDrag.cpp

Drop함수는 저번 PlayerBase에 있던 DraggingSwap함수와 같은 기능을 합니다. Drag를 시작한 Slot과 Drop한 Slot의 type을 확인하여 어떤 동작을 할지 정해주는 분기함수입니다.


SlotDrag.cpp

<저번과 크게 달라지지 않은 SwapInven>


SlotDrag.cpp

현재는 Inventory 뿐이기 때문에 다른 경우는 작성해 두지 않았습니다. 만일 Drop한 Slot의 Type이 Q_Item일 경우 이미 해당 QuickSlot에 다른 Item이 들어가 있는것이기 때문에 참조중인 ItemData에서 해당 Slot을 제거해줍니다. 이후에는 Drag한 Slot과 연결된 ItemData의 참조중 목록에 추가해주고 Type과 Index를 지정해주고 갱신해줍니다.


이제 작업한 코드가 잘 작동하는지 테스트를 해볼 시간입니다.


<아이템을 옮기자 갱신이 안되는 문제가 생겼다>


어째서 이러한지 이유를 알기위해서 열심히 디버그도 해보고 머리도 굴려봤습니다. 결론부터 말씀드리자면 우선 아이템 사용후 갱신하는 방식을 바꿔서 그렇습니다. Inventory의 Slot UI는 ItemData 배열과 1:1로 대응하기 때문에 ItemDate를 Swap하면 ReferenceSlots 또한 바뀌어서 그렇습니다.


<이해를 돕기위한 이미지>


위의 이미지처럼 Inventory UI에서는 Slot이 가지고 있는 Index를 통해서 Inventory 배열에 접근해서 아이템을 사용하는데 Swap후에는 갱신해야할 Slot UI 목록이 바뀌어서 그런것입니다.


InGameData.cpp

해당 문제를 해결하기 위해서 Slot목록을 담은 배열만 바꿔주는 함수를 작성했습니다.


SlotDrag.cpp

Inventory 배열의 정보를 Swap해준 다음에 방금 만들어준 SwapReference함수를 불러줍니다.


<아이템을 옮겨도 Inventory UI는 잘 작동하지만...>


네 이번에는 QuickSlot쪽에서 다시 문제가 생겼습니다. 좀 문제점을 찾아본 결과 아까와 같은 맥락의 ItemData를 참조하고 있는 Slot들에 대한 갱신 문제였습니다. Inventory UI의 Slot은 Inventory 배열과 1:1 대응이지만 QuickSlot의 경우 1:1 대응이 아니기 때문에 ItemData를 참조중인 QuickSlot은 여전히 그 목록에 있어야 하는것입니다.


이번 글이 너무 길어지는 것 같아서 다음 포스팅에서 이어 작성하도록 하겠습니다. 분량조절 하는건 쉽지 않은것 같습니다.

Posted by 별수집가
,

안녕하세요 '언리얼로 만들어보는 RPG'의 필자입니다. 작업진도가 생각보다 빠르게 나가지 않는게 필자의 게으름 탓이겠지만 아니라고 믿고싶습니다. 오늘은 퀵슬롯을 만들 예정인데 기존의 구조를 조금 변경해야할 필요성이 있습니다. 또한 Player를 통해서 Slot의 Swap을 해준게 적합한 위치가 아니라 판단되어 다른 클래스에서 처리할 것입니다.


현재 인벤토리는 이런식으로 Slot UI에서 Slotnum를 통해 직접적으로 접근해 갱신하는 형태를 띄고 있습니다. 이 부분은 크게 변경되지 않겠지만 QuickSlot이 생김으로써 인벤토리 배열에 있는 아이템 데이터를 참조하는 Slot이 1:1이 아니게 됩니다. 1:N 의 형태를 띄기 때문에 아이템을 사용했을경우 갱신을 할 다른 방법을 통해야 합니다.


그래서 필자가 떠오른 생각은 ItemData 안에 현재 Data를 참조하고 있는 Slot들의 포인터를 배열로 담아두고 갱신을 할때 배열에 들어와 있는 Slot들을 갱신해주는 형태를 취하려고 합니다. 관련된 디자인 패턴으로는 아마 (&)감시자 패턴이 가장 근접하지 않은가 싶습니다.


<무지개색 총공격이다!>


여전히 Slotnum를 통해서 직접적인 접근을 할 수 있지만 사용후에 갱신을 하는 대상이 하나가 아니라 다수이기 때문에 ItemData에서는 참조중인 Slot들을 배열에 넣어 위 이미지 처럼 관리할 것입니다. 또한 Slotnum은 Data배열과 1:1 대응일 경우를 기반으로 설계되었기 때문에 이제부터는 연결된 배열의 위치를 따로 저장할 Index라는 변수를 만들것 입니다.


대충 계획이 세워졌으니 바로 작업을 시작하도록 하겠습니다.


InGameData.h

USlot*를 담아줄 배열을 선언해주고 함수를 통해서만 접근할 수 있도록 Private영역으로 집어넣었습니다. 해당 배열에 들어있는 Slot들은 정보가 바뀐걸 알려주기만 하면 되기 떄문에 외부에서 접근해 사용할 수 없게 만들었습니다. public 영역의 밑줄친 3개의 함수는 각각 배열에 추가, 삭제를 해줍니다.


InGameData.cpp

<Add · Remove Slot 구현코드>


InGameData.cpp

<아이템 사용후 참조하고있는 Slot들을 갱신한다>


이제 ItemData쪽에서는 갱신해줄 Slot들을 받아서 처리할 준비가 다 되었습니다. 다음으로는 Slot에서 Slotnum과 Index의 작업을 해줘야 합니다.


Slot.h

<Slotnum 역할을 대신할 Index 선언>


Slot.cpp

슬롯의 타입이 Item, Skill때만 Slotnum과 Index를 같게 만들어 줍니다. 왜냐하면 해당 Slot들은 UI에서 Data배열과 1:1로 대응하는 Slot들이기 때문입니다. 이후에는 Slot클래스에서 Slotnum을 이용해 참조하던 부분들을 전부 Index로 바꿔주시면 됩니다.


QuickSlot.h

Slot UI를 담아줄 배열과 기본 채워줄 기본이미지를 가지고 있을 DefTex 변수를 선언해 줍니다. Use함수의 경우 플레이어가 키보드의 숫자를 이용해 퀵슬롯을 사용할때 쓸 함수입니다.


QuickSlot.cpp

초기화의 경우 Inventory와 마찬가지로 Type, Textuer등을 정해주는 방식으로 굴러갑니다. 저 Action함수는 이번 작업을 하면서 필요성에 의해 분리된 것입니다.


Slot.cpp

<Slot에 우클릭을 할경우 작동하던 코드>


Slot.cpp

<QuickSlot에서 부를 수 있도록 분리시켰다>


Slot.cpp

<훨씬 깔끔해진 코드>


BpQuickSlot(UMG 에디터)

위젯 블루프린트를 만들어 아까 C++로 작성한 QuickSlot를 부모로 상속받아서 위처럼 만들어 주시면 됩니다.


GameUI.h

<QuickSlot을 위한 자리를 만들어 주자>


이후에는 여타 다른 UI처럼 Player포인터를 채워주시고 Init함수를 호출해주기만 하면 됩니다.


BpGameUI(UMG 에디터)

<적당한 위치를 잡아준다>


<정상적으로 나오는 QuickSlot>


시간이 너무나 빠르게 흐르는거 같습니다. 앞으로 작성해야할 내용이 꽤나 많이 남아있는데 필자는 벌써 힘이 다 떨어져 버리고 말았습니다. 체력을 좀 회복하고 다음글에서 돌아오도록 하겠습니다.

Posted by 별수집가
,

안녕하세요 '언리얼로 만들어보는 RPG'의 필자입니다. 이번 글에서는 플레이어의 수치 예를들어 체력이나 경험치 등을 표시해줄 UI를 만들것입니다. 작업을 시작했을때 Image Widget을 두개 겹치고 TextBlock Widget을 사용하는등의 계획을 세웠었는데 보니까 이미 ProgressBar Widget을 언리얼 UMG에서 지원해 주고 있었습니다. 물론 그덕분에 조금 헤매게 되었지만 그건 넘어가도록 하겠습니다.


<구조를 고민하는 필자>


역시 작업에 들어가기전에 전체적인 밑그림을 그려보는게 좋습니다. 우선 ProgressBar Widget이 있는덕분에 필자가 만들 UI의 이름이 피치못하게 Gauge가 되겠습니다. 또 이 Gauge UI는 체력, 마력, 경험치, 로딩 등등 에서 다 쓰일것이기 때문에 Type을 이용해서 어떤 정보를 가져다가 사용할지 결정해줄 것 입니다. 이미지의 경우도 마찬가지여서 이미지를 결정해주는건 Gauge UI가 속해있는 부모 UI에서 할 것입니다.


대충 정리해보자면

1. 어떤 정보를 참조할지 결정할 Type 만들기

2. 사용될 이미지는 속한 부모 UI에서 정해주기

뭐 이 정도인거 같습니다. 그러면 바로 작업을 시작하도록 해보겠습니다.


InGameData.h

<Gauge UI의 타입들>


Gauge.h

Gauge UI도 Player의 정보를 가져다가 써야하기 때문에 CustomUI를 상속받습니다. 그리고 이런 저런 함수가 좀 많아보이는데 어려운것은 없고 보이는 대로 관련된 변수들을 설정해주는 함수들입니다. 필요한 Widget의 경우는 관련 수치를 보여줄 TextBlock, 이미지를 그려줄 ProgressBar 둘만 있으면 됩니다.


Gauge.cpp

<Init 구현코드 실질적 역할은 SetValue 함수가 한다>


Gauge.cpp

<선언해둔 Set함수들의 구현코드>

Type, Color, Texture는 Gauge UI가 속한 부모 UI에서 불러서 이용하는 함수입니다. Init함수에서 실질적인 역할을 하는 SetValue함수가 가장 중요하다고 볼 수 있습니다. 크게 어려운 코드는 없으니 천천히 보고 따라하시면 될 것 입니다.


BpGauge(UMG 에디터)

빌드를 하신다음 평소처럼 UserWidget 블루프린트를 만들어 아까 열심히 작성한 Gauge클래스를 상속합니다. 계층구조창에 보이시는 것 처럼 Bind 해줄 Widget 두개와 해당 위젯을 들고 있어줄 Overlay Widget을 만들어 주면 됩니다. 이외의 작업은 따로해주실건 없습니다.


GameUI.h

<무수히 많이 추가된 변수들>


숫자가 좀 많을 뿐이지 크게 걱정할건 없습니다. 전부다 Gauge UI의 변수와 설정해줄 데이터들입니다. 어떤 이미지를 사용할지 색은 어떻게 할 것인지 등을 정해주는게 끝입니다. 물론 이걸 보시는 여러분이 Gauge클래스 전방선언을 센스있게 하셨으리라 믿습니다.


GameUI.cpp

새로 생긴 변수들을 이용해서 초기화를 시켜줍니다. 인벤토리와 마찬가지로 Player포인터를 채워주고 Type, Color등 기본부분을 지정해주고 Init함수를 호출해주면 됩니다! 그리고 기쁜마음으로 빌드를 합시다.


BpGameUI(UMG 에디터)

이제 열심히 밑작업을 한대로 Gauge UI를 3개 만들어서 각각 Bind 할 이름으로 변경해 줍니다. 그리고 무수히 많이 추가되었던 색상과 어떤 이미지를 쓸지 설정해주면! 저희가 원하는 UI를 만들게 되는 것입니다.


<정상적으로 나오는 UI>


여기까지 만들었으니까 끝이면 참 좋겠지만 회복아이템을 이용했을때 갱신이 되어야 합니다. 저번처럼 디버그 메시지를 이용하는건 임시 조치였을 뿐이니까요.


InGameData.h

이번 작업을 하면서 문제가 좀 있었는데 CharacterAbility에서 수치가 변경되면 Player가 들고있는 UI에 접근해서 갱신을 해줘야 한다는 문제가 있었습니다. 결국 이를 해결하기 위해서 소유자 클래스 포인터를 들고 있게 했습니다. 그리고 해당 하는 소유자가 Player인지 확인하는 bool값과 소유자를 정해주는 함수도 만들었습니다.


InGameData.cpp

<SetOwner함수의 구현코드 별것 없다>


GameUI.h

새로운 함수를 하나 선언해줍니다. 해당하는 함수는 플레이어의 수치가 변동될때 UI를 갱신하기 위해 사용될 것입니다.


GameUI.cpp

현재로서는 HP회복물약만 존재하기 때문에 다른 경우는 비워두겠습니다. 저번에 만든 SetValue를 이용하면 되기때문에 크게 추가되는 코드는 없습니다.


InGameData.cpp

저번에 체력과 자원이 변경될때 디버그 메시지를 이용했지만 이번에는 바꿔서 Owner가 Player인지 확인하여 변경해주는 함수를 호출하도록 합니다. 여기까지 작업했으면 준비는 끝났습니다. 이제 빌드를 하고서 확인하러 가보도록 하겠습니다.


<정상적으로 갱신이 되는 체력바>


인벤토리에서 너무 오래걸려서 지레 겁먹었는데 생각보다 금방할 수 있었던 작업이었습니다. 이 다음에는 역시나 아직 남아있는 UI작업을 더 하려고 생각하고 있습니다. 아마 퀵슬롯 까지만 만들어 두고 스킬은 생각을 좀 해봐야 할 거 같습니다. 읽어주시느라 또 기다려주시느라 감사합니다.


<집요정에게 인정받은 글입니다>


Posted by 별수집가
,

안녕하세요 '언리얼로 만들어보는 RPG'의 필자입니다. 저번 글에서 Drag&Drop까지 처리했다면 참 좋겠지만 안타깝게도 분량조절에 실패하는 바람에 이렇게 뒤로 밀리게 되었습니다.


그런고로 오늘은 Drag&Drop을 해볼예정입니다만 필자도 처음 작업할때 감을 잡기가 어려워서 언리얼 공식문서의 튜토리얼을 참고했었습니다. 혹시 관심이 있는분이 있으실지 모르니 링크를 남겨두겠습니다. 별로 의미는 없는 여담이지만 필자의경우 멍청하게 영어버전을 찾아서 작업했었습니다. 여러분은 편하게 한글로 보고 하시면 됩니다.


언제나 새로운 작업을 시작할때는 대충이라도 전체적인 방식에 대한 그림을 그려두시는게 좋습니다. 필자의 경우에는 Slot UI에서 작업을 할 생각이며 실질적으로 Drag&Drop을 통해서 인벤토리의 정보가 바뀌는 부분은 PlayerBase쪽에서 할 것입니다. 그 중간에 Inventory UI에서도 거쳐가는 함수를 만들 예정입니다.


대충 정리해보자면

1. Drag를 시작할때 해당 Slot의 번호와 Type을 저장

2. Drop을 하게되면 PlayerBase에서 시작 Slot과 Drop Slot의 정보를 전달

3. 전달받은 정보를 기반으로 인벤토리를 Swap

이정도로 정리가 될것 같습니다.


Drag&Drop을 할때 관여되는 클래스가 있는데 해당 클래스는 (&)DragDropOperation입니다. 필자는 지금까지와 마찬가지로 해당 클래스를 상속받아서 Slot의 Drag&Drop용 클래스를 만들어서 처리해 줄 것입니다.


SlotDrag.h

필자는 상속을 받아서 SlotDrag라는 클래스를 만들었습니다. 막상 딱히 해줄건 없고 Drag를 시작한 Slot의 num과 해당 Slot의 타입을 기억해줍니다.


Slot.cpp

아이템 사용은 우클릭으로 했으니 Drag&Drop은 좌클릭으로 처리하겠습니다. 새롭게 추가된 코드에서 DetectDragIfPressed함수는 Blueprint/WidgetBlueprintLibrary.h를 추가해 줘야합니다. 해당 함수는 인자로 들어온 버튼이 눌린상태로 Drag를 했는지 잡아내 주는 함수입니다.


Slot.h

DragDropOperation을 이용하려면 위 이미지에 나오는 NativeOnDragDetected, Drop을 오버라이딩 해주셔야합니다. 그리고 Drag를 하는동안 마우스를 따라 보여줄 UI클래스를 에디터에서 설정해주도록 합니다.


Slot(UMG에디터)

빌드를 해보신다면 저렇게 클래스를 설정해줄 수 있게됩니다. 저희는 이미 Widget을 준비해둔 블루프린트 클래스가 필요하니 꼭 BpSlot으로 설정하셔야 합니다.


Slot.cpp

OnButtonDown함수에서 왼클릭을 했을때 DetectDragIfPressed함수를 통해서 OnDragDetected함수가 불러집니다. 이후에는 별거 없습니다. 아까 상속받아서 만든 SlotDrag클래스를 새로 만들어주고 Drag를 시작한 Slot의 num과 type을 넣어줍니다.


그 다음에 아까 에디터상에서 클래스를 설정해주어서 nullptr이 아니라면 새롭게 만들어 type, Player포인터, num을 채워주고 Refresh함수를 부릅니다. 해당함수는 Slot UI를 갱신해줍니다. 이렇게 새로 만든 Slot을 SlotDrag의 DefaultDragVisual에 대입해줍니다. 해당 변수는 Drag를 하는동안 따라다닐 UI를 가지는 변수입니다.


Slot.cpp

Drag도중 마우스를 떼면 OnDrop함수가 호출이 됩니다. 물론 반드시 호출되는건 아니고 확인해본 결과 OnDragDetected함수의 인자 OutOperation가 nullptr이라면 Drop은 불러지지 않습니다.


Slot에 Drop이 된다면 PlayerBase에 만들어둔 DraggingSwap함수로 두 Slot의 정보를 전달해 줍니다. 방식이 좀 왔다갔다 하는편이라서 한눈에 잘 안들어 오실수도 있지만 양해 부탁드리겠습니다.


PlayerBase.h

<새롭게 추가한 함수 DraggingSwap, SwapInven>

PlayerBase.cpp

좀 번거롭기는 하지만 Drag&Drop을 반드시 인벤토리에서만 하리라는 법은 없기때문에 거쳐가는 함수를 하나 만들었습니다. DraggingSwap함수에서는 들어온 두 슬롯의 정보를 기반으로 어떤 Swap함수를 부를지 정해줍니다. 지금당장은 인벤토리에서만 Swap하는걸 작업하기 떄문에 그거말고는 없습니다.


Slot의 type이 둘다 Item이라면 SwapInven함수에 해당하는 번호들을 전달해줍니다. 이후 인벤토리 배열에서 해당 인덱스들을 Swap해주고 Slot UI를 갱신하기 위해서 GameUIWidget에 만든 RefreshInventory함수를 불러줍니다.


GameUI.cpp


Inventory.cpp

이후에는 위의 두 함수가 차례대로 불러지면서 Slot UI의 이미지를 갱신해줍니다. 그리고 빌드를 돌려서 테스트 할 일만 남았군요 이제


<길고긴 고생끝에 성공한 Drag&Drop>


필자는 이게 성공했을때 너무나 감격했었습니다. DragDropOperation에 대한 정보는 너무 없었고 이걸 사용하는 예제도 찾지 못헀었기때문에 순전히 필자 스스로의 힘으로 만들어야 했었기 때문입니다. 지금 읽고 계시는 여러분도 직접 코드를 작성해서 돌아가는걸 보게된다면 똑같은 기분은 아닐지언정 상당히 뿌듯해지지 않을까 감히 예상해봅니다.


이번에도 글이 좀 길어진것 같지만 읽어주셔서 감사합니다. 아마 다음글은 멀지 않은날에 쓸거 같고 HP나 경험치를 보여주는 Bar를 만들 예정입니다. 모두들 이번주의 시작을 기분좋게 마무리 하셨기 바랍니다.

Posted by 별수집가
,

안녕하세요. '언리얼로 만들어보는 RPG'의 필자입니다. 이번에는 인벤토리의 아이템을 사용하고 가능하면 Drag&Drop까지 해볼 생각입니다. 물론 현재 캐릭터의 상태에 대한 표시를 해줄 UI 예를 들어 HPBar 같은게 존재하지 않음으로 간단하게 디버그메세지로 확인할것입니다.


이번글을 본격적으로 시작하기전에 해야할 밑작업이 있습니다. 우선 아이템 정보를 가진 구조체의 정의와 이용방식을 좀 바꿔야 합니다. 인벤토리 구현을 처음 시작할때 상속을 통한 가상함수 오버라이딩을 이용하려고 했었지만 언리얼에서는 구조체 포인터는 UPROPERTY 시스템에서 관리해주지 않기 때문입니다.


물론 구조체의 상속을 통한 가상함수 오버라이딩을 사용할 수 없는것은 아니지만 필자가 원하는 방식은 아니기 때문에 설명은 생략하도록 하겠습니다. 혹시 궁금하신분이 있으시다면 나중에 기회를 봐서 작성해 보도록 하겠습니다.


PlayerBase.h

<강한 거부의사를 밝히는 언리얼>


이러한 이유로 인해서 필자는 많은 고민을 했었는데 초기에 생각했었던 에디터상에서 정보를 조절해줄 수 있는걸 포기하고 싶지 않았습니다. UObject를 상속받아서 만들어보기도 하고 SceneComponent를 상속받아서 32개의 컴포넌트를 달아주는 미친짓도 해보았습니다. 결국 선택한 방법은 구조체 안에 또 구조체를 넣어버리는 몰상식한 일명 '무한 구조체 패턴'을 선택한 것입니다.


InGameData.h

<포션의 정보를 담을 구조체>

InGameData.h

기존의 ItemData에서 가상함수를 없애버렸습니다. 그리고 포션정보를 담아줄 FPotionData 구조체를 변수로 하나 들고있게 해주었습니다. 필자 또한 알고있습니다 이게 얼마나 끔찍한 행동인지 하지만 어쩔 수 없는 선택이라고 위안을 삼고 있습니다.


이제부터는 아이템이 사용될경우 어떻게 해서 플레이어의 상태를 변경해줄 것인지에 대해서 코드를 작성할 시간입니다.


InGameData.cpp

저번과 코드가 좀 달라진 부분은 스위치문이 생겼다는것 정도입니다. 필자가 선택한 절충안은 보기에 좀 별로이긴 하지만 그래도 나름 만족하고 있습니다.


InGameData.cpp

아이템을 사용할때 Player포인터를 받아와서 이렇게 Ability에 접근하여 Ability에서 처리하도록 만들었습니다. 이게 객체지향적인 프로그래밍이라할 수 있는지는 모르겠지만 필자 생각에는 그냥 그렇게 하는게 좋을것 같았습니다.


InGameData.h

CharacterAbility에도 꽤나 많은 변화가 생겼습니다. 저번에 대충 만들었던거에 비하면 필요한 부분들을 다 채워넣었다고 보시면됩니다. 그리고 이번에 가장 중요한 ChangeHP-Resource 함수를 선언해줬습니다.


InGameData.cpp

이로써 기본적인 준비가 드디어 끝났습니다. 아니 뭐 별거 하지도 않은거 같은데 시간은 왜이리 빨리 가는지알 수가 없는 노릇입니다. 이제 할건 Slot UI에서 마우스 입력을 처리해서 아이템을 사용하기만 하면 됩니다.


회복하기전의 체력은 빨간색으로 회복하고난 다음의 체력은 초록색으로 표시하기로 하고 작업을 하기전에 빌드를 한번 하여 에디터에서는 어떻게 되었는지 한번 확인해봅시다.


BpPlayer(블루프린트)

구조체 안에 구조체가 있는 매우 기괴하지만 원하는 그림이 완성되었습니다.


Slot.h

이미지에 있는 NativeOnMouseButtonDown함수는 실제로 에디터상에서도 오버라이딩이 가능한 함수중 하나입니다. 필자도 자세한건 사실 잘 모르는데 오버라이딩 하려고 원본 함수를 찾아가보시면 해당 함수 안에서 에디터에서 부르는 OnMouseButtonDown함수를 호출하는걸 보실 수 있습니다.


언리얼에서는 이런식으로 에디터에서도 C++상에서도 오버라이딩 하여서 원하는 방식으로 구현할 수 있는 기회를 제공해주는것 같습니다. 물론 필자에게는 매우 좋은 이야기입니다.


Slot.cpp

실질적인 행동을 해주는 코드입니다. 필자의 경우 버튼으로도 해보았었지만 버튼보다는 이렇게 마우스이벤트를 오버라이딩 해서 사용하는게 더 좋은것 같습니다. 이로써 포션뿐이긴 하지만 아이템을 사용하기 위한 작업은 끝났습니다. 이제 빌드를 하시고 직접 테스트 하기만 하면 끝입니다!


<정상적으로 작동하고 있다>


움짤의 화질이 너무 좋지 않은것 같아서 따로 사진을 찍어서 보여드립니다. Drag&Drop까지 하고싶었는데 변경된 부분이 생각보다 많아서 시간이 꽤나 걸렸습니다. 필자는 이만 여기서 물러가고 다음 포스팅때 Drag&Drop을 하도록 하겠습니다. 그 다음에는 체력과 마력, 경험치 등을 보여주는 UI를 만들어볼 예정입니다.

Posted by 별수집가
,

안녕하세요. '언리얼로 만들어보는 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>


자 여기까지 오시면 기본적인 작업이 드디어 끝난것입니다. 앞으로 남은내용이 얼마 없긴 하지만 필자의 체력은 더이상 남아있지 않군요. 정말로 하얗게 불태워 버렸습니다. 이 포스팅은 여기서 마무리하고 다음에 뵙도록 하겠습니다.


<불타버린 필자>

Posted by 별수집가
,

안녕하세요 '언리얼로 만들어보는 RPG'의 필자입니다. 오늘은 저번 포스팅때 확인했던 문제부터 시작을 할 예정입니다.


기억하실지 모르겠지만 저번 포스팅때 C++상에서 코드로 위젯을 생성하면 UMG에디터에서 상호작용이 아예 안되는 문제가 있었지요. 그 문제를 해결하기 위해서 좀 찾다보니 UPROPERTY meta 설정에 (&)BindWidget 설정이 있는걸 알게 됐습니다.


UISlot.h

<BindWidget 설정을 해준 모습>


이제 UMG에디터에서 위젯을 생성한후에 바인딩을 해줄것이기 때문에 저번 포스팅때 오버라이딩한 함수는 지워버렸습니다. 이제 더이상 필요가 없어지면 사라지게되는건 숙명이기때문에 어쩔 수 없습니다.


BpUISlot(UMG 에디터)

<바인딩 해달라고 오류를 발생시키는 에디터>


빌드가 성공적으로 이루어지고 나면 에디터상에서 위젯을 바인딩 해달라고 이렇게 오류를 띄워줍니다. 왼쪽이 변수의 이름이고 오른쪽이 위젯의 타입이니 그대로 맞춰서 위젯을 생성해주시면 됩니다.


BpUISlot(UMG 에디터)

1번 박스를 보시면 필자의 경우 Grid Panel아래에 이미지와 텍스트를 넣었는데 보시는 분들은 원하시는걸 골라서 넣으셔도 됩니다. 튜토리얼영상이나 다른분들이 Slot UI를 만들때는 보통 버튼과 이미지를 이용하시는데 필자는 버튼을 이용하지 않을 계획이라 제외했습니다. 이미지의 경우 자식위젯을 가질 수 없기도 해서 저런식으로 배치를 하게 되었습니다.


2번 박스에 Custom이라 설정되어있는걸 보실 수 있는데 UI의 전체적 크기에 대한 설정을 할 수 있는걸로 보입니다. 필자는 64x64크기로 만들어주었습니다.


BpUISlot(UMG 에디터)

빨간색으로 테두리 쳐있는 부분을 집중적으로 보시면 되며 좌측부터 GridPanel, Image, Text의 디테일 설정입니다. 필자도 설정을 정확히 파악하고 있는것은 아니어서 이렇다고 확실히 설명해 드릴 수 없는부분에 심심한 유감을 표하는 바입니다. 그저 GridPanel에서 설정한 행과 열의 채우기에 따라서 안에 들어가있는 위젯들이 행과 열에 따라서 다르게 채워지는걸로 알고있습니다.


Slot UI는 지금 이정도면 더 필요한게 없을것 같습니다. 이 Slot을 이용해서 인벤토리 UI를 제작하도록 해보겠습니다.


BpInventory(UMG 에디터)

영상이 아닌 사진으로만 설명하려니까 쉽지는 않은것 같습니다. 인벤토리 UI의 경우 Slot을 가지고 있어야 하기 때문에 Uniform GridPanel을 사용했습니다. 저렇게 아름답게 정리되는건 다 순수한 노가다의 힘으로 이루어진것이란건 비밀아닌 비밀입니다.


BpInventory(UMG 에디터)

인벤토리UI의 GridPanel, Text, Uniform GridPanel의 디테일 설정입니다. 필자와 다른방식을 취하셔도 되나 참고하시라고 올려놓습니다.


BpInventory(UMG 에디터)

<슬롯중 하나>


필자의 경우 인벤토리의 크기를 32로 지정해줬습니다. 보시는분들은 더 크게 혹은 더 작게 해주셔도 되는데 이부분에서 좀 고역인게 디테일 설정창을 보시면 Row, Column을 통해서 Uniform GridPanel에서의 위치를 정해주실수 있고 Slotnum을 설정해 주시면 됩니다. 이걸 32개면 다 일일이 설정을 해주셔야 한다는게 좀 고통스러울 뿐입니다.


이제 화면에 잘 나오는지를 한번 확인해볼 시간입니다. 튜토리얼 영상을 보셨다면 아시겠지만 보통 인벤토리나 장비창 같은경우 메인UI에 자식으로 붙여 앵커위치를 조절해서 사용하더군요 그걸 위해서 저희도 메인UI를 하나 만들어줍니다.


BpGameUI(UMG 에디터)

<인벤토리를 넣어주고 우측중앙에 앵커를 설정한 모습>


BpPlayer(블루프린트 에디터)

<BpGameUI를 생성하고 화면에 추가>


가볍게 테스트를 위해서 확인만 하는것이기 때문에 블루프린트를 통해서 작업했습니다.


<화면에 잘 나타나는 인벤토리>


화면에 잘 뜨는것까지는 기분이 참 좋습니다만, 이제 해결해야할 부분은 PlayerBase에 접근해서 가지고있는 인벤토리 배열의 정보를 참조하여서 UI를 채워주는일만 남았습니다. 지금까지는 우선 화면에 띄우는걸 목적으로 삼았지만 앞으로는 UI와 PlayerBase간의 정보 교환을 어떻게 처리할지에 대해서도 생각을 하고 작업을 해야할것 같습니다.


<이게 뭐냐구요? 이번 포스팅의 끝을 알리는겁니다>


어느덧 벌써 2019년의 1월이 지나고 설날이 코앞으로 다가왔습니다. 여러분 모두 즐거운 명절 되시길 바라며 이번 포스팅은 여기까지 해야할거 같습니다.


이번에도 그렇지만 그리 많은 내용을 담지 않았는데도 불구하고 시간이 훌쩍 지나고 체력도 벌써 방전이군요. 운동을 좀 꾸준히해서 체력을 길러야하는데 사람일이란게 참 마음대로 되는게 없습니다. 다음에는 전체적인 UI의 구성을 어찌할지와 인벤토리의 정보를 어떻게 갱신할지에 대해서 작성하도록 하겠습니다.

Posted by 별수집가
,

벌써 이 시리즈의 포스팅을 8번째 하는것이지만 언제나 그렇듯이 들어가는 말을 적는게 참 쉽지 않은것 같습니다. 길고 긴 캐릭터 구현이 끝났으니까 다음목표로 인벤토리를 잡았습니다. 크게 어렵지 않으리라 생각했었지만 예상치 못한 복병이 꽤나 있었다는게 뼈아픈 추억이지만 말입니다.


우선 본격적으로 시작하기 전에 UI작업의 갈피를 잡을수 있게 해줬던 튜토리얼 영상입니다. 언리얼 공식 유튜브 채널에 있는것이니 보시면서 참고하실 수도 있으실겁니다. 물론 필자의 경우 C++로 작업하기 때문에 에디터 상에서 UI작업을 어떻게 해야하는지 정도만 참고했습니다.


언제나 그렇듯이 전반적인 작업에 대한 그림을 그려두고 시작을 해야 나중에 할 삽질이 줄어듭니다. 보통 인벤토리를 구현하시는 분들을 보면 아이템에 대한 정보를 UObject를 상속받아서 클래스 형태로 만드는 경우가 많았는데 필자의 경우에는 원하는 바와 좀 달라서 구조체를 이용하기로 했습니다.


구조체를 이용하기로 한 이유가 몇가지 존재하는데

1. 아이템에 대한 정보를 가지고만 있으면 되기 때문에 클래스를 상속받아 쓰지않는 함수나 변수를 가지고 있는것이 마음에 들지 않았습니다.

2. 에디터상에서 아이템 정보를 수정해서 직접 확인할 수 있어야 합니다.


써놓고 보니 별 대단한 이유는 아니었습니다. 그래도 하고싶은대로 해야하지 않겠습니까?

그런 관계로 필자는 구조체를 이용하기로 하였고 상속구조를 통한 다형성을 이용하려고 합니다.


<어떤 타입의 아이템인지 알려줄 Enum>

대충 필요해보이는 것들은 다 집어넣어봤습니다. 이름, UI에 그려질 텍스쳐, 아이템 타입, 몇개를 가지고 있는지, 그리고 가상함수 두개를 만들어서 상속받은 쪽에서 오버라이딩 할 예정입니다. Clear함수는 말그대로정보를 초기화 시켜버리는 용도입니다.


이제 PlayerBase로 가서 FItemData형 배열을 선언해줍니다. 이후에는 에디터에서 잘 작동하는지 한번 확인을 해봐야겠네요


<필자가 원하는대로 된것을 확인할 수 있다>


이제 아이템 데이터를 기반으로 화면에 띄워주는 작업을 하면 되겠군요! 언리얼에서 UI작업을 해본적이 없기에 튜토리얼 영상을 따라서 열심히 만들어 봅니다. 하지만 필자는 C++로 만들어보고 싶어졌습니다.


UserWidget클래스를 상속받아서 내부에 필요한것들을 채워넣어줍니다. Slotnum 변수는 PlayerBase에 있는 인벤토리 배열의 몇번째 인덱스와 연결되어있는지 설정해줄 변수입니다. 나머지의 용도는 보이는 그대로이기 때문에 잘 아시리라 생각하지만 ESlotType이라는 Enum에 대해서 궁금해 하실거 같습니다.


슬롯이라는 UI를 인벤토리, 장비창, 퀵슬롯 등에서도 재사용할 수 있도록 타입에 따른 참조위치를 조절하려는 목적으로 만들었습니다. 언급된 3가지 정도는 만들어야 그럴싸 해보이지 않을까 생각해서 하게 된 것입니다.


이제 문제는 어떻게 C++로 UI에 필요한 위젯들을 생성하느냐였는데 이부분은 열심히 구글신을 통해 검색해서 실마리를 찾게 되었습니다.

https://answers.unrealengine.com/questions/177938/create-umg-button-from-c.html

위의 링크에서 방법을 알아낸 덕분에 저는 순조롭게 코드를 작성하기 시작했습니다.


빨간색 밑줄이 생기기는 하지만 문제없이 돌아가니 걱정하지 않으셔도 됩니다. 라고 말씀드리고 싶었지만 크나큰 문제가 있었습니다. 바로 에디터가 터져버린다는 것입니다. 이유를 예상하건데 위젯을 만들때 생성자 부분에서 만들어내기때문에 터진것 같았습니다.


부모클래스를 뒤져보면서 오버라이딩 할만한 함수를 찾다가 딱 맞아떨어지는듯한 함수를 찾게되었습니다. 이름부터가 초기화에 관련되어있는것이었는데 이거다! 싶은생각에 바로 오버라이딩을 하여서 빌드를 돌려봤더니 터지지 않았습니다.


기쁜 마음으로 여태껏 작성한 클래스를 기반으로 블루프린트를 만들었는데 여기서또다시 한번 막히게 됐습니다. 이유인 즉슨


<필자가 만든 클래스 기반의 블루프린트>

<언리얼의 UMG 에디터>


바로 UMG에디터를 통해서 수정을 할 수 없게되었다는것입니다. 이것도 언리얼을 통해서 UI작업을 해본적이 없다보니까 생긴 문제점중 하나였는데 검색해보니까 위젯 블루프린트를 먼저 생성한 후에 확장을 시켜야 한다고 합니다. 확장이 무슨소리인가 했더니 부모 클래스를 바꿔줄 수 있다는 소리였는데, 이 내용은

https://wiki.unrealengine.com/Extend_UserWidget_for_UMG_Widgets

에서 확인하실 수 있습니다.


<우여곡절 끝에 만들어진 UI>


이렇게 겨우겨우 성공했나 싶었는데 세상에 맙소사 C++로 만들어낸 위젯의 경우 선택도 수정도 무엇하나 되는게 없었습니다. 분명히 화면에 있는데 클릭도 안되고 뭔가 상호작용을 할 수 없었습니다. 이후에도 몇번의 검색과 공식카페에 질문을 통해서 알아낸 바로는 C++에서 만들어낸 위젯은 에디터상에서 조절하는 방법을 아는사람이 없었고 현재로써는 코드로 만들어 사용하는건 전혀 불필요한 작업이었다는것으로 판명났습니다.


<필자의 기분을 매우 잘 나타내는 그림>


별로 작성한것도 없는것 같은데 벌써 시간이 많이 흘렀습니다. 네 그렇습니다 이번 포스팅은 여기까지 마무리 하고서 힘을 재충전하고 다음포스팅때 돌아오도록 하겠습니다.

Posted by 별수집가
,

안녕하세요 여러분 작업에 정신이 팔려서 정작 중요한 작업일지에 소홀해져버린 필자가 돌아왔습니다.


마지막 포스팅을 두어번 정도 다시 읽고 나서야 어디서 부터 시작을 해야할지 감을 잡게 되었네요. 저번 포스팅에서는 필자를 매우 괴롭혔던 경사면 처리를 해결했었군요. 이번 포스팅에서는 캐릭터의 이동을 마지막으로 마무리할 예정입니다.


카메라가 보는 방향을 기준으로 8방향 이동이 가능하게 하는게 기본적인 계획입니다. 특별히 크게 어려울 거라는 생각은 안하고 있었습니다만..

<누구나 그럴싸한 계획은 가지고 있다>


언제나 모두에게 적용이되는 훌륭한 명언을 만들어낸 타이슨 선생님의 말씀처럼 계획대로 굴러갈 수는 없는 노릇이지요.


우선 좀더 구체적으로 계획을 잡아봐야 합니다.

1. InputVector에 속도를 곱해서 넣지않고 방향에 대한 값만 집어넣어줘야합니다.

2. 들어온 InputVector를 기반으로 카메라의 방향을 이용해서 이동방향 정해주기


당장 생각나는건 이 두가지 정도인거 같습니다. 그러면 바로 작업으로 들어가도록 하는게 좋겠네요


저번과 다르게 이동함수에서 이동속도롤 곱하지 않습니다.

왜냐하면 들어온 InputVector의 값을 참고해서 카메라를 기준으로 방향을 잡아서 이동시킬것이기 때문입니다.


위 사진의 빨간 밑줄쳐진 부분을 보시면 아시다시피 SimulateGravity함수의 매개변수에서 FRotator를 뺐습니다. 또한 Player의 RootComponent에서 가져오던 Rotator를 Camera로부터 가져오도록 변경을 했습니다.


저번코드에서는 Rotator를 받아서 이용했지만 카메라의 방향을 기준으로 삼아야 하기때문에 이동시에 현재 Rotator를 저장해서 이용하기로 했습니다.

<이동할 당시의 카메라 방향을 저장해줄 CurrentRotation 변수>


다음으로 살펴볼 함수는 moveSmooth함수입니다.


저번이랑 다르게 내용이 좀 늘어났는데, 들어온 InputVector에서 X축과 Y축의 값을 이용하여 현재 캐릭터가 어느 방향을 향해야 하는지 구합니다. 그렇게 구한 Delta벡터를 정규화 시켜서 방향벡터로 만들어내고 이를 플레이어의 이동속도와 DeltaSecond를 곱해서 Velocity에 대입합니다.


그렇게 구한 Velocity의 회전정보를 CurrentRotation에 넣어주고 MoveAlongFloor 함수를 실행시키면!


<필자의 의도대로 잘 이동하는 모습>


하지만 역시나 하나를 해결하면 또다른 문제가 터지기 마련입니다. 기분 좋게 언덕을 올라가는 테스트를 하다가 큰 문제점을 몇가지 발견했는데요. 옆을보고 언덕을 올라간다던가... 너무 높은 경사를 꾸역꾸역 올라간다던가 하는 문제들이었습니다.


<딱봐도 높은 언덕을 요상하게 올라가는 캐릭터>


이 문제도 해결하기 위해서 하루정도 시간이 들어갔었는데 찾아보니 옆을보고 올라가는경우에는 SlideAlongSurface함수에서 Rotator를 적용하는게 여전히 RootComponent함수 에서 가져오기 때문이었고 높은곳을 꾸역꾸역 올라가는건 MoveAlongFloor함수 에서 충돌부분을 뭉뚱그려 처리해서 였습니다.


미끄러지는 처리를 해줄때 CurrentRotation을 집어넣어주면 어색하게 다른방향을 바라보면서 언덕을 올라가거나 벽을 따라 미끄러지는 경우를 해결해줄 수 있습니다.


빨간색으로 테두리를 친 부분이 변경된 부분입니다.


이전 작업에서는 충돌하거나 뚫었을경우에 둘다 미끄러지도록 작성했지만 이때문에 경사가 높아도 그냥 올라갈 수 있었던 것입니다. 이제는 뚫었을 경우에 올라갈 수 없는 벽같은 물체를 만난것으로 가정하고 미끄러지게 하고 충돌했을경우에는 충돌 대상을 확인하여 현재 밟고있는 바닥일 경우 경사가 걸어 올라갈 수 있는 경사인지 확인후 경사벡터를 다시 구해 이동시켰습니다.


<정상적으로 잘 올라가고 경사가 가파르면 막힌다>


이로써 캐릭터의 기본적인 구현이 끝났습니다.

다음 포스팅 부터는 캐릭터의 인벤토리를 구성하고 UI를 만들어서 화면에 띄우는걸 해보도록 하겠습니다. 


개인적인 여담이지만 UI작업을 해본적이 별로 없어서 많이 헤매고 정보를 찾는데 시간도 오래걸렸었습니다. 차근차근 짚어가면서 포스팅을 할테니 시간이 좀 걸릴 수 있다라는 변명입니다


아무튼 기다려 주신분들이 있을지 모르겠으나 긴시간 기다려주셔서 감사합니다.

Posted by 별수집가
,

진도나간건 별것도 없는데 벌써 캐릭터 하나 만드는동안 넘버링이 6까지 와버렸습니다.


저번 글에서 깜빡하고 지나쳐 버린게 하나 있었는데 글을 읽으신 분들이 눈치 채셨을지는 모르지만 제가 찝찝하니까 짚고 넘어가야 할거 같네요


저기 빨간색으로 밑줄 쳐져있는 WalkableFloorZ라는 변수와 그에 관련된 다른 변수, 함수들에 관한 이야기 입니다.


우선 저 변수는 언리얼에서 제공하는 CharacterMovement에서 설정하는 값이랑 동일한 역할을 하는 용도이며 저걸 가져옴으로써 3개의 함수를 같이 가져오게 되었습니다. ISWalkable함수를 쓰기위해서 가져온 변수가 짐을 많이 들고오게 된것이지요


두 변수의 차이점은 Angle은 저희가 흔히쓰는 360도를 의미하는 디그리(Degree) 값을 저장하고 Z는 라디안(Radian)값을 저장하는 변수입니다. 보시는것처럼 Angle의 UPROPERTY의 meta설정 부분을 통해서 에디터 내에 해당 변수의 최소, 최대값을 고정시킬수가 있음을 확인하실수 있습니다.


이번에는 함수 두개를 한번에 올려드렸는데 이 두개의 함수는 서로를 보완해주는 역할이라서 그렇습니다. 내용을 보시면 어려운것은 없으니 긴 설명은 안하고 넘어가겠습니다.


가장 중요한 함수를 소개할 시간이 되었습니다. 위 함수는 보시는 바와같이 전처리기if 조건을 통해서 에디터 상에서만 작동을 하도록 조건을 걸어두었습니다.


저 함수는 에디터에서 변수를 조정할떄 불러지게되는데 만일 변경되는 변수가 에디터에서는 접근할수 없는 다른 변수와 연계가 되어야 하거나 영향을 끼친다는 가정하에 저런식으로 오버라이딩 해서 작성을 해주시면 됩니다.


실제로 필자또한 이번 Movement클래스를 구현하면서 언리얼 소스코드를 열심히 보지않았다면 알수 없었던 부분이었습니다. 내용적으로 어려운 부분은 없으나 이러한 기능이 있다는것을 알고계신다면 코드상에서 작동할 변수이나 에디터의 영향을 받아야 하는 경우에 하나의 방법이 될 수 있다 생각해서 넘어갈수가 없었습니다.


이걸로 저번 포스팅에서 잊어먹고 설명하는걸 까먹었던걸 해결했으니 남아있는 이동 부분에 대해서 설명을 계속 이어나가보도록 하겠습니다.


<미련이 남아 덕지덕지 달아둔 주석들>


코드가 좀 깨끗하지는 않지만 나름의 고군분투가 있었다고 생각해 주시면 좋을거 같습니다. 캐릭터의 현재 이동상태를 이용해 중력을 적용하고 CurrentFloor도 갱신해 주었으니 이제 남은건 전진 또 전진뿐 입니다.

언리얼 소스코드의 MoveSmooth에서는 필자가 올린것과는 다르게 실제로 더 많은 내용이 들어가 있습니다. 하지만 필자의 경우 필요에 맞춰서 간략화 시키는게 목표였기 때문에 대폭 줄이고 약간의 여지를 주석으로 남겨두었습니다.


우선 이동값을 계산하여서 입력이 없으면 돌아가고 있을때 MoveAlongFloor함수를 실행시키는 간단한 문지기 역할을 합니다.


함수의 시작과 동시에 CurrentFloor를 확인해서 걸어다닐수 있는 지형이 아니라면 코드를 실행하지 않고 return시킵니다. 만일 걸어다닐수 있다면 현재 밟고있는 지형에 부딪히지 않고 매끄럽게 지나갈수 있도록 이동벡터를 생성해주어야 하는데 그 역할을 ComputeGroundMovementDelta함수에서 해줍니다. 실질적으로 지형의 높낮이에 맞춰 부드럽게 이동할수 있게 해주는 가장 핵심적인 코드는 위 함수에 있기 때문에 설명을 시작하도록 하겠습니다.


함수로 들어오는 인자들은 각각 지형에 관계없이 진행할 방향 벡터, 중력체크당시에 구한 CurrentFloor의 Hit결과값과 LineTrace를 통한 결과인지를 받습니다. 보시면 if문에 IsWalkable함수를 주석쳐놨는데 필자의 판단상 굳이 저기서까지 확인할 필요는 없을것 같아서 패스했습니다.


충돌한 지점의 노말값과  캐릭터의 전징방향을 통해서 지형에 충돌하지 않는 방향으로 이동벡터를 생성해내고 그걸 반환시키는 코드입니다. 막상 확인하고 보니까 좀 허무한 기분이 들기도 했지만 수학에 대해서 기본적인 수준과 응용력을 지니신 분들이라면 막히지 않고 잘 하셨을거 같습니다. 필자는 수학에 상당히 약한편이다 보니 이해하는데 시간이 꽤나 오래걸렸지만 말이지요


그다음엔 별것 없습니다. 구한 벡터를 통해서 이동을 시켜보고 만일 부딪힌다면 일단 지형은 부딪히지 않도록 조치를 취해두었으니 장애물일것이 분명하기 때문에 미끄러지게 처리를 해주면 됩니다.


막상 정리하고보니까 글이 별로 길지도 않고 이렇게 나눠야 할 필요가 있었나 싶기도 하지만 믿어주세요. 어제는 너무나 피곤해서 더이상 글을 쓸수가 없는 상황이었습니다. 이제 필자는 다시 작업을 하다가 정리할 만한 내용이 생기면 들고오도록 하겠습니다.


Posted by 별수집가
,