Articles ·
Godot inventory system tutorial
Build a complete Item Vault inventory for a 2D or 3D Godot 4 game: pickups, enemy drops, stacking, crafting, equipment, and save and load sharing one item model.
Watch the Item Vault walkthroughThis tutorial builds one inventory pipeline end to end: world pickups land in a player inventory, stacks merge by item id, recipes consume ingredients through an atomic craft, an equipment set reads from the same items, and the whole thing saves and loads. Every stage shares one item model, so nothing is defined twice.
Item Vault is an addon for Godot 4, not a ready-made inventory screen: it owns the item model, containers, and their save format, and your game owns the scenes and UI.
It assumes the minimal setup from
Getting Started: item definitions in an ItemDatabase,
an inventory target, and pickup routing through ItemVaultRuntime.
1. Pickups route into inventory
Item Vault uses scene-owned runtime services. Add ItemVaultRuntime to
scenes that need item database and pickup-to-inventory behavior, and let
InventoryPickupBridge carry pickup events into an InventoryTarget.
Your game never hand-moves items from the world into a bag: the pickup event
fires, the bridge routes it, the target receives it.
Keep pickup scenes as the only world-facing item code. Everything downstream works with inventory containers, never with the pickup node.
2. Stacks merge by item id
Define one ItemDefinition per item type. The id is the stable save and
load key, so use short string names like &"iron_ore", never file paths.
max_stack caps how many units one stack holds:
var ore := ItemDefinition.new()
ore.id = &"iron_ore"
ore.display_name = "Iron Ore"
ore.max_stack = 32
ore.category = &"ore"
_db.register(ore)Hold stacks in an ItemContainer built from a ContainerType:
var ct := ContainerType.new()
ct.id = &"player"
ct.max_stacks = 12
ct.slot_based = false
var backpack := ItemContainer.new(ct)Adding a stack returns the overflow, so the caller always knows what did not fit. Removing by id and quantity keeps counts exact:
var overflow: ItemStack = backpack.add(ItemStack.new(def, qty))
var missing: int = 0 if overflow == null else overflow.quantity
backpack.remove(&"iron_ore", spent)Put stack metadata (tags, stack overrides, world pickup data) in the layer Item Data and Stack Metadata describes. Item ids stay stable; presentation details live beside them, not inside them.
3. Craft through one atomic transaction
Crafting is an optional Item Vault module under
addons/item_vault/crafting/. It is built on the same item and inventory
types, so recipes reference real definition ids and the craft mutates real
containers. Author a recipe from CraftingIngredient lines, register it in a
CraftingRecipeRegistry, and run it through CraftingService, which
validates, preflights, and commits through one atomic
CraftingTransaction. The typed CraftingResult tells you exactly what was
consumed and produced, with stable failure reasons when the craft cannot run.
Because the transaction is atomic, a failed craft changes nothing: no half-eaten ingredients, no duplicated outputs. Read results, never intermediate container states. The full authoring reference is Crafting Integration.
4. Equipment stays game-owned
Item Vault ships no equipment classes, and that is deliberate. Equipment
rules (which slots exist, what each accepts, what stats change) belong to
your game. The inventory side of the pattern is simple: hold the equipped set
in its own ItemContainer and move stacks between it and the backpack with
the same add and remove calls as any other transfer. The container enforces
counts and capacities; your game enforces fit.
Connect stat and gameplay effects only after the inventory change succeeds, never before. The pattern for reacting to committed changes (healing, equipment bonuses, quest hooks) is documented in Game-Owned Gameplay Integration.
5. Save the envelope, not the nodes
Inventory state saves as JSON through the schema in Save Schema: schema version, inventory shape, containers, stacks, and typed instance resources. Item ids are the stable keys, so a save written today still resolves after you rearrange scenes, as long as definition ids do not change.
Crafting-domain state persists separately through CraftingSave, next to the
inventory envelope rather than inside it. Keep the two envelopes in one game
save file with independent version handling, so a crafting change never
forces an inventory migration.
6. Use the same inventory in 2D and 3D
Nothing in the pipeline above is dimension-specific. Item definitions,
containers, stacks, and save envelopes are data, so a 2D and a 3D scene share
one ItemDatabase and the same container methods. Only the world-facing side
differs: a SceneSpawner2D or SceneSpawner3D fires the spawn event, and the
pickup bridge routes it into the same target.
SpawnEvent and PickupEvent carry a dimension field, but the plugin
documents it as diagnostics only and does not route on it. Do not branch on
it. Narrow on the spawner's node type when you need Vector3 or Vector2
math, as
Dimension-Neutral Handling
explains.
7. Drops roll once and pick up through the same bridge
An enemy death is a drop site, not a special inventory path: roll what to drop, spawn a pickup scene, and let the bridge from section 1 carry it into the target. Keep one roll site per game, or two enemy scripts will disagree about the odds.
const DROP_CHANCE := 0.35
func _on_enemy_died(at: Vector2, pickup_scene: PackedScene, item_id: StringName) -> void:
if randf() > DROP_CHANCE:
return
var pickup := pickup_scene.instantiate()
pickup.position = at
pickup.item_id = item_id
add_child(pickup)When the table itself should be data an item designer edits, author it as a drop-table resource instead of hard-coding ids in the enemy scene; the roll call does not change. Keep one table per enemy type, not per instance: a per-instance table is how two identical slimes end up dropping different loot.
Two rules prevent the usual bugs:
- Roll the stack, not the slot. Five ore is one pickup carrying a stack of five, not five pickups the player has to walk over.
- Let the inventory decide overflow. The bridge returns what did not fit. Spawn that remainder back into the world instead of dropping it on the floor of the function, or a full bag silently eats the drop.
Drop weights, guaranteed drops and one-time drops are game design. Item Vault owns the item model and the pickup route, not your loot table.
Common mistakes
- Rolling loot in two places. Enemy scripts and a spawner both rolling means double drops nobody can reproduce.
- Deleting the pickup when the inventory is full. Return the overflow to the world instead of losing it.
- Using file paths as item ids. Rename one file and every save breaks.
- Moving items into equipment before the transfer succeeds. Commit first, react second.
- Reading a container mid-craft. Wait for the typed result.
- Saving node paths or scene state as inventory data. Save the JSON envelope; rebuild nodes from it on load.
- Duplicating item facts (names, stack sizes) in UI scripts. The
ItemDatabaseis the single source; UI reads from it.
Try the demo
The Item Vault demo plays this whole pipeline: pickups, grid transfers, crafting bench, shop, and save and load. Try it in your browser on the Item Vault itch.io page before building your own.