ItemVaultRuntime is the scene-owned composition root for ItemVault services.
Use it in scenes that need an item database, pickup event routing, and an
inventory target.
Why It Is Called a Bridge
Pickup nodes emit PickupEvent values. Inventories mutate state. The
InventoryPickupBridge is the small service between those two responsibilities:
PickupEvent -> InventoryPickupBridge -> Inventory or Wallet
This is intentionally tight integration with ItemVault inventory, but it avoids hardwiring every pickup node directly to one inventory instance. The runtime owns the connection for the scene, so tests, menus, split-screen scenes, and temporary demo scenes can each have isolated inventory routing.
What Runtime Owns
| Service | Role |
|---|---|
ItemVaultBus |
Scoped signal bus for item events. |
ItemDatabase |
Lookup table for ItemDefinition.id. |
InventoryPickupBridge |
Routes PickupEvent.stack into an ItemVault inventory target. |
You can use the services directly in your own composition root, but
ItemVaultRuntime is the ready-made wiring node.
Basic Setup
var runtime := ItemVaultRuntime.new()
add_child(runtime)
runtime.database_dirs = ["res://items"]
runtime.auto_reload_database = true
runtime.set_inventory_target(player_inventory)
runtime.initialize()
ItemVault's Inventory type is the primary target. Advanced game-owned adapters
can provide the same method shape:
func add(stack: ItemStack) -> ItemStack:
# Return null if fully accepted.
# Return overflow stack if only partially accepted.
return null
Connect Pickup Nodes
Connect pickup nodes when they enter the scene or when you spawn them:
if not runtime.connect_pickup(pickup):
push_warning("Pickup was not connected to ItemVaultRuntime.")
connect_pickup() returns false when the node is null, already connected, or
does not expose the expected picked_up signal.
Disconnect when a pickup should stop routing through the runtime:
runtime.disconnect_pickup(pickup)
Bridge Behavior
InventoryPickupBridge listens for PickupEvent values. When enabled, it:
- Ignores null events, null stacks, or missing targets.
- Sends currency stacks to
Inventory.walletwhen the item category is&"currency"and the target exposes a wallet. - Otherwise calls
target.add(event.stack). - Emits
pickup_rejected(event, overflow)when the target cannot accept the full stack.
Use this signal to show feedback when the inventory is full:
runtime.bridge.pickup_rejected.connect(_on_pickup_rejected)
func _on_pickup_rejected(event: PickupEvent, overflow: ItemStack) -> void:
print("Could not pick up %s x%s" % [overflow.item.id, overflow.quantity])
Overflow Behavior: What Happens When the Inventory Is Full
When a pickup is taken but the inventory cannot accept the full stack, the
bridge writes the overflow onto PickupEvent.overflow. After the picked_up
signal finishes, take() checks the event and decides what to do:
| Outcome | Condition | What happens |
|---|---|---|
| Fully accepted | event.overflow == null |
Pickup frees or disables. Standard happy path. |
| Fully Rejected | event.overflow.quantity >= event.stack.quantity |
Pickup stays in the world with its original stack. Metadata is restored so the pickup is still collectible. |
| Partially Accepted | event.overflow.quantity < event.stack.quantity |
Pickup stays in the world. Metadata is updated to the overflow quantity so the remaining items can be collected later. |
This means items are never lost — a pickup that cannot be fully absorbed stays in the game world with whatever quantity could not fit.
Reading the Outcome in Your Game Code
After take() returns, check the event:
var event := pickup.take(collector)
if event.fully_accepted():
# Normal success — play pickup sound, show "Item acquired" toast.
pass
elif event.fully_rejected():
# Inventory was completely full — nothing was taken.
# Show "Inventory full" UI feedback.
pass
else:
# Partially accepted — some items went in, rest remain on the ground.
# event.overflow.quantity is how many did NOT fit.
print("Took some, %d remaining" % event.overflow.quantity)
Customizing Overflow Behavior
The default behavior (keep the pickup alive with overflow) is safe but you may want different game-feel. Common alternatives:
Destroy overflow (arcade style):
# After take(), if partially accepted, free the pickup anyway.
var event := pickup.take(collector)
if not event.fully_accepted():
pickup.root.queue_free() # discard remaining items
Refuse entirely ( Souls-like):
# Check capacity BEFORE calling take().
var bridge := runtime.bridge
# Use inventory query API to check space:
var can_fit := player_inventory.query_containers("", stack.quantity).size() > 0
if not can_fit:
show_message("Cannot carry more")
return
var event := pickup.take(collector)
Drop overflow as a new world item:
var event := pickup.take(collector)
if event.overflow != null and not event.fully_accepted():
# Spawn a new Pickup2D at this position with the overflow stack.
spawn_world_item(pickup.global_position, event.overflow)
Currency Pickups
Currency stacks (category "currency") route to the wallet, which has no
capacity limit. Currency pickups are always fully accepted — overflow does
not apply to wallet-routed items.
Scene-Owned Runtime, Game-Owned Architecture
ItemVault does not require a global singleton. If your game has a global
inventory service, keep that service in the game and pass the relevant target
into ItemVaultRuntime.
For GECS or other world-entity systems, keep the adapter at the game boundary:
consume the ItemVault ItemStack, then pass stack.to_dict() or mapped
component data into the game-owned world.
Disable Automatic Routing
If a scene should receive pickup events but not add items automatically, disable the bridge:
runtime.bridge_enabled = false
runtime.initialize()
Troubleshooting
| Problem | Fix |
|---|---|
| Pickup signal fires but inventory does not change | Confirm runtime.set_inventory_target(...) points at an ItemVault Inventory or adapter with add(stack). |
connect_pickup() returns false |
Confirm the pickup node exposes picked_up and is not already connected. |
| Currency does not enter wallet | Confirm the item category is &"currency" and the target exposes a wallet. |
| Items route between unrelated scenes | Use one ItemVaultRuntime per scene scope instead of a global bridge. |
What's Next
- Read
getting-started.mdfor the minimal setup path. - Read
item-data-and-stack-metadata.mdfor the pickup stack handoff. - Read
json-catalogs-and-saves.mdfor stack dictionaries and persistent world drops.
Source
addons/item_vault/guides/runtime-and-pickup-bridge.md
Plugin docs root:gdscript/plugins/item_vault_dev/addons/item_vault/guides