Inventory System

The Inventory System package was heavily refactored in the 1.5.x architecture shift.

It now follows a more modular runtime layout and is designed to work well with the Data package and CatalogKey-driven authoring patterns.

What This System Solves

  • defining items and item instances
  • moving items between inventories/slots
  • building UI views (inventory, equipment, tooltips)
  • composing item behavior (equip, consume, stat preview, etc.) via features

Item database (IInventoryItemDatabase)

UI and inventory code take IInventoryItemDatabase (lookup definitions, icons, CreateItem, etc.). Resolve it with ServiceLocator.Get<IInventoryItemDatabase>() after registration.

Default implementation: InventoryItemDatabaseSO — a ScriptableObject that reads item definitions from IAssetCatalog<InventoryItemDefinitionSO> / ICatalog registered under the items provider id (same pattern as samples). Use the asset as-is, subclass it, or provide your own IInventoryItemDatabase and register that instance against the interface.

The old InventoryItemDatabase type name is removed; migrate your ItemDatabase (or similar) to InventoryItemDatabaseSO or IInventoryItemDatabase.

Item definition assets (InventoryItemDefinitionSO)

Definitions are always DataSO<InventoryItemDefinition> via InventoryItemDefinitionSO. There is no InventoryItemDefinitionSO<T>—catalogs and samples expect that concrete SO type.

  • Optional thin subclass for menu naming only: class EquipmentSO : InventoryItemDefinitionSO { } with [CreateAssetMenu(...)].
  • Equipment stats / slot type: use EquipableFeature (and related RPG bridge features) on InventoryItemDefinition.Features, instead of a parallel generic definition type on the SO.

Key Ideas

  • Data-first definitions: item “definitions” are authored as data (good for JSON, remote content, versioning).
  • Key-based asset lookup: fields like icons can be stored as keys (e.g. CatalogKey) while still showing rich inspector previews.
  • Feature composition: items gain gameplay capabilities by attaching feature modules, instead of relying on deep inheritance trees.

Tooltips: stat lines moved to features

Inventory tooltips no longer call a virtual like GetItemStatDisplayLines on definitions. The slot tooltip builder calls InventoryItem.BuildItemTooltipStats, which loops InventoryItemDefinition.Features and, for each IItemFeature, calls BuildTooltipContent(VisualElement host, in ItemTooltipContext context).

Migrate custom stat/ingredient lines by implementing IItemFeature (plus IFeature.CloneFeature), add an instance to the item definition’s Features list, and build VisualElement children on the host (see ItemTierFeature for a small example, EquipableFeature for RPG stat text). Use ItemTooltipContext for IInventoryItemDatabase, optional attribute-name filtering, and equipment comparison via GetComparison.

Note: InventoryItemDefinition.Features is typed as List<IFeature> (the base interface). If your project has other IFeature sub-interfaces (e.g. unit feature definitions), the Inspector dropdown will show all types. To restrict the dropdown to inventory features only, change the list type to List<IItemFeature> and register a [CustomPropertyDrawer(typeof(IItemFeature), true)] that scans only for IItemFeature types. See Sub-interface type filtering for the full pattern.

ItemTierFeature tooltips resolve the tier line through IValueCatalog registered under InventoryConstants.ItemTierCatalogId (GetDisplayValue on the effective CatalogKey, fallback to the raw key). Slot USS on the Background button adds a slug derived from that display when available (e.g. common from Common), otherwise from the key (e.g. 0 stays 0). Style tier chrome in USS against those slugs; ClearSlotVisual removes slugs and legacy raw-key classes for all keys listed in the tier ICatalog.

The tooltip's tier line ("Tier: Rare") carries item-tooltip-tier (ItemTierFeature.TooltipTierUssClass) plus a modifier with the same slug (item-tooltip-tier--rare), and since 0.4.8 no inline colour. The tier is a modifier rather than the bare slug, so the rules that style your slots by tier (.rare) stay off the line. Without a theme rule it takes the tooltip's label colour (in Luna's default theme the light --color-neutral-50, close to the white it had before). A theme on a light tooltip card colours it:

css
/* every tier line, then one tier */ .item-tooltip-tier { color: var(--my-ink); } .item-tooltip-tier--rare { color: var(--my-rare-ink); }

Before 0.4.8 the line was painted white inline, and an inline style beats every sheet, so no theme could darken it.

Slot template: the art element and the amount (0.4.8)

InventoryItemSlotController binds a slot template by element names:

NameElementRole
BackgroundButton (required)The slot: clicks, the tooltip, drag, the selected and tier classes
Iconany VisualElement (optional)The item art
TopLeft, TopRight, BottomLeftLabel (optional)Free corner labels
BottomRightLabel (optional)The amount

The art element. When the template has an Icon element (InventoryItemSlotController.IconElementName), the slot paints the item art and the empty icon (SetEmptyIcon) on it, and the tooltip reads the art from it. The drag ghost is a fresh copy of the same template, so it paints its art on its own Icon. Without one, the art is the Background button's own background-image, as before. Read the art from slot.ArtElement (the Icon element, else the button), not from Background.style.backgroundImage. When upgrading: if your slot template already names another element Icon (a badge, a lock), the art now goes there, so rename that element.

The art on its own element lets the slot's face carry layers: a baked fx underlay or a soft shadow under the art, a gloss over it. On the button itself the art is the button's background-image, and every child (an underlay included) paints over it. Give the element picking-mode="Ignore" like the corner labels, and size it in USS:

xml
<ui:Button name="Background" class="my-slot"> <ui:VisualElement name="Icon" picking-mode="Ignore" class="my-slot__art" /> <ui:Label name="BottomRight" picking-mode="Ignore" /> </ui:Button>
css
.my-slot__art { position: absolute; left: 12px; top: 12px; right: 12px; bottom: 12px; -unity-background-scale-mode: scale-to-fit; }

The amount. A stack above one shows its amount on BottomRight as x8 by default; a single item shows none. Override FormatAmount(int) in a slot subclass for your own format:

csharp
public sealed class MySlotController : InventoryItemSlotController { // ... constructor ... // A bare numeral on a count chip: "8", not "x8". protected override string FormatAmount(int amount) => amount.ToString(); }

Slot click and the tooltip (0.3.2)

A slot click closes the tooltip, runs your OnClick handler, then re-shows the tooltip — right for inspect-style clicks, wrong when the handler moved, replaced, or consumed from the item (place-into-loadout flows): the stale tooltip floated over the new state until the pointer left the slot. Since 0.3.2 the re-show requires the same item instance at the same amount; handlers that mutate the slot get a clean close with no re-show. No consumer changes needed — delete any deferred close-all workarounds.

Grid data modes (0.4.2)

The 0.4.1 release carried only a version bump; these modes ship in 0.4.2.

InventoryViewPagination reads its items in one of two ways, chosen at construction:

  • Bound (inventory given): the view pages the live InventoryBase.Items and re-reads them on Refresh(). Attach it to the screen or modal view with AttachToView(uiView) and it also rebuilds on every show — fade-in and occlusion reveal alike — so a grid built at scene boot never pages a stale snapshot. SetItemList on a bound view is a caller bug and logs an error (it was a silent no-op before 0.4.2).
  • Unbound (inventory: null): the view pages whatever list you pass to SetItemList — a gift picker showing only giftable items, a shop stock, a reward roll. Call Refresh() after mutating that list in place.
csharp
// bound: live inventory, refreshes itself on every show var grid = new InventoryViewPagination(itemDatabase, player.Inventory, slotTemplate, gameObject, root, container, tooltip); grid.AttachToView(UIView); // unbound: a curated list var picker = new InventoryViewPagination(itemDatabase, null, slotTemplate, gameObject, root, container, tooltip); picker.SetItemList(giftableItems);

Filter chips (0.4.3)

ShowOnlyFilterButtons (hide filter chips by index) is removed. Declare the group's universe on the grid instead: view.SetFilterOptions(0, new[] { FilterIds.Meal, FilterIds.Potion }). Values outside the set are ignored, their chips are hidden, and with nothing active the grid shows the union of the allowed values, so no click sequence can widen a curated view (Luna GridViewBase, 2.1.4).

Suggested Mental Model

  • Definition: “what this item type is” (static data)
  • Instance: “a concrete owned item” (runtime state like stack count, durability, rolled stats)
  • Slot: “a location that can hold an item” (inventory slot, equipment slot, hotbar slot)
  • Feature: “optional behavior attached to a definition/instance” (equipable, consumable, stat provider, etc.)

Package Layout (Common Areas)

  • Runtime/Item/Core: definitions, references, item databases
  • Runtime/Item/Features: feature modules (compose behavior)
  • Runtime/Item/Slot: slot controllers, visuals, tooltip builders
  • Runtime/UI: inventory/equipment UI controllers and views
  • Runtime/ItemStats: stat line data and controllers (UI-friendly formatting)
  • Data.DropTable/Runtime: weighted drop sources and roll helpers — see Drop Table

Integration Points

  • Data Package: makes “definitions as data” practical (default vs actual workflows, JSON tools).
  • Data Catalogs + CatalogKey: decouple definitions from direct Unity asset references while keeping authoring comfortable.
  • RPGStats (optional): when you want item features to drive stat deltas, comparisons, and equipment rules.

What Changed in 1.5.0 (High-Level)

  • inventory became more modular (separated responsibilities and assemblies)
  • moved toward feature-driven item behavior
  • stronger emphasis on key-based references and data-driven definitions

Settings

Theme

Light

Contrast

Material

Dark

Dim

Material Dark

System

Sidebar(Light & Contrast only)

Light
Dark

Font Family

DM Sans

Wix

Inclusive Sans

AR One Sans

Direction

LTR
RTL