Curitor
Gameplay Systems · Topic Guide

Inventory & Equipment

Carried stacks in a hotbar and bag, gear worn in five slots that shows on the character and boosts their stats — with mouse drag-and-drop and gamepad selection. Author items as assets, then include them in the character's Known Items list for saving and networking.

Items are assets

An ItemDefinition asset carries both representations of an item at once: a sprite for UI slots and a world model for dropped items and worn gear. Sprite is UI, model is world — an item with no model still works, and falls back to a generic placeholder crate when it lands on the ground.

Create one with Create → Party Core Kit → Inventory → Item Definition. Everything downstream — hotbar tiles, pickup prompts, drops, loot tables, the network layer, save files — reads the asset. Adding an item is authoring an asset, not editing code.

FieldTypeDefaultWhat it does
idstring—Unique machine-readable key, e.g. 'stone'. This is what travels over the network and into save files, so it is the one field you should not change after players have saves.
displayNamestring—Shown in the hotbar and in pickup prompts.
iconSprite—Hotbar tile icon. Optional — the tile falls back to the display name.
descriptionstring—Body text in the inventory window's details pane.
worldModelGameObject—3D model shown when the item lies in the world, and — for equipment — worn on the character. Null gives a generic placeholder crate.
worldModelDropScalefloat1Scale applied to the world model when the item is lying on the ground, so a wearable-sized mesh can read correctly as a pickup.
maxStackint1How many fit in one slot. 1 means every copy takes its own slot.
pickupActivationInteractionActivationPressHow pickups of this item activate: Press, Hold or a repeated press. Authored pickups and runtime drops both default to this, so a dropped item keeps behaving the way that item's pickups behave.
pickupHoldSecondsfloat1Hold duration, when the activation is Hold.
pickupPressCountint3How many presses, when the activation is a repeated press.

Carrying: the hotbar and the bag

CharacterInventory is one flat array of slots split into two visible regions: the leading slots are the hotbar, the rest are the bag. Selection cycles the hotbar; the bag is what the inventory window shows.

FieldTypeDefaultWhat it does
hotbarSlotsint6Quick-access slots shown on the hotbar. Selection cycles these.
bagSlotsint12Additional slots shown in the inventory panel, opened with the Inventory action (keyboard I, gamepad Select by default).
knownItemsItemDefinition[]—Every item that may appear in this inventory. Replicated item ids resolve back to assets through this list, exactly as CharacterStats resolves effects through knownEffects.
allowItemDroppingbooltrueAllow dropping items into the world by dragging a slot out of the window or right-clicking it. Off means items can only be moved between slots.

Wearing: five slots, and why bonuses live in stats

CharacterEquipment holds one EquipmentDefinition per slot — Head, Body, Legs, LeftHand, RightHand — and requires a CharacterInventory on the same object, because gear is moved in and out of the bag rather than existing separately from it.

An EquipmentDefinition is an ItemDefinition — it inherits every field above and adds the wearing ones. That inheritance is also the whole eligibility rule: the item type is the category. A piece fits a slot when it is equipment and its slot matches. There is no separate tag list to keep in sync.

// The entire eligibility check, from CharacterEquipment:
public static bool CanEquip(ItemDefinition item, EquipmentSlot slot)
{
    return item is EquipmentDefinition equipment && equipment.slot == slot;
}
FieldTypeDefaultWhat it does
slotEquipmentSlot—Which of the five slots this piece fits: Head, Body, Legs, LeftHand or RightHand.
modifiersStatModifier[]—Stat improvements while equipped. Each names a StatDefinition and a flat bonus added to that stat's MAXIMUM — for example +25 max health.
equipPositionVector3(0, 0, 0)Local position offset on the target socket or bone. Tune per item and per rig.
equipRotationVector3(0, 0, 0)Local rotation, in euler angles, on the target socket or bone.
equipScalefloat1Uniform scale for the worn visual.

Hand equipment first uses an authored Socket_Hand_L or Socket_Hand_R. Without those sockets, it falls back to the Humanoid hand bones. Other worn visuals attach to Humanoid bones — head to Head, body to Chest, legs to Hips, hands to LeftHand and RightHand. The mapping finds an attachment point; proportions still differ between characters, so check item offsets and grip orientation on your own rig. Rigid body equipment does not deform like skinned clothing.

Dropping and picking up

Dragging a slot out of the inventory window — or right-clicking it — drops the stack into the world as a physical pickup wearing the item's worldModel. The drop is a real object — a trigger sphere for range, a collider and a rigidbody so it falls and rests, and an InteractableBehaviourthat shows a prompt once you are close. Activating that prompt — press, hold, or repeated press, whichever the item's pickupActivation says — puts the stack back in your bag.

Dropped items belong to the world, not to whoever dropped them: every player can see and take one, and they survive a save. That last part takes a little machinery, because a runtime-spawned object cannot be "remembered" the way a scene object can — there is nothing in the loaded scene to restore onto. The save holds a recipe instead (which item, how many, where it came to rest) and rebuilds the drop. See Saving & Loading for the three shapes persistence uses.

Set allowItemDropping to false on CharacterInventory for a game where items should never reach the floor.

Giving a player something

  1. Author the item

    Create → Party Core Kit → Inventory → Item Definition. Set an id, a displayName, an icon, and a maxStack above 1 if it should pile up.
  2. Add it to knownItems

    On the player's CharacterInventory, append the asset to knownItems. Skipping this works offline and fails online, which is the worst way for it to fail.
  3. Hand it over

    Either place a pickup in the scene, add the item to a container's loot, or call AddItem from a rule or a script.
  4. Press play

    The tile appears in the hotbar, the details pane reads the description, and dropping it puts the world model on the floor.

The API, if you want it

The surface is deliberately small, and every mutating call returns bool so a refusal is something you can branch on rather than something you discover later.

using Curitor.PartyCoreKit.Inventory;
using UnityEngine;

public class StarterKit : MonoBehaviour
{
    [SerializeField] private CharacterInventory inventory;
    [SerializeField] private CharacterEquipment equipment;
    [SerializeField] private ItemDefinition stone;
    [SerializeField] private EquipmentDefinition helmet;

    private void Start()
    {
        // false when there is no room -- the item is NOT silently eaten.
        if (!inventory.AddItem(stone, 10))
            Debug.Log("Bag is full.");

        // Equip from whichever slot the helmet landed in; whatever was
        // worn goes back to the bag.
        if (inventory.AddItem(helmet))
        {
            for (int i = 0; i < inventory.SlotCount; i++)
            {
                if (inventory.GetSlot(i)?.item != helmet) continue;
                equipment.EquipFromInventory(i, EquipmentSlot.Head);
                break;
            }
        }
    }

    private void OnEnable()
    {
        inventory.OnSlotChanged += HandleSlotChanged;
        inventory.OnSelectionChanged += HandleSelectionChanged;
        equipment.OnEquipmentChanged += HandleEquipmentChanged;
    }

    private void OnDisable()
    {
        inventory.OnSlotChanged -= HandleSlotChanged;
        inventory.OnSelectionChanged -= HandleSelectionChanged;
        equipment.OnEquipmentChanged -= HandleEquipmentChanged;
    }

    private void HandleSlotChanged(int index) { }
    private void HandleSelectionChanged(int previous, int current) { }
    private void HandleEquipmentChanged(EquipmentSlot slot) { }
}

The rest of the surface: GetSlot, FindItem (id to asset, through knownItems), MoveSlot, DropFromSlot, RemoveFromSlot, RemoveFromSelected, SelectSlot and CycleSelection; on equipment, GetEquipped, CanEquip, EquipFromInventory, UnequipToInventory and DropEquipped.

The windows

The inventory window supports mouse drag-and-drop and a gamepad-native select-to-move flow; container windows use hover-for-details, click-to-take. All of it is built from the toolkit's themed widget kit, so restyling your game restyles these screens too.

  • Move a stack: Submit on its bag slot, then Submit on the destination. Submit on the same slot again to cancel. Each local player has an independent selection.
  • Equip: Submit on an item in your bag, navigate to its matching equipment slot, then Submit. An incompatible slot leaves the item and selection intact.
  • Unequip: with no bag item selected, Submit on worn equipment to return it to available bag space.
  • Store in an open chest: Submit on a non-empty bag slot deposits that stack, matching a mouse click. A full or unavailable chest keeps your item.

World dropping uses the pointer gestures described above; it has no dedicated gamepad gesture in this UI. Custom controls can call the inventory's drop methods if your game needs one.

The window is opened by the Inventory input action — I on keyboard, Select on a gamepad by default — routed through a HotkeyBehaviour and a Toggle Object instruction rather than hard-wired. Rebinding it is editing the input asset; re-pointing it at a different panel is editing one instruction, no code either way.

Multiplayer