Tarkov-like Inventory Tutorial (4/4)

Brann Goldbeard·2025년 9월 26일

인벤토리 시스템

목록 보기
4/4

지금까지 인벤토리에 아이템을 띄우는 것 까지 완성했다.

아이템을 인벤토리에서 옮기고, 땅에 버리고, 회전하는 것만 완성하면 된다.

이번 튜토리얼을 마지막으로 위에 기술한 모든 기능을 구현하며 마무리 될 예정이다.


  • WBP_Inventory 로 돌아가서 BackGroundBorder에 마우스입력을 감지하는 이벤트에 바인딩을 하며 시작한다.

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

Handled 와 Unhandled의 차이

  • 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 함수를 오버라이딩 하도록 한다.

  • Create Drag Drop Operation 노드
    이 노드는 "무엇을(Payload) 옮길 것인지, 어떻게(Visual) 보여줄 것인지,
    누가(Operation Class) 이 일을 처리할 것인지"
    를 정의하여,
    드래그 작업이 위젯 경계를 넘어 다른 위젯으로 정보를 전달할 수 있도록 준비하는 역할을 합니다.
  1. Payload (데이터)
    Payload 핀에 연결되는 오브젝트 참조는 드래그 대상이 되는 실제 데이터 그 자체입니다.
    저장하는 것: 드래그 앤 드롭 작업이 완료되었을 때, 드롭된 위치로 전달되어야 할 핵심 정보입니다.
    인벤토리 예시: 이 인벤토리 시스템에서는 BP Item Object 참조가 Payload로 사용되는 것이 일반적입니다.
    이 BP Item Object 인스턴스 안에는 아이템의 ID, 스택 수, 크기, 위치 등 모든 데이터가 들어있으므로, 이 참조만 전달하면 됩니다.

타입: 일반적으로 UObject의 자식 클래스(예: BP Item Object) 참조가 사용됩니다.

  1. Default Drag Visual (시각적 요소)
    Default Drag Visual 핀에 연결되는 오브젝트 참조는 마우스를 따라다니며 사용자에게 보여주는 시각적인 요소입니다.
    저장하는 것: 드래그 작업이 시작될 때 생성되어 마우스 커서 아래에 표시되는 위젯(Widget) 인스턴스입니다.
    인벤토리 예시: 인벤토리 아이템의 아이콘만 표시하는 작은 User Widget 클래스가 사용됩니다.
    이 위젯은 보통 Create Widget 노드를 통해 생성된 후 Default Drag Visual에 연결됩니다.

특별한 이유로 드래깅 할 때 다른 이미지를 원한다면 다른 위젯을 생성해서 해당 노드에 연결해서 전달해주면 됩니다.

드래깅하면서 인벤토리에서는 지워져야 하므로, Removed 이벤트를 호출하고, Remove From Parent로 마찬가지로 위젯에서도 삭제해줍니다.

다음은 On Mouse Button Down 함수를 오버라이딩 합니다.

  • 왼쪽 마우스 버튼 클릭을 감지해서 OnDragDetected 이벤트를 자동으로 호출해주는 역할을 합니다.

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

다만, 아직 드롭기능을 구현하지 않았으므로 아이템을 드랍하면 영원히 사라져버립니다.

드랍기능에서 우리가 구현해야 할 부분은, 인벤토리 바깥 부근에서 떨어트리면 아이템이 땅에 떨어지며 스폰하고
인벤토리 내부 다른 위치에 놓을 경우 아이템의 위치가 변경되어야 합니다.
우선 아이템이 땅에 버려지는 부분부터 구현해보도록 합시다.

WBP_Inventory로 가서 OnDrop 함수를 오버라이딩 합니다.

Get Payload(ItemObject)를 가져와서 Casting 해주도록 합니다.
여기에서 아이템을 떨어트리는 기능을 만들 수 있지만, 위젯 내부에 해당 기능을 구현하는 것은 옳은 방법이 아닐겁니다.
그러므로 GameState를 만들어서 그 안에 아이템을 떨어트리고 생성하는 기능을 만들고 호출하도록 합니다.

이미 있던 GameState를 써도 상관없지만, 새로 만들 예정이라면 'GameStateBase'를 상속받아서
자신만의 GameState를 생성하도록 합니다.

  • GameStateBase 내부에 SpawnItemFromActor 함수를 생성합니다.
  • 생성 후 위와 같이 3개의 파라미터를 생성해줍니다.

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

  • GroundClamp가 true일 경우 LineTrace 노드를 통해 스폰이 될 위치의 바닥 위치를 구해서 Spawn Location 으로 덮어씌워줍니다.

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

  • GetItemClass 함수를 만들어주었다.

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

  • 만약 GameState를 생성해서 추가했다면 반드시 프로젝트 세팅이나 월드 세팅에서 자신이 만든 Game State Class에 지정해주도록 해야한다.

  • 이제 WBP_Inventory에서 OnDrop 함수가 완성 할 수 있게 되었다.

