
지금까지 인벤토리에 아이템을 띄우는 것 까지 완성했다.
아이템을 인벤토리에서 옮기고, 땅에 버리고, 회전하는 것만 완성하면 된다.
이번 튜토리얼을 마지막으로 위에 기술한 모든 기능을 구현하며 마무리 될 예정이다.


Handled는 해당 노드가 실행될 경우, 이벤트 시스템이 해당 이벤트가 이미 처리되었다고 판단하고, 더 이상
다른 액터나 위젯에 이 이벤트를 전달하지 않는다.
예를 들어, 버튼 위젯을 클릭했는데 그 아래에 있는 다른 위젯이 반응하지 않도록 만들 수 있다.
버튼 위젯의 OnClicked 이벤트가 Handled를 반환하면, 이벤트 체인이 거기서 멈추기 때문이다.
Handled 와 Unhandled의 차이
WBP_InventoryGrid 로 가서 GridBorder에서 On Mouse Button Down 이벤트에 또 바인딩을 해주도록 하자.


인벤토리 보더와 마찬가지로 다음과 같이 Handled를 연결해주면 더 이상 인벤토리를 열었을 때 케릭터의 시점이 움직이지 않는다.
이제 아이템에 마우스를 호버링 했을 때 하얀색 배경이 떠서 가시성을 높혀주도록 할 예정이다.
WBP_Item으로 가서 On Mouse Enter/Leave 이벤트를 오버라이딩 하도록 한다.

다음과 같이 해당 이벤트가 실행됐을 때 배경색이 변하도록 해주었다.
마우스가 WBP_Item에 호버링 되었을 때는 BackGroundBorder가 하얀색으로, 아니라면 기존 Brush Color를 갖는다.

테스트 해보면 다음과 같이 마우스를 올리면 배경이 하얀색으로 바뀌는걸 확인 할 수 있다.
다음은 드래그앤드롭 이벤트를 바인딩해보자.
WBP_Item에서 On Drag Detected 함수를 오버라이딩 하도록 한다.


타입: 일반적으로 UObject의 자식 클래스(예: BP Item Object) 참조가 사용됩니다.
특별한 이유로 드래깅 할 때 다른 이미지를 원한다면 다른 위젯을 생성해서 해당 노드에 연결해서 전달해주면 됩니다.
드래깅하면서 인벤토리에서는 지워져야 하므로, Removed 이벤트를 호출하고, Remove From Parent로 마찬가지로 위젯에서도 삭제해줍니다.
다음은 On Mouse Button Down 함수를 오버라이딩 합니다.

여기까지 잘 마무리됐다면, 이제 인벤토리에서 아이템을 드래깅 할 수 있습니다.

다만, 아직 드롭기능을 구현하지 않았으므로 아이템을 드랍하면 영원히 사라져버립니다.
드랍기능에서 우리가 구현해야 할 부분은, 인벤토리 바깥 부근에서 떨어트리면 아이템이 땅에 떨어지며 스폰하고
인벤토리 내부 다른 위치에 놓을 경우 아이템의 위치가 변경되어야 합니다.
우선 아이템이 땅에 버려지는 부분부터 구현해보도록 합시다.
WBP_Inventory로 가서 OnDrop 함수를 오버라이딩 합니다.


Get Payload(ItemObject)를 가져와서 Casting 해주도록 합니다.
여기에서 아이템을 떨어트리는 기능을 만들 수 있지만, 위젯 내부에 해당 기능을 구현하는 것은 옳은 방법이 아닐겁니다.
그러므로 GameState를 만들어서 그 안에 아이템을 떨어트리고 생성하는 기능을 만들고 호출하도록 합니다.
이미 있던 GameState를 써도 상관없지만, 새로 만들 예정이라면 'GameStateBase'를 상속받아서
자신만의 GameState를 생성하도록 합니다.



*다음과 같이 입력받는 Actor로 부터 위치와 노멀라이즈된 Vector를 이용해서 플레이어의 1.5m 앞의 좌표를 구해
Spawn Location (Local)에 저장해주도록 합시다.


LineTrace 이후에 바닥에 실제 아이템이 Spawn하도록 합니다.
이를 위해서는 스폰할 실제 아이템 클래스가 필요하므로 BP_ItemObejct로 돌아가서 ItemClass를 반환하는 Getter 함수를 만들어줍니다.

