Skip to content

Item System

Summary: The Item System uses UItemObject as the runtime instance and FItemManifest as the data template. Items are composed of fragments that define behavior, with FGuid instance IDs for reliable networking. The UItemRegistrySubsystem provides centralized lookup across all containers.

Table of Contents


Architecture Overview

UItemManifestDataAsset (Design-Time)
FItemManifest (Data Template)
    ├── FItemFragment[] (Composition via TInstancedStruct)
    │   ├── FGridFragment
    │   ├── FImageFragment
    │   ├── FItemNameFragment
    │   ├── FEquipmentFragment
    │   ├── FWeaponFragment
    │   └── ... (extensible)
FItemManifest::Manifest(Outer) ──► UItemObject (Runtime Instance)
    ├── FGuid InstanceId (Unique per instance)
    ├── int32 TotalStackCount
    ├── OwningContainer reference
    └── StateModules[] (Polymorphic per-instance runtime state)
        ├── FRemnantItemState    (Sealed/Awakened, echo mods, desire)
        ├── FCorruptedState      (future)
        └── ... (extensible — see Item State Modules)

Key Design Principles

Principle Implementation
Composition over Inheritance Items compose fragments instead of inheriting behavior
Template/Instance Split FItemManifest is the template, UItemObject is the instance
GUID-Based Identity Each item has unique FGuid for reliable networking
SubObject Replication Items replicate as subobjects of their container
Static Template + Mutable State Fragments carry template data; state modules carry per-instance runtime state

Core Concepts

Why Composition?

Traditional inheritance creates rigid hierarchies that are hard to extend:

UWeaponItem : UEquippableItem : UItem  ← Adding "quest weapon" is awkward

Fragment composition allows flexible item types:

Quest Weapon = FGridFragment + FImageFragment + FEquipmentFragment + FWeaponFragment + FQuestFragment

Any combination of fragments creates new item types without new classes.

UItemObject vs FItemManifest

Aspect FItemManifest UItemObject
Purpose Data template Runtime instance
Lifetime Copied on manifest Exists while item exists
Network Not replicated directly Replicated subobject
Ownership Data asset Container component
Identity None FGuid InstanceId

Runtime State Modules

Static fragment data (icon, stats, weapon class) answers what the item is. State modules answer what the item has become — per-instance mutable state that changes during play. A Remnant going from Sealed to Awakened, a hypothetical corrupted flag, a socket-link pattern — all land as polymorphic state module structs attached to UItemObject::StateModules.

Aspect Fragment State Module
Source UItemManifestDataAsset template Runtime code (factory, tracker, crafting)
Mutability Static within an instance's lifetime Mutates during play
Storage FItemManifest::Fragments UItemObject::StateModules
Persistence Reconstructed from ItemID template Serialized per-instance as typed JSON entry
Replication Rides with manifest ReplicatedUsing=OnRep_StateModules, broadcasts change delegate

Non-participating items (most of the catalogue) have an empty StateModules array — zero cost. Adding a new module type is one new struct; UItemObject stays closed. See Item State Modules for the full pattern, wrapper-JSON persistence shape, and worked examples.

Why TInstancedStruct?

Fragments use TInstancedStruct<FItemFragment> because: - Polymorphic storage in UPROPERTYs (editor-editable) - No UObject overhead for simple data - Efficient serialization and replication - Type-safe fragment queries


Item Data Flow

Creation (Manifest Process)

Data Asset
    ├─ FItemManifest with configured fragments
Manifest(Outer)
    ├─ Create UItemObject with Outer
    ├─ Copy manifest data to item
    ├─ Call Fragment::Manifest() on each fragment
    │   └─ Fragment initialization hook (e.g., set CurrentUsages = MaxUsages)
    ├─ Clear source fragments (prevents double-use)
UItemObject ready for use

Fragment Query Pattern

Fragments are queried by type, not by index:

Method Purpose
GetFragmentOfType<T>() Get single fragment (const)
GetFragmentOfTypeMutable<T>() Get single fragment (mutable)
GetAllFragmentsOfType<T>() Get all fragments of type
HasFragmentOfType<T>() Check if fragment exists

Query iteration is O(n) over fragments. For hot paths, consider caching results.


Instance Identity

Why GUIDs?

Pointers are unreliable across network boundaries: - Client and server have different memory addresses - Pointer values change on respawn/level travel - Cannot serialize pointers for RPCs

FGuid provides: - Consistent identity across all machines - Stable reference for RPC parameters - Desync detection capability

GUID Lifecycle

Item Created (Server)
    ├─ InitializeInstanceId() → FGuid::NewGuid()
Item Replicated (To Clients)
    ├─ InstanceId replicates with item
