Skip to main content

The inventory manager

UEDInventoryManagerComponent is the inventory of an actor: every container it carries, in one component, answering questions about the actor rather than about any one bag.

Why one component

The alternative — a component per container — means every system that touches the inventory has to know how many bags exist and thread a reference to each one. Adding a stash becomes a component, a variable, and an edit to everything that counts items.

Here, adding a stash is a line in an array. And "how many arrows do I have" spans the quiver, the pack and the bag being worn without the caller knowing those exist.

Setup

Inventory = CreateDefaultSubobject<UEDInventoryManagerComponent>(TEXT("Inventory"));
Inventory->InitialContainers = { HotbarDef, MainDef, EquipmentDef };
Inventory->InitialItems.Add(FEDInitialItem{ StarterSword, 1, EquipmentTag });
PropertyMeaning
Initial ContainersBuilt on the authority at BeginPlay, in this order.
Initial ItemsGranted once every container exists — so an item that belongs in the quiver is never put in the pack just because the quiver was declared second.
Default Container QueryWhich container catches an item nothing else claims.
Stranded Item Handler ClassesWhat happens to contents when a container is removed.
Watched DefinitionsNarrows the total-changed event. Empty reports everything.
Max Session DistanceHow far the actor may get from an open world container before it closes.

Finding it

UEDInventoryManagerComponent* Inv = UEDInventoryManagerComponent::Find(SomeActor);

Find asks IEDInventoryOwner first and falls back to the actor's components. Implement the interface when the answer is not the obvious one — a Pawn forwarding to its PlayerState so the inventory survives a respawn.

Asking about containers

UEDContainer* Hotbar = Inv->GetContainer(HotbarTag);        // first match, hierarchical
Inv->GetContainersByTag(BagTag, OutContainers); // all of a kind
UEDContainer* One = Inv->GetContainerById(SavedGuid); // exactly one
Inv->GetContainersByItem(BackpackHandle, OutContainers); // what an item brought
Inv->GetContainers(OutContainers); // everything
Inv->GetContainersWithTags(TraitTags, OutContainers); // by descriptive tags on the definition

GetContainerDefinition(Tag) gives back the asset a container was built from, which is what a UI needs for a tab label or icon.

Acting on the whole inventory

These span every container and are authority-only.

CallBehaviour
TryAddItem(Definition, Count, PreferredContainer)Routed by data when no container is named. All-or-nothing per container — it does not spread one stack across several.
TryRemoveItems(Definition, Count)Spends across containers, smallest piles first. All-or-nothing overall.
TryRemoveItem(Handle)Removes one specific stack, wherever it is.
TryMoveToContainer(Handle, TargetTag, Count)Moves between this actor's own containers.
GetItemCount / HasItemSummed across everything.
FindItem(Handle, OutEntry)Which container holds a handle.
FindContainerFor(Definition, Count)Where an item would go — the honest answer for a "cannot carry that" prompt.

Naming a container turns a hint into an instruction: if that container refuses, the call fails rather than quietly putting the item elsewhere. That distinction matters for "equip this", which should fail loudly.

Gaining and losing containers at runtime

UEDContainer* Satchel = Inv->AddContainer(SatchelDefinition);   // a quest reward
Inv->RemoveContainer(Satchel); // taken away again
Inv->RemoveContainerById(Guid);
Inv->RemoveContainerByTag(Tag); // the first of a kind

A container that belongs to an item is added with AddNestedContainer(Definition, OwningItem) and cannot be removed on its ownRemoveContainer refuses it and says so. Removing it directly would leave the item convinced it still had storage, with nothing to reopen it. Move or remove the item and its container follows. See Nested containers.

What happens to the contents

Removing a container offers each item to stranded-item handlers in order; the first that takes it owns it.

HandlerWhat it does
UEDStrandedItemHandler_Rehome (default)Puts the item in another container the owner has, using the same placement rules a pickup would.
your ownSubclass UEDStrandedItemHandler in C++ or Blueprint — drop it at the player's feet, mail it, charge storage.

Anything no handler accepts is destroyed and logged as a warning. At that point the game has not said what should happen to a player's things, and neither silently deleting them nor leaking the container is an acceptable default.

Events

EventFires when
OnInventoryChangedAnything in any container changed. One binding covers containers gained later.
OnStackCountChangedAn item's actor-wide total changed. Quest counters, ammo readouts, pickup toasts.
OnContainersChangedA container was added or removed — or became identifiable on a client.
OnInitializationFinishedSetup finished, or gave up.
OnSessionOpened / OnSessionClosedA world container was opened or closed.

WhenReady(Callback) runs a callback once the inventory is usable, immediately if it already is. Every system that touches an inventory at startup hits the same race — containers are built in BeginPlay on the server and arrive later still on clients — and polling for it is what everybody writes instead, always slightly wrong.

Open containers

The manager also tracks which containers elsewhere in the world this actor has open, because that is a question about the actor too.

Inv->OpenSession(ChestContainer);    // safe from a client; the server validates range
Inv->CloseSession(ChestContainer);
Inv->HasOpenSession(ChestContainer);

The server owns the list and re-checks the distance, so walking away closes the screen through OnSessionClosed instead of leaving a chest usable from across the map. MaxSessionDistance sets the leash; 0 disables it. Nothing ticks until something is open.

Where next