실제로 테스트 해보면..

  • 인벤토리 밖으로 끌어다 놓으면 아이템이 플레이어 캐릭터 앞에 스폰되는 것을 확인할 수 있다.
    하지만 실제로 인벤토리 배열 내부에서 아이템이 제대로 지워지고 있지 않으므로 해당 부분을 만들어보자.

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

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

인풋값과 비교하여 같을 경우, Items 배열을 초기화해준다. 또한 IsDirty를 true로 바꿔주어 인벤토리에 변화가 생겨 Refresh 함수가 호출되도록 해준다.

Drop 기능이 완벽하게 작동한다면 이제 인벤토리 내부에서의 이동을 구현해보도록 하자.

WBP_InventoryGrid로 이동해서 OnDrop 함수를 오버라이딩한다.
오버라이딩 후 몇 가지 필요한 함수를 만들어야 하는데, 우선 GetPayload 함수를 생성하도록 한다.

  • Input으로 DragDropOperation 오브젝트 래퍼런스를 생성한다.
  • Out은 BP_ItemObject 오브젝트 래퍼런스를 생성한다.

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

OnDrop으로 이동해서 해당 함수를 이용해봅시다.

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

  • IsRoomAvailable 함수 내에서는 BP_ItemObject를 Input값으로 받아줍니다.
    해당 Payload가 유효하면 InventoryComponent에서 IsRoomAvailable 함수를 이용해 아이템 배열 내부에 공간이 충분한지 확인해줍니다.
    하지만 아직 Top Left Index에 넣을 값을 위젯 내부에서 만들지 않았으므로 해당 변수를 선언해주도록 합시다.

  • Boolean은 이후 드래그앤드롭 시 백그라운드에 Highlight 되는 기능을 위해 미리 만들어주었습니다.

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

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

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

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

  • MousePositionTile 함수를 만들어서 TileSize의 비율에 따라서 Right Boolean과 Down Boolean을 반환합니다.

  • 해당 노드를 호출함과 동시에 OnDragOver 함수 내의 로컬 변수로 저장해주도록 합니다.
  • 위의 로직을 통해 TopLeftPosition을 구할 수 있습니다.
  • 해당 로직을 통해 구한 TopLeftPosition을 DraggedItemTopLeftTile 변수에 저장해주면 마무리됩니다.

  • OnDrop으로 돌아가서 충분한 여유 공간이 있는가에 대한 로직을 작성해줍니다.

  • 충분한 여유공간이 있다면, GetPayload - Add Item At 함수를 이용해 해당 인벤토리에 아이템을 만들어줍니다.

이제 테스트를 해본다면, 아이템이 제대로 인벤토리 내에서 움직이는지 확인 할 수 있습니다.

하지만 아이템을 다른 아이템 위에 드래깅한다거나, 드래깅하면서 내가 놓을 수 있는지 없는지에 대한 시각적 효과가 있어야 합니다.


  • 이동이 가능한 곳이라면 초록색으로 그게 아니라면 빨간색으로 뜨는 UI가 필요하다...

  • OnDrop 함수에서 다음과 같이 로직을 완성해준다.

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

해당 이벤트들을 통해서 DrawDropLocation 값을 변경해 저장해주도록 하자.

OnPaint 함수로 이동해서 이전에 작성하지 않은 로직을 완성해보자.



  • 위와 같이 로직을 완성하면 드래그앤드롭시 원하는 색상으로의 표기가 가능해진다.
  • 프로젝트 초기에 만들었던 SB_Color는 이곳에 사용하면 된다.

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

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

  • ItemObject 내에 Rotate 함수 로직

다른 함수도 하나 만들어주도록 한다.

  • IsRotated 함수 Rotate를 반환한다.

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

  • GetDimension 함수를 다음과 같이 수정하도록 한다.

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

  • 다음과 같이 Do once를 이용해 아이템이 회전 후 추가하는 로직을 추가해준다.

마지막으로 간단한 버그를 수정하고 마무리하겠다.

아이템을 드래그하면서 인벤토리를 종료하게 되면 마우스 포인터에 아이템 아이콘이 붙어서 지워지지 않는데,

  • 다음과 같이 플레이어 케릭터에서 인벤토리를 실행하는 키에 branch 노드를 추가하고 Is Drag Dropping을 확인해준다.
    그럼 더 이상 아이템을 드래깅 중일때 인벤토리를 종료할 수 없게 된다.

0개의 댓글