RPC Operations
    ├─ Client sends: Server_PlaceItemById(ItemGuid, TargetIndex)
    ├─ Server finds: Registry->FindItemByInstanceId(ItemGuid)
    └─ Server validates: Item exists and matches expected state

Desync Detection

When client and server disagree about item state: 1. RPC includes item GUID and expected position 2. Server looks up item and verifies position 3. If mismatch, server forces resync via MarkArrayDirty()


Item Registry

UItemRegistrySubsystem

Central lookup service for all items across all containers:

UItemRegistrySubsystem (GameInstance Subsystem)
    ├── RegisteredContainers[]
    │   ├── Player Inventory
    │   ├── Player Equipment
    │   ├── Stash
    │   └── World Containers
    └── ItemToContainerCache (lazily rebuilt)

Container Auto-Registration

Containers register automatically: - BeginPlay()Registry->RegisterContainer(this) - EndPlay()Registry->UnregisterContainer(this)

Cross-Container Operations

Method Purpose
FindItemByInstanceId(Guid) Find item anywhere
FindContainerForItem(Item) Get item's current container
MoveItemToContainer(Item, Target, Position) Transfer between containers
DropItemToWorld(Item, Count, Player) Spawn as world pickup

Replication Strategy

Replicated Properties

Property Purpose
ItemManifest Full fragment data
TotalStackCount Current stack size
InstanceId Unique identity
StateModules Per-instance runtime state (via OnRep_StateModulesFOnItemStateModulesChanged)

Server-side mutation of a state module's fields requires calling UItemObject::MarkStateModulesDirty() after the edit — UE's replication layer doesn't auto-detect sub-struct changes inside a TArray<TInstancedStruct<>>. See Item State Modules.

SubObject Pattern

Items don't replicate independently. They replicate as subobjects of their container:

UItemContainerComponent
    ├─ IsSupportedForNetworking() → true
    ├─ bReplicateUsingRegisteredSubObjectList → true
    └─ AddReplicatedSubObject(ItemObject)
        └─ Item replicates with container

Benefits: - Items only exist where containers exist - Automatic cleanup when container destroyed - Delta replication via FastArray


Public Contracts

UItemObject

Method Purpose
GetItemManifest() Get item data (const)
GetItemManifestMutable() Get item data (mutable)
GetInstanceId() Get unique GUID
GetTotalStackCount() Get current stack size
SetTotalStackCount(Count) Set stack size
IsStackable() Check if item can stack
GetMaxStackSize() Get maximum stack size
GetOwningContainer() Get container reference
GetStateModule<T>() / GetStateModuleMutable<T>() Typed state module lookup
AddStateModule<T>(Initial) Add if absent, no-op if present
RemoveStateModule<T>() Remove by type
MarkStateModulesDirty() Server-side. Forces net update after mutation.

FItemManifest

Method Purpose
Manifest(Outer) Create UItemObject instance
GetItemType() Get item type tag
GetItemID() Get unique item ID string
GetDimensions() Get grid size for inventory
GetFragmentOfType<T>() Query fragment by type
GetCompatibleContainers() Valid container types

UItemRegistrySubsystem

Method Purpose
RegisterContainer(Container) Add container to registry
UnregisterContainer(Container) Remove container from registry
FindItemByInstanceId(Guid) Global item lookup
FindContainerForItem(Item) Get item's container
MoveItemToContainer(Item, Target, Position) Cross-container transfer

Source Reference

Core Classes

File Location Purpose
ItemObject.h Inventory/Items/ItemObject.h Runtime item instance
ItemObject.cpp Inventory/Items/ItemObject.cpp Instance implementation
ItemManifest.h Inventory/Items/Manifest/ItemManifest.h Data template struct
ItemFragment.h Inventory/Items/Fragments/ItemFragment.h Fragment base and types
ItemRegistrySubsystem.h Inventory/SubSystems/ItemRegistrySubsystem.h Central lookup service
ItemManifestDataAsset.h Inventory/Items/Manifest/ItemManifestDataAsset.h Design-time asset

Key Functions

Function File Line Purpose
Manifest() ItemManifest.cpp - Create item instance
InitializeInstanceId() ItemObject.cpp - Generate GUID
GetFragmentOfType<T>() ItemManifest.h - Fragment query
FindItemByInstanceId() ItemRegistrySubsystem.cpp - Global lookup


Recent Changes

Date Change Impact
2026-04 State modules (TArray<TInstancedStruct<FItemInstanceStateModule>> on UItemObject) Per-instance runtime state layer for Remnant and future mechanics. See Item State Modules.
- Initial fragment composition system Items composed of fragments, not inheritance
- GUID-based identity Reliable networking for item operations
- Registry subsystem Cross-container item lookup
- SubObject replication Items replicate with containers