GetItemClass 함수를 만들어주었다.
해당 함수를 만들고 GameState에서 위와 같이 SpawnActor에 전달해주게 되면 성공적으로 해당 함수가 호출되면
플레이어의 앞에 아이템이 스폰되는 기능을 구현해냈다.

실제로 테스트 해보면..

InventoryComponent로 돌아가서 RemoveItem 함수를 생성한다.

ItemObject 파라미터를 받아와서, 해당 참조가 유효하다면 Items를 순회하며,

인풋값과 비교하여 같을 경우, Items 배열을 초기화해준다. 또한 IsDirty를 true로 바꿔주어 인벤토리에 변화가 생겨 Refresh 함수가 호출되도록 해준다.
Drop 기능이 완벽하게 작동한다면 이제 인벤토리 내부에서의 이동을 구현해보도록 하자.
WBP_InventoryGrid로 이동해서 OnDrop 함수를 오버라이딩한다.
오버라이딩 후 몇 가지 필요한 함수를 만들어야 하는데, 우선 GetPayload 함수를 생성하도록 한다.

이 GetPayload 함수는 드롭 이벤트가 발생했을 때
'내가 받은 데이터가 인벤토리 아이템 데이터가 맞으며, 안전하게 사용할 수 있는가?'를 검증하고
데이터를 꺼내주는 게이트 키퍼(Gatekeeper) 역할 때문에 필수적입니다.
OnDrop으로 이동해서 해당 함수를 이용해봅시다.

GetPayload 함수를 이용해서 드랍한 아이템의 유효성을 확인했다면 인벤토리 내부에 아이템을 드랍할 여유공간이 있는지도
확인이 필요합니다.
이를 위해서 IsRoomAvailableForPayload 함수를 만들어주도록 합니다.
(같은 기능을 InventoryComponent에 만들어놨다는 사실을 기억하길 바랍니다.)


Int Point타입의 DraggedItemTopLeftTile을 이용해서 InventoryComponent의 Tile To Index 함수를 이용해서 1차원 공간으로 변형해 IsRoomAvailable 노드에 연결해줍니다.

위의 DraggedItemTopLeftTile의 인풋값을 받아오기 위해서
OnDragOver 함수를 오버라이딩 해줍니다.

OnDragOver 함수에서 마우스의 위치를 계산해 로컬 변수로 저장해줍니다. 또한 이후에 아이템을 옮길 때를 위해 별도의 기능들이 필요합니다.

아이템을 드래그 할 때 각 타일의 절반을 넘어갈 시 최적의 공간을 찾아서 표시가 되어야 합니다.
이를 위한 기능을 만들어봅시다.






이제 테스트를 해본다면, 아이템이 제대로 인벤토리 내에서 움직이는지 확인 할 수 있습니다.
하지만 아이템을 다른 아이템 위에 드래깅한다거나, 드래깅하면서 내가 놓을 수 있는지 없는지에 대한 시각적 효과가 있어야 합니다.



WBP_InventoryGrid 이벤트그래프에서 On Drag Enter/Leave 이벤트를 오버라이딩해서 생성한다.

해당 이벤트들을 통해서 DrawDropLocation 값을 변경해 저장해주도록 하자.
OnPaint 함수로 이동해서 이전에 작성하지 않은 로직을 완성해보자.



마지막으로 아이템 이미지의 회전을 적용하기 위해 On Preview Key Down 함수를 오버라이딩 한다.

R 키가 입력되면, Payload로부터 Rotate 함수와 Refrehs 함수를 호출해온다.
Rotate 함수는 매우 간단한데, 기존에 만들었던 Rotated Boolean을 스위칭해주는 함수이다.

ItemObject 내에 Rotate 함수 로직
다른 함수도 하나 만들어주도록 한다.

이제 아이템이 회전되었냐에 따라 BP_ItemObject에서 차원을 새로 반환해야 하므로

회전로직에 의해서 TryAddItem 함수도 수정이 필요하다.

마지막으로 간단한 버그를 수정하고 마무리하겠다.
아이템을 드래그하면서 인벤토리를 종료하게 되면 마우스 포인터에 아이템 아이콘이 붙어서 지워지지 않는데,
