본문으로 건너뛰기

안동민 개발노트

본문 시작

간단한 인벤토리 시스템 구현

아이템 구조체·획득 액터·플레이어 배열·슬롯 위젯을 연결해 동적으로 갱신되는 인벤토리를 완성합니다.

이전 절들에서 위젯 블루프린트의 기본 개념, 동적 UI 생성, 애니메이션, 그리고 게임플레이와의 상호작용 방법을 학습했습니다.

이제 이러한 지식들을 종합하여 간단한 인벤토리 시스템을 실제로 구현해보겠습니다.

인벤토리 시스템은 UI와 게임플레이 로직이 밀접하게 연동되는 좋은 예시이며, 다양한 UI 컴포넌트와 데이터 관리 기법이 함께 사용됩니다.

인벤토리 시스템 구현

인벤토리는 아이템 획득 이벤트, 플레이어 보관 배열, 슬롯 위젯 갱신이 한 흐름으로 이어져야 합니다. 인벤토리 갱신 흐름은 아이템 데이터가 들어오면 UI가 어떤 순서로 재생성되고, 중복 획득과 빈 슬롯을 어떻게 처리하는지 분리합니다.

  1. 구현 순서

    Overlap은 아이템 구조체만 플레이어에게 보내고 월드 액터 책임을 끝냅니다. 2 중첩 규칙과 빈 공간을 확인한 뒤 배열 수량을 한 번만 변경합니다. 3 데이터 변경이 성공했을 때만 화면 위젯에 갱신 신호를 보냅니다. 4 기존 슬롯은 수량만 바꾸고 새 항목만 생성해 Grid Panel에 배치합니다.

  2. Pickup이 ItemData 전달

    Overlap은 아이템 구조체만 플레이어에게 보내고 월드 액터 책임을 끝냅니다.

  3. PlayerInventory 갱신

    중첩 규칙과 빈 공간을 확인한 뒤 배열 수량을 한 번만 변경합니다.

  4. Inventory Changed 이벤트

    데이터 변경이 성공했을 때만 화면 위젯에 갱신 신호를 보냅니다.

  5. 슬롯 재사용·갱신

    기존 슬롯은 수량만 바꾸고 새 항목만 생성해 Grid Panel에 배치합니다.

  6. 책임 분리

    Sphere Collision의 Begin Overlap에서 플레이어 캐릭터를 캐스팅하고 ItemInfo를 전달합니다. ItemName, ItemIcon, Description, AttackPower, Weight를 하나의 구조체로 묶어 슬롯 갱신에 전달합니다. PlayerInventory 배열에 NewItem을 Add한 뒤, 열린 WBP_InventoryScreen에 RefreshInventoryUI를 호출합니다.


인벤토리 시스템의 목표

우리가 구현할 인벤토리 시스템은 다음과 같은 기능을 가집니다.

  • 플레이어가 아이템을 획득하면 인벤토리에 추가됩니다.
  • 인벤토리 UI에 획득한 아이템이 동적으로 표시됩니다.
  • 인벤토리 UI는 아이템의 아이콘과 이름을 보여줍니다.
  • (선택 사항) 인벤토리 창을 열고 닫을 수 있습니다.

시스템 구현을 위한 준비물

이 시스템을 구현하려면 다음 블루프린트들이 필요합니다.

아이템 데이터 구조체: F_ItemData (5장 2절에서 생성)

  • ItemName (Text)
  • ItemIcon (Texture2D Soft Object Reference)
  • Description (Text)
  • AttackPower (Integer)
  • Weight (Float)

플레이어 캐릭터 블루프린트: BP_PlayerCharacter (또는 여러분의 플레이어 캐릭터)

  • PlayerInventory (변수 타입: F_ItemData 구조체의 배열 Array)

아이템 획득용 액터: BP_PickupItem (새로 생성)

  • Overlap 이벤트 발생 시 F_ItemData를 플레이어에게 전달

인벤토리 슬롯 위젯: WBP_InventorySlot (새로 생성)

  • 개별 아이템을 표시하는 UI (아이콘, 이름)
  • 아이템 데이터를 받아 자신을 업데이트하는 함수 포함

메인 인벤토리 위젯: WBP_InventoryScreen (새로 생성)

  • 인벤토리 슬롯들을 동적으로 추가할 Grid Panel 포함
  • 인벤토리 데이터를 받아 슬롯을 새로고침하는 함수 포함

인벤토리 UI 구현 단계별 가이드

인벤토리 UI 연결 구조

인벤토리 UI는 아이템 액터, 캐릭터 배열, 슬롯 위젯, 화면 위젯이 데이터를 주고받는 구조입니다. 연결 방향을 분리하면 디버깅이 쉬워집니다.

  1. BP_PickupItem

    월드에 놓인 아이템이 overlap을 감지합니다.

  2. Character

    아이템 데이터를 인벤토리 배열에 추가합니다.

  3. Inventory Data

    F_ItemData 배열이 이름과 아이콘 같은 표시 정보를 보관합니다.

  4. Slot Widget

    각 아이템 한 칸을 생성하고 갱신합니다.

  5. Inventory Screen

    슬롯들을 배치하고 열기/닫기를 처리합니다.

BP_PickupItem 액터 생성

월드에 배치되어 플레이어가 주울 수 있는 아이템을 만듭니다.

액터 블루프린트 생성: BP_PickupItem (부모 클래스: Actor)

컴포넌트 추가
  • Static Mesh Component (예: Shape_Sphere 또는 실제 아이템 메시)
  • Sphere Collision Component (Is Overlap Events Enabled 체크)
변수 추가
  • ItemInfo (변수 타입: F_ItemData)
  • 디테일 패널에서 ItemInfo 변수를 선택하고 Instance EditableExpose on Spawn을 체크합니다. 이렇게 하면 월드에 배치된 BP_PickupItem 인스턴스마다 다른 아이템 정보를 직접 입력할 수 있습니다.
이벤트 그래프 로직
  • Sphere CollisionOn Component Begin Overlap 이벤트 노드를 가져옵니다.
  • Other ActorBP_PlayerCharacter인지 Cast To BP_PlayerCharacter 노드로 확인합니다.
  • 캐스팅이 성공하면, BP_PlayerCharacterAddItemToInventory 함수 (다음 단계에서 생성)를 호출하고 ItemInfo 변수(Get 노드)를 전달합니다.
  • 아이템을 주웠으므로, Destroy Actor 노드를 호출하여 자신을 제거합니다.
  • 컴파일 및 저장합니다.

월드 배치: 메인 에디터로 돌아와 BP_PickupItem 인스턴스를 월드에 여러 개 배치하고 각 인스턴스에 다른 ItemInfo를 입력해봅니다. (예: ItemName "골드", ItemIcon 골드 아이콘 텍스처 등)

BP_PlayerCharacter에 인벤토리 로직 추가

플레이어 캐릭터가 아이템을 관리하고 UI 업데이트를 지시하는 역할을 합니다.

변수 추가: PlayerInventory (변수 타입: F_ItemData배열 Array)

함수 추가: AddItemToInventory

  • 입력 핀: NewItem (타입: F_ItemData)
  • 로직
    • PlayerInventory 변수에서 드래그하여 Add 노드를 호출합니다. NewItem을 연결합니다.
    • (선택 사항) Print String 노드를 연결하여 아이템 [NewItem.ItemName] 획득! 메시지를 출력합니다.
    • Get Player Controller 노드를 가져옵니다.
    • Get Player Controller에서 Get Widget of Class 노드를 호출합니다. Widget ClassWBP_InventoryScreen을 선택합니다.
    • Get Widget of ClassReturn ValueCast To WBP_InventoryScreen으로 캐스팅합니다.
    • 캐스팅이 성공하면, WBP_InventoryScreenRefreshInventoryUI 함수 (다음 단계에서 생성)를 호출하고, PlayerInventory 변수(Get 노드)를 RefreshInventoryUI의 입력 핀에 연결합니다.
  • 컴파일 및 저장합니다.

WBP_InventorySlot 위젯 구현

인벤토리 슬롯 데이터 흐름

슬롯 위젯은 단순 버튼이 아니라 아이템 배열, 표시 데이터, 클릭 입력, 드래그 상태, 갱신 함수가 만나는 경계다.

  1. 1
    아이템 획득

    Pickup Actor가 플레이어 인벤토리 배열에 ItemData를 추가한다. add item

  2. 2
    슬롯 생성

    InventoryScreen이 배열 길이만큼 Slot Widget을 만들고 인덱스를 전달한다. create widget

  3. 3
    표시 갱신

    슬롯은 아이콘, 수량, 이름을 현재 ItemData에서 읽어 그린다. refresh

  4. 4
    입력 처리

    클릭, 우클릭, 드래그 이벤트가 슬롯 인덱스와 함께 캐릭터 로직으로 돌아간다. event

  5. 5
    배열 변경

    사용, 이동, 버리기 후 배열을 바꾸고 UI 전체 또는 해당 슬롯을 다시 갱신한다. sync ↺ 표시 갱신

인벤토리의 각 아이템을 개별적으로 표시하는 작은 위젯입니다.

위젯 블루프린트 생성: WBP_InventorySlot (부모 클래스: User Widget)

디자이너 탭
  • Size Box를 캔버스 패널에 배치하고 Width Override, Height Override를 100x100 등으로 설정하여 슬롯 크기를 고정합니다.
  • Size Box의 자식으로 Border를 배치하여 배경색을 줍니다.
  • Border의 자식으로 Vertical Box를 배치합니다.
  • Vertical Box의 자식으로 Image (이름: ItemIcon, Is Variable 체크)와 Text (이름: ItemNameText, Is Variable 체크)를 배치합니다.
  • ImageText의 정렬 및 패딩을 조절하여 아이콘과 텍스트가 잘 보이도록 합니다.
그래프 탭
  • 함수 추가: UpdateSlotInfo
    • 입력 핀: ItemData (타입: F_ItemData)
    • 로직
      • ItemData (입력 핀)에서 Break F_ItemData 노드를 가져옵니다.
      • ItemNameText 변수(Set 노드)의 Text 핀에 ItemName (Break F_ItemData의 출력)을 연결합니다.
      • ItemIcon 변수(Set Node)의 Brush 핀에 Make Brush From Texture 노드를 연결합니다.
      • Make Brush From TextureTexture 핀에 ItemIcon (Break F_ItemData의 Soft Texture2D Object Reference 출력)을 직접 연결합니다. (언리얼 엔진이 소프트 레퍼런스를 자동으로 로드하여 사용합니다. 명시적 Load Asset 노드가 필요 없는 경우도 있지만, 안전하게는 Load Asset 이후 연결하는 것이 좋습니다.)
      • 컴파일 및 저장합니다.

WBP_InventoryScreen 위젯 구현

모든 인벤토리 슬롯을 담고 관리하는 메인 인벤토리 UI입니다.

위젯 블루프린트 생성: WBP_InventoryScreen (부모 클래스: User Widget)

디자이너 탭
  • Canvas PanelBorder를 배치하여 인벤토리 창의 배경을 만듭니다. (크기와 색상 조절)
  • Border의 자식으로 Grid Panel을 배치하고 이름을 InventoryGrid로 지정한 후 Is Variable을 체크합니다. (이 패널에 슬롯 위젯들이 추가됩니다.)
  • Grid PanelSlot 설정에서 Column Fill 또는 Row Fill을 조절하여 그리드 크기를 설정할 수 있습니다.
그래프 탭
  • 함수 추가: RefreshInventoryUI
    • 입력 핀: CurrentInventory (타입: F_ItemData배열 Array)
    • 로직
      • InventoryGrid 변수(Get 노드)에서 드래그하여 Clear Children 노드를 호출합니다. (기존 슬롯들을 모두 제거하여 새로 그립니다.)
      • CurrentInventory (입력 핀)에서 드래그하여 For Each Loop 노드를 가져옵니다.
      • Loop Body 핀 뒤에:
        • Create Widget 노드를 호출합니다. Widget ClassWBP_InventorySlot을 선택합니다. Owning PlayerGet Owning Player를 연결합니다.
        • Create WidgetReturn ValueCast To WBP_InventorySlot으로 캐스팅합니다.
        • 캐스팅이 성공하면, WBP_InventorySlotUpdateSlotInfo 함수를 호출하고 For Each LoopArray Element (현재 아이템 데이터)를 연결합니다.
        • InventoryGrid 변수(Get 노드)에서 드래그하여 Add Child to Grid 노드를 호출합니다.
          • TargetInventoryGrid를 연결합니다.
          • ContentCast To WBP_InventorySlotAs WBP Inventory Slot 출력 핀을 연결합니다.
          • RowColumn 핀은 For Each LoopIndex를 활용하여 계산합니다. (예: 5칸짜리 그리드라면 Column = Index % 5, Row = Index / 5와 같이 계산)
      • 컴파일 및 저장합니다.
RefreshInventoryUI는 배열을 지우고 다시 그리는 갱신 루틴이다

인벤토리가 바뀔 때마다 기존 슬롯을 비운 뒤 배열 순서대로 슬롯 위젯을 생성해 데이터를 주입한다.

  1. 1
    Clear Grid

    기존 자식 위젯 제거

  2. 2
    Loop Items

    CurrentInventory 순회

  3. 3
    Create Slot

    WBP_InventorySlot 생성

  4. 4
    Inject Data

    아이콘, 이름, 수량 전달

  5. 5
    Place Slot

    Index로 Grid 위치 계산

점검기준
완료 신호화면 슬롯 수와 배열 길이가 일치한다.
빈 배열Clear 뒤 새 슬롯을 만들지 않아도 이전 슬롯이 남지 않는다.
성능아이템 수가 많으면 슬롯 재사용이나 풀링을 검토한다.

메인 게임 컨트롤러에서 인벤토리 띄우기

플레이어 컨트롤러에서 인벤토리 창을 관리합니다.

블루프린트 열기: BP_PlayerController

변수 추가: InventoryScreenRef (타입: WBP_InventoryScreen Object Reference)

이벤트 그래프 로직
  • Event BeginPlay 뒤에:
    • Create Widget 노드로 WBP_InventoryScreen을 생성합니다. Owning PlayerSelf (Player Controller 자신)를 연결합니다.
    • Create WidgetReturn ValueSet InventoryScreenRef 변수에 연결하여 참조를 저장해둡니다.
    • 컴파일 및 저장합니다. (초기에는 화면에 추가하지 않습니다.)
  • Event I (또는 인벤토리 키) 입력 노드를 추가합니다.
    • InventoryScreenRef 변수(Get 노드)에서 Is Valid 노드를 연결하여 위젯이 유효한지 확인합니다.
    • Branch 노드(Is Valid의 출력)의 True 핀 뒤에:
      • InventoryScreenRef에서 Is Visible 노드를 호출하여 현재 보이는지 확인합니다.
      • Branch 노드(Is Visible의 출력)로 분기합니다.
        • True (현재 보임): 인벤토리 닫기 로직
          • InventoryScreenRef에서 Remove From Parent 노드를 호출하여 화면에서 제거합니다.
          • Set Input Mode Game Only 노드를 호출합니다.
          • Set Show Mouse Cursor 노드를 호출하고 bShowMouseCursor를 해제합니다.
        • False (현재 숨겨짐): 인벤토리 열기 로직
          • InventoryScreenRef에서 Add to Viewport 노드를 호출하여 화면에 추가합니다.
          • Set Input Mode UI Only 노드를 호출합니다.
          • Set Show Mouse Cursor 노드를 호출하고 bShowMouseCursor를 체크합니다.
          • InventoryScreenRef에서 RefreshInventoryUI 함수를 호출하고, BP_PlayerCharacterPlayerInventory 변수를 가져와 연결합니다. (플레이어 캐릭터의 인벤토리 데이터를 UI에 전달)

UI 동작 테스트

테스트 전에는 아이템 획득, 배열 갱신, 슬롯 생성, 입력 모드 복구가 같은 실행 흐름 안에서 이어지는지 한 번 더 확인합니다.

획득부터 창 닫기까지 확인하기

인벤토리 예제는 Overlap, 배열 추가, UI 새로고침, 입력 모드 복구가 모두 이어져야 완성됩니다.

  1. Overlap이 한 번만 처리된다

    Pickup BP_PickupItem이 AddItemToInventory 호출 뒤 Destroy Actor로 사라지는지 봅니다.

  2. 슬롯 수가 배열과 맞는다

    Refresh InventoryGrid를 Clear Children 한 뒤 For Each Loop로 슬롯을 다시 채웁니다.

  3. I 키로 열고 닫는다

    Toggle Add to Viewport와 Remove From Parent가 같은 위젯 참조에서 실행되는지 확인합니다.

  4. 마우스 상태를 복구한다

    Cursor 창을 닫을 때 Set Input Mode Game Only와 커서 숨김이 같이 실행되어야 합니다.

모든 블루프린트를 컴파일하고 저장합니다.

메인 에디터의 월드에 BP_PlayerCharacter와 여러 BP_PickupItem 인스턴스들을 배치합니다.

게임을 플레이합니다.

BP_PickupItem에 겹치면 아이템을 획득하고 메시지가 출력되는 것을 확인합니다.

I 키를 누르면 인벤토리 창이 나타나고, 획득한 아이템들이 그리드에 동적으로 표시되는 것을 확인합니다.

I 키를 다시 누르면 인벤토리 창이 닫히고 마우스 커서가 사라지는 것을 확인합니다.


이로써 간단한 인벤토리 시스템 구현이 완료되었습니다!

이 예시는 UI, 데이터 관리, 블루프린트 통신 등 이전에 학습했던 여러 개념들이 어떻게 실제 게임 시스템에서 함께 작동하는지 보여주는 좋은 예시입니다.

이 인벤토리 시스템은 단순히 기본 구현이며, 여기에 아이템 사용, 버리기, 드래그 앤 드롭, 아이템 툴팁, 스택 시스템 등 다양한 기능을 추가하여 더욱 풍부하게 만들 수 있습니다.

기본 인벤토리가 동작한 뒤에는 데이터, UI, 입력, 확장 지점을 나누어 점검하면 다음 기능을 붙이기 쉽습니다.

인벤토리 시스템 완성 점검표

아이템 획득부터 UI 표시까지 한 번에 보며, 빠진 참조와 갱신 호출을 좁혀 나갑니다.

  1. F_ItemData

    Data 이름, 아이콘, 설명처럼 슬롯에 보여줄 값을 구조체에 모읍니다.

  2. BP_PickupItem

    Actor Overlap에서 플레이어에게 ItemInfo를 넘기고 자신을 제거합니다.

  3. PlayerInventory

    Player 배열에 아이템을 추가한 뒤 인벤토리 UI 새로고침을 호출합니다.

  4. WBP_InventoryScreen

    Widget Grid Panel을 비우고 슬롯 위젯을 생성해 데이터를 적용합니다.

마지막으로 인벤토리 데이터 배열과 슬롯 위젯을 동기화할 때의 원본 데이터, 갱신, 사용 요청 기준을 정리합니다.

인벤토리 데이터와 슬롯 UI 동기화

아이템 줍기, 배열 갱신, 슬롯 표시, 사용 요청을 같은 데이터 기준으로 묶으면 인벤토리 UI가 덜 흔들립니다.

  1. 데이터 배열이 화면의 원본

    BP_PlayerCharacter의 InventoryItems 배열을 원본으로 두고 WBP_InventoryScreen은 배열을 읽어 슬롯을 다시 그립니다. 01 BP_PickupItem이 Overlap에서 ItemData를 전달하면 Add Item 함수가 중복 가능 여부와 최대 개수를 검사합니다. 02 WBP_InventorySlot은 아이콘, 이름, 수량을 ItemData에서 받고 클릭 시 자신의 Index를 부모 위젯에 알립니다. 03 Use 요청은 캐릭터 로직에서 검증하고 성공한 뒤 배열을 변경하며, UI는 변경된 배열로 Refresh Inventory를 호출합니다.

  2. 데이터 배열이 화면의 원본

    BP_PlayerCharacter의 InventoryItems 배열을 원본으로 두고 WBP_InventoryScreen은 배열을 읽어 슬롯을 다시 그립니다.

  3. 줍기

    BP_PickupItem이 Overlap에서 ItemData를 전달하면 Add Item 함수가 중복 가능 여부와 최대 개수를 검사합니다.

  4. 표시

    WBP_InventorySlot은 아이콘, 이름, 수량을 ItemData에서 받고 클릭 시 자신의 Index를 부모 위젯에 알립니다.

  5. 사용·재갱신

    Use 요청은 캐릭터 로직에서 검증하고 성공한 뒤 배열을 변경하며, UI는 변경된 배열로 Refresh Inventory를 호출합니다.