Item System¶
Summary: The Item System uses
UItemObjectas the runtime instance andFItemManifestas the data template. Items are composed of fragments that define behavior, withFGuidinstance IDs for reliable networking. TheUItemRegistrySubsystemprovides centralized lookup across all containers.
Table of Contents¶
- Architecture Overview
- Core Concepts
- Item Data Flow
- Instance Identity
- Item Registry
- Replication Strategy
- Public Contracts
- Source Reference
- Related Systems
- Recent Changes
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:
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_StateModules → FOnItemStateModulesChanged) |
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 |
Related Systems¶
- Item Fragments - Fragment types and composition
- Inventory System - Container management
- Equipment System - Equipment handling
- Loot System - Item generation and drops
- GAS Overview - Attribute integration
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 |