About inventory-system
An inventory-system skill for Godot 4.3+ developers, with implementation guidance in GDScript and C#. It covers item and slot modeling, stacking, equipment extensions, UI binding, and save serialization.
Key features
- 01Item data defined with Resource
- 02Slot management and stack operations
- 03Inventory and equipment signals
- 04Grid UI and drag-and-drop binding
- 05Save serialization by item ID
Use cases
- 01Build a fixed-slot bag with stack limits
- 02Update grid UI when inventory changes
- 03Add equipment slots and aggregate stats
- 04Save slot contents and restore items on load
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ jame581/ godotprompter --skill inventory-systemAfter installing, type/inventory-systemin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/core-classes.mdequipment.mdserialization.mdui-binding.mdDetails
From the project READMEInventory Systems in Godot 4.3+
All examples target Godot 4.3+ with no deprecated APIs. This skill carries the decisions; the implementations live in references/ (GDScript first, then C#) — load the one you need.
Related skills: resource-pattern for custom Resource data containers, save-load for inventory serialization, event-bus for inventory change notifications, hud-system for inventory UI display, popochiu for adventure-game inventory.
1. Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ UI Layer │
│ InventoryUI (Control) │
│ └─ GridContainer │
│ └─ SlotUI × N (Button) │
│ └─ TextureRect (icon) + Label (qty) │
│ │
│ Connects to: inventory_changed signal │
│ Drag-and-drop via _get_drag_data / _drop_data │
└───────────────────────┬─────────────────────────────────┘
│ reads / mutates
┌───────────────────────▼─────────────────────────────────┐
│ Inventory (Node) │
│ slots: Array[InventorySlot] │
│ add_item(item, qty) → leftover: int │
│ remove_item(item, qty) │
│ has_item(item, qty) → bool │
│ get_item_count(item) → int │
│ │
│ signals: inventory_changed │
│ item_added(item, quantity) │
│ item_removed(item, quantity) │
└───────────────────────┬─────────────────────────────────┘
│ references
┌───────────────────────▼─────────────────────────────────┐
│ Data Layer (Resources) │
│ ItemData (Resource) │
│ id, name, description, icon, max_stack_size, │
│ item_type enum │
│ │
│ InventorySlot (inner class / Resource) │
│ item: ItemData, quantity: int │
└─────────────────────────────────────────────────────────┘
2. The Decisions That Matter
Three choices shape every inventory. Make them explicitly and state which you picked — the rest is mechanical.
| Decision | Take | Why |
|---|---|---|
Item identity: Resource vs. Dictionary / JSON | Resource in .tres | @export slots accept typed items, the Inspector edits them, and shared instances compare by reference |
Slot container: Resource vs. RefCounted | RefCounted | Slots are runtime state, not authored assets. A Resource invites saving slots to disk and reloading stale item copies |
add_item return: bool vs. leftover count | leftover int | Partial adds are the common case (stack caps, full bag). A bool forces the caller to re-query what actually fit |
If your game needs something else — a weight-based bag with no slots, a grid inventory with shaped items, an infinite stack list — say so before building. The fixed slot array this skill describes is the wrong skeleton for all three.
Non-obvious specifics
- Fill existing stacks before empty slots — two passes, in that order. A single naive pass scatters one item type across several slots and reports the bag full while space remains.
- Emit
inventory_changedonce per operation, after both passes complete — not once per slot touched. Per-slot emission re-renders the whole UI N times for oneadd_item. - Items compare by reference. Godot caches loaded resources, so
load()on the same.tresreturns the same instance andslot.item == itemis correct and cheap. If youduplicate()items at runtime, that breaks — key offItemData.idinstead. - Clear
itemtonullwhen quantity reaches 0, oris_empty()lies and the slot is never reusable. - Prevent stacking with data, not branches — set
max_stack_size = 1onEQUIPMENTandKEY_ITEMitems rather than special-casing them inadd_item. - C#: mark
ItemData[GlobalClass]or the Inspector will not offer it as a type when creating.tresfiles.
Inventory API contract
| Member | Returns | Contract |
|---|---|---|
add_item(item, quantity) | int | Leftover that did not fit; 0 means everything was added |
remove_item(item, quantity) | — | Removes up to quantity; silent when fewer are held |
has_item(item, quantity) | bool | Convenience over get_item_count |
get_item_count(item) | int | Summed across every slot |
inventory_changed | signal | Fired once per mutating operation — drives all UI |
item_added(item, quantity) | signal | Carries the amount actually added |
item_removed(item, quantity) | signal | Carries the amount actually removed |
Full
ItemData,Inventory, andInventorySlotimplementations in GDScript and C#: references/core-classes.md
3. Equipment Extension
Add equipment slots (HEAD, CHEST, WEAPON, etc.) with an Equipment node beside the Inventory, holding a slot map keyed by a SlotType enum. Stat aggregation sums a stats Dictionary (a property you add to ItemData) across equipped items; signal equipment_changed when slots change.
See references/equipment.md for the full GDScript and C#
Equipmentclass withSlotTypeenum, equip / unequip API, and stat aggregation.
4. UI Binding
Slot-grid UI: a GridContainer of Button slot widgets, each rendering one InventorySlot. Drag-and-drop uses _get_drag_data / _drop_data / _can_drop_data on the slot widget. The Inventory emits inventory_changed; the UI re-renders affected slots.
See references/ui-binding.md for the full GDScript and C# slot widget (drag/drop, hover preview), inventory grid layout, and tooltip wiring.
5. Serialization
Persist each slot as an {id, qty} pair (null for an empty slot), never the ItemData itself or its path. An ItemRegistry autoload loads res://items/ at startup and resolves ids back to items on load.
See references/serialization.md for the GDScript and C#
ItemRegistrywithserialize_inventory/deserialize_inventory; put the result in your save file (see save-load for itsversionfield).
Implementation Checklist
-
ItemDataextendsResourcewith a stableidstring set in the Inspector -
ItemDatafiles live underres://items/and are committed to version control -
Inventory.add_item()returns leftover count; callers handle a full inventory -
inventory_changedsignal drives all UI updates — UI never polls per-frame -
InventorySlot.remove_from_stack()clearsitemtonullwhen quantity reaches 0 - Equipment slots keyed by
SlotTypeenum, not by string, to catch typos at compile time -
Equipment.get_total_stat()is called when stats are needed, not cached unless profiling demands it - Serialization stores
id + quantityonly — never fullItemDataobjects or resource paths -
ItemRegistryloads items at startup; all deserialization goes through it - Drag-and-drop swaps slot contents directly then emits
inventory_changedonce -
max_stack_size = 1onEQUIPMENTandKEY_ITEMtypes to prevent stacking - All
push_error()messages include the class name and method for easy tracing