안동민 개발노트

본문 시작

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

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

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

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

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


인벤토리 시스템의 목표

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

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

시스템 구현을 위한 준비물

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

아이템 데이터 구조체: F_ItemData (5장 2절에서 생성한 구조체에 ItemIcon을 추가합니다. 기존 ItemMesh 필드는 필요하면 유지합니다.)

  • 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 제거는 배열을 지우지 않습니다.픽업 액터ItemInfo캐릭터인벤토리 배열인벤토리 화면저장한 참조자식 슬롯이름·아이콘
인벤토리 데이터와 표시 위젯의 책임픽업이 아이템 데이터를 전달하면 캐릭터가 배열을 갱신합니다. 컨트롤러가 보관한 화면 참조로 갱신을 요청하고 화면은 자식 슬롯을 재구성합니다. UI 제거는 배열을 지우지 않습니다.픽업 액터ItemInfo캐릭터인벤토리 배열인벤토리 화면저장한 참조자식 슬롯이름·아이콘

Clear Children은 화면의 자식 슬롯을 제거합니다. 플레이어의 인벤토리 배열을 지우는 작업은 아닙니다.

BP_PickupItem 액터 생성

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

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

컴포넌트 추가
  • Static Mesh Component (예: Shape_Sphere 또는 실제 아이템 메시)
  • Sphere Collision Component: 플레이어와의 Query/Overlap 응답을 설정하고 양쪽 컴포넌트의 Generate Overlap Events를 켭니다.
변수 추가
  • ItemInfo (변수 타입: F_ItemData)
  • 디테일 패널에서 ItemInfo 변수를 선택하고 Instance Editable 및 Expose on Spawn을 체크합니다. 이렇게 하면 월드에 배치된 BP_PickupItem 인스턴스마다 다른 아이템 정보를 직접 입력할 수 있습니다.
이벤트 그래프 로직
  • Sphere Collision의 On Component Begin Overlap 이벤트 노드를 가져옵니다.
  • Other Actor가 BP_PlayerCharacter인지 Cast To BP_PlayerCharacter 노드로 확인합니다.
  • 캐스팅 성공 뒤 Do Once를 통과시켜 같은 픽업을 중복 처리하지 않도록 한 다음, AddItemToInventory에 ItemInfo를 전달합니다. 이 단순 예제는 용량 제한 없이 배열 추가가 가능한 조건입니다.
  • 아이템 추가 호출이 끝난 성공 흐름에서만 Destroy Actor(Self)로 픽업을 제거합니다. UI가 아직 없어도 인벤토리 데이터의 추가는 유지합니다.
  • 컴파일 및 저장합니다.

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

BP_PlayerCharacter에 인벤토리 로직 추가

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

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

함수 추가: AddItemToInventory

  • 입력 핀: NewItem (타입: F_ItemData)
  • 로직
    • PlayerInventory 변수에서 드래그하여 Add 노드를 호출합니다. NewItem을 연결합니다.
    • (선택 사항) Print String 노드를 연결하여 아이템 [NewItem.ItemName] 획득! 메시지를 출력합니다.
    • 자신을 소유한 Controller를 BP_PlayerController로 캐스트하고, 다음 단계에서 저장할 InventoryScreenRef를 읽습니다.
    • 참조가 유효하면 RefreshInventoryUI에 현재 PlayerInventory를 전달합니다. 아직 위젯이 없으면 이 UI 호출만 건너뛰고, 나중에 창을 열 때 최신 배열로 갱신합니다.
  • 컴파일 및 저장합니다.

WBP_InventorySlot 위젯 구현

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

위젯 블루프린트 생성: 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 체크)를 배치합니다.
  • Image와 Text의 정렬 및 패딩을 조절하여 아이콘과 텍스트가 잘 보이도록 합니다.
그래프 탭
  • 함수 추가: UpdateSlotInfo
    • 입력 핀: ItemData (타입: F_ItemData)
    • 로직
      • ItemData (입력 핀)에서 Break F_ItemData 노드를 가져옵니다.
      • ItemNameText 위젯 참조(Get)에서 Set Text를 호출하고 ItemName 값을 연결합니다. 위젯 참조 변수를 다른 객체로 교체하는 Set과 구별합니다.
      • Image 위젯 참조 ItemIcon(Get)에서 Set Brush from Soft Texture를 호출하고, 구조체의 ItemIcon 경로를 Soft Texture에 연결합니다. 이 API가 소프트 텍스처 요청을 처리하도록 합니다.
      • Make Brush From Texture는 로드된 Texture2D를 받습니다. 소프트 참조가 이 핀에서 자동 로드된다고 가정하지 않습니다. 명시적인 비동기 로드가 필요하면 함수 밖의 이벤트 그래프에서 Async Load Asset을 호출하고 완료 후 유효한 텍스처를 적용합니다.
      • 컴파일 및 저장합니다.

WBP_InventoryScreen 위젯 구현

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

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

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

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

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

블루프린트 열기: BP_PlayerController

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

이벤트 그래프 로직
  • Event BeginPlay 뒤에:
    • Create Widget 노드로 WBP_InventoryScreen을 생성합니다. Owning Player는 Self (Player Controller 자신)를 연결합니다.
    • Create Widget의 Return Value를 Set InventoryScreenRef 변수에 연결하여 참조를 저장해둡니다.
    • 컴파일 및 저장합니다. (초기에는 화면에 추가하지 않습니다.)
  • 이 예제는 단일 로컬 플레이어를 대상으로 합니다. Controller에 Event I 또는 프로젝트의 대응 Input Action 이벤트를 연결합니다. 게임패드·키보드 포커스와 닫기 버튼도 실제 UI에서 확인합니다.
    • InventoryScreenRef 변수(Get 노드)에서 Is Valid 노드를 연결하여 위젯이 유효한지 확인합니다.
    • Branch 노드(Is Valid의 출력)의 True 핀 뒤에:
      • InventoryScreenRef에서 Is In Viewport를 호출하여 화면에 추가되어 있는지 확인합니다. Is Visible은 Visibility 속성 기준이므로 Viewport 추가 여부의 대용으로 쓰지 않습니다.
      • Branch 노드(Is In Viewport의 출력)로 분기합니다.
        • True (현재 보임): 인벤토리 닫기 로직
          • InventoryScreenRef에서 Remove From Parent 노드를 호출하여 화면에서 제거합니다.
          • Set Input Mode Game Only로 복구합니다. 열 때 이동·시점 입력을 제한했다면 Set Ignore Move Input(false)와 Set Ignore Look Input(false)도 짝을 맞춰 호출합니다.
          • Set Show Mouse Cursor 노드를 호출하고 bShowMouseCursor를 해제합니다.
        • False (현재 숨겨짐): 인벤토리 열기 로직
          • InventoryScreenRef에서 Add to Viewport 노드를 호출하여 화면에 추가합니다.
          • Set Input Mode Game And UI를 호출합니다. 이 예제에서는 위젯이 I 키를 처리하지 않아 Controller가 닫기 입력을 받을 수 있게 구성합니다. UI Only를 쓰려면 위젯 안에 별도의 닫기 입력 경로가 필요합니다.
          • Set Show Mouse Cursor(true)로 커서를 표시합니다. 메뉴 동안 이동·시점을 막으려면 열기 분기에서만 Set Ignore Move Input(true)와 Set Ignore Look Input(true)를 한 번씩 호출합니다.
          • InventoryScreenRef에서 RefreshInventoryUI 함수를 호출하고, BP_PlayerCharacter의 PlayerInventory 변수를 가져와 연결합니다. (플레이어 캐릭터의 인벤토리 데이터를 UI에 전달)

UI 동작 테스트

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

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

GameMode에 BP_PlayerController와 플레이어 캐릭터를 지정하고 로컬 플레이어가 캐릭터를 조종하도록 구성합니다. GameMode로 스폰할지 배치 Pawn을 Possess할지 한 방식을 선택하고, 여러 BP_PickupItem을 배치합니다.

게임을 플레이합니다.

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

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

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


획득 한 번당 배열 요소가 한 개 추가되고, 창을 열고 닫을 때 입력 상태도 함께 복구되는지 확인하세요.

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

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