The Universal Item System is a modular, data-driven item management framework that ships alongside OmniShift. It provides a unified approach to defining, storing, equipping, and interacting with in-game items. While fully usable as a standalone system, it integrates natively with OmniShift's GAS (Gameplay Ability System) components, character attributes, interaction hand system, and procedural animation rig.
Version: 1.0.0
Engine Compatibility: Unreal Engine 5.6+
Developer: MountainLabs UG
License: Proprietary
GameplayTag system for queries, filtering, and UI displayUUniversalItemSubsystem
├── Item Definition Registry
│ ├── Data Asset Loading
│ ├── Item ID Resolution
│ └── Definition Caching
├── Inventory Manager
│ ├── Slot Management
│ ├── Stack Handling
│ └── Capacity Enforcement
├── Equipment Manager
│ ├── Slot Mapping
│ ├── Stat Application (via GAS)
│ └── Visual Updates (mesh/skeletal mesh attachment)
├── Loot & Spawning
│ ├── Loot Tables
│ ├── Drop Generation
│ └── Rarity Weights
└── Persistence
├── Save State Serialization
├── Load State Deserialization
└── Delta Compression
The Item System connects to OmniShift through several dedicated integration points:
| Item System | OmniShift Component | Integration |
|---|---|---|
| Stat Modifiers | GASPAC_AttributeSet_Core |
Equipment applies modifiers to Health, Stamina, Mana, MoveSpeed |
| Item Abilities | GASPAC_AbilitySystemComponent |
Items can grant gameplay abilities on equip |
| Item Pickup | GASPAC_InteractionHandComponent |
Interaction hand detects and picks up world items |
| Equipment Mesh | GASPAC_ProceduralRigComponent |
Equipped items attach to rig bones with IK adjustments |
| Inventory UI | GASPAC_CameraManagerComponent |
Camera mode transitions for inventory inspection |
Every item type is backed by a UUniversalItemDefinition Data Asset that describes its static properties. This is the single source of truth for what an item is.
UCLASS(BlueprintType)
class UUniversalItemDefinition : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
// Unique identifier for this item type
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
FGuid ItemId;
// Display name (supports localized text)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
FText DisplayName;
// Description shown in tooltips
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
FText Description;
// Inventory / tooltip icon
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
UTexture2D* Icon;
// Maximum quantity per stack (1 = unique/no-stacking)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
int32 MaxStackSize = 1;
// Item rarity tier
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
EUniversalItemRarity Rarity = EUniversalItemRarity::Common;
// Gameplay tags for filtering and queries
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
FGameplayTagContainer ItemTags;
// Custom instance class (for special item behavior)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
TSubclassOf<UUniversalItemInstance> InstanceClass;
// Stat modifiers applied when equipped (OmniShift GAS integration)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
TArray<FUniversalItemStatModifier> StatModifiers;
// Arbitrary numeric properties (damage, heal amount, armor, etc.)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
TMap<FName, float> NumericProperties;
// Behavior flags
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Behavior")
bool bCanBeDropped = true;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Behavior")
bool bCanBeDestroyed = true;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Behavior")
bool bCanBeTraded = true;
// World representation
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|World")
TSoftClassPtr<AActor> WorldPickupClass;
// Equipment slot this item fits (None = not equippable)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Equipment")
EUniversalEquipmentSlot EquipmentSlot = EUniversalEquipmentSlot::None;
};
UniversalItemDefinition as the parent classDisplayName, Description, and IconMaxStackSize (1 for equipment, >1 for consumables/materials)ItemTags for filtering (e.g., Item.Weapon.Sword, Item.Consumable.Potion)EquipmentSlot and add StatModifiersNumericProperties (e.g., "HealAmount": 50.0)Runtime state is tracked by UUniversalItemInstance, allowing per-item data like durability or charges:
UCLASS(BlueprintType, DefaultToInstanced, EditInlineNew)
class UUniversalItemInstance : public UObject
{
GENERATED_BODY()
public:
// Unique instance identifier (auto-generated)
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Instance")
FGuid InstanceId;
// Reference to the definition (soft pointer)
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Instance")
TSoftObjectPtr<UUniversalItemDefinition> Definition;
// Current stack count
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Instance")
int32 Quantity = 1;
// Per-instance property overrides (durability, charges, enchant level)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Instance")
TMap<FName, float> InstanceProperties;
// Attached gameplay components (buffs, enchantments, etc.)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Instance")
TArray<UUniversalItemComponent*> Components;
};
Items are categorized using UE's Gameplay Tag system, which allows hierarchical filtering:
Item
├── Weapon
│ ├── Melee
│ │ ├── Sword
│ │ ├── Axe
│ │ └── Dagger
│ └── Ranged
│ ├── Bow
│ ├── Staff
│ └── Firearm
├── Armor
│ ├── Helmet
│ ├── Chest
│ ├── Gloves
│ ├── Boots
│ └── Shield
├── Consumable
│ ├── Potion
│ ├── Food
│ └── Scroll
├── Material
│ ├── Ore
│ ├── Wood
│ └── Cloth
└── Misc
├── Key
├── Quest
└── Currency
Query items by category:
// Find all melee weapons
TArray<UUniversalItemDefinition*> MeleeWeapons;
for (UUniversalItemDefinition* Def : Subsystem->GetAllDefinitions())
{
if (Def->ItemTags.HasTag(FGameplayTag::RequestGameplayTag("Item.Weapon.Melee")))
{
MeleeWeapons.Add(Def);
}
}
| Rarity | Enum Value | Color | Weight Multiplier | Typical Use |
|---|---|---|---|---|
| Common | Common |
Gray | 1.0x | Basic materials, starter gear |
| Uncommon | Uncommon |
Green | 0.6x | Upgraded crafted items |
| Rare | Rare |
Blue | 0.3x | Named equipment, rare drops |
| Epic | Epic |
Purple | 0.1x | Powerful unique items |
| Legendary | Legendary |
Orange | 0.03x | Best-in-slot, one-of-a-kind |
Rarity affects:
Items define arbitrary key-value numeric properties via NumericProperties on the definition:
| Property Key | Type | Example Items |
|---|---|---|
Damage |
float | Weapons |
Armor |
float | Armor pieces |
HealAmount |
float | Health potions |
ManaRestore |
float | Mana potions |
Durability |
float | Equipment |
Weight |
float | All items (encumbrance) |
SellPrice |
float | All items |
Range |
float | Ranged weapons |
Access properties at runtime:
float Damage = ItemDef->NumericProperties.FindRef("Damage"); // 0.0 if not set
The UUniversalInventoryComponent is an Actor Component that manages item storage:
// In your character or player controller constructor
UUniversalInventoryComponent* Inventory = CreateDefaultSubobject<UUniversalInventoryComponent>(TEXT("Inventory"));
Inventory->SetCapacity(20); // 20 slots
Or in Blueprint:
UniversalInventoryComponent to your character BlueprintInitial Capacity in the Details panelRarity Config for color display// Add an item (returns true if successful)
FUniversalItemStack Stack;
Stack.Definition = HealthPotionDef;
Stack.Quantity = 5;
int32 OutRemaining;
bool bAdded = Inventory->AddItem(Stack, OutRemaining);
// Remove items
bool bRemoved = Inventory->RemoveItem(HealthPotionDef->ItemId, 2);
// Check if the player has an item
bool bHasItem = Inventory->ContainsItem(HealthPotionDef->ItemId, /*MinQuantity=*/ 3);
// Get total count of a specific item
int32 Count = Inventory->GetItemCount(HealthPotionDef->ItemId);
Stacking is automatic based on MaxStackSize from the item definition:
// Adding 50 of a stackable item (MaxStackSize=20) fills 2 slots (20+20) and leaves 10 remaining
FUniversalItemStack BigStack;
BigStack.Definition = ArrowDef; // MaxStackSize = 20
BigStack.Quantity = 50;
int32 Remaining;
Inventory->AddItem(BigStack, Remaining); // Remaining = 10
// Split a stack
int32 NewSlotIndex;
Inventory->SplitStack(SlotIndex, 5, NewSlotIndex); // Splits 5 into new slot
// Transfer an item from player inventory to a chest
UUniversalInventoryComponent* ChestInventory = Chest->FindComponentByClass<UUniversalInventoryComponent>();
bool bTransferred = PlayerInventory->TransferItem(FromSlot, ToSlot, ChestInventory);
The inventory component exposes Blueprint-delegatable events:
| Event | Signature | Description |
|---|---|---|
OnItemAdded |
FGuid ItemId, int32 Quantity, int32 SlotIndex |
Fired when items are added |
OnItemRemoved |
FGuid ItemId, int32 Quantity, int32 SlotIndex |
Fired when items are removed |
OnItemUsed |
FGuid ItemId, int32 SlotIndex |
Fired when an item is consumed/used |
OnInventoryFull |
(none) | Fired when an add operation fails due to full inventory |
OnInventoryChanged |
(none) | Fired on any inventory modification (useful for UI refresh) |
OnSlotUpdated |
int32 SlotIndex |
Fired when a specific slot's contents change |
In Blueprint, bind to these events on the Inventory Component to drive UI updates, play sounds, or trigger gameplay logic.
The system provides a predefined set of equipment slots:
| Slot | Enum Value | Description |
|---|---|---|
| Head | Helmet |
Helmets, hats, circlets |
| Chest | Chest |
Armor, robes, jackets |
| Hands | Gloves |
Gloves, gauntlets, bracers |
| Legs | Boots |
Boots, greaves, sabatons |
| Main Hand | MainHand |
Primary weapon or tool |
| Off Hand | OffHand |
Shield, secondary weapon, quiver |
| Ring 1 | Ring1 |
Finger ring |
| Ring 2 | Ring2 |
Finger ring |
| Amulet | Amulet |
Necklace, amulet, pendant |
| Back | Back |
Cape, cloak, wings |
// The equipment component is separate from inventory
UUniversalEquipmentComponent* Equipment = CreateDefaultSubobject<UUniversalEquipmentComponent>(TEXT("Equipment"));
// Equip an item from inventory
UUniversalItemInstance* ItemToEquip = PlayerInventory->GetItemAt(SlotIndex);
if (ItemToEquip)
{
UUniversalItemInstance* PreviouslyEquipped = Equipment->EquipItem(ItemToEquip);
if (PreviouslyEquipped)
{
// Put the old item back in inventory
FUniversalItemStack ReturnStack;
ReturnStack.Definition = PreviouslyEquipped->Definition;
ReturnStack.Quantity = PreviouslyEquipped->Quantity;
PlayerInventory->AddItem(ReturnStack, OutRemaining);
}
// Remove from inventory
PlayerInventory->RemoveItem(ItemToEquip->Definition->ItemId, 1);
}
When an item is equipped, its StatModifiers are applied to the character's GAS attribute set:
// Stat modifier structure
USTRUCT(BlueprintType)
struct FUniversalItemStatModifier
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FGameplayAttribute Attribute; // e.g., GASPAC_AttributeSet_Core::GetHealthAttribute()
UPROPERTY(EditAnywhere, BlueprintReadOnly)
EUniversalModifierOp ModifierOp = EUniversalModifierOp::Add;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
float Magnitude = 0.0f;
};
Modifier operations:
| Operation | Description |
|---|---|
Add |
Adds flat value (e.g., +10 Health) |
Multiply |
Multiplies base value (e.g., 1.15x MoveSpeed) |
Override |
Sets absolute value |
Example: A steel chestplate that adds 50 armor and 15 max health:
// On the item definition's StatModifiers:
// 1. Attribute = Armor, Op = Add, Magnitude = 50.0
// 2. Attribute = MaxHealth, Op = Add, Magnitude = 15.0
Equipped items with a skeletal mesh are automatically attached to the character rig:
// On the item definition:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Equipment")
USkeletalMeshComponent* EquipmentMesh;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Equipment")
FName AttachSocketName = "hand_r"; // Bone/socket to attach to
The system integrates with GASPAC_ProceduralRigComponent to apply IK adjustments when heavy equipment changes the character's center of mass.
| Event | Description |
|---|---|
OnItemEquipped |
An item was equipped to a slot |
OnItemUnequipped |
An item was removed from a slot |
OnEquipmentChanged |
Any equipment slot was modified |
OnStatModifiersApplied |
Stat modifiers from equipment were recalculated |
// Use (consume) an item from a specific slot
bool bUsed = Inventory->UseItem(SlotIndex, OwningCharacter);
The use flow:
bCanBeDestroyed flag, ItemTags includes Item.Consumable)OnItemUsed delegateOnUsed method on any attached UUniversalItemComponent instancesAttach UUniversalItemComponent subclasses to item definitions for custom behavior:
UCLASS()
class UHealingItemComponent : public UUniversalItemComponent
{
GENERATED_BODY()
public:
virtual void OnUsed(AActor* User) override
{
if (UUniversalItemInstance* Inst = GetOwningInstance())
{
float HealAmount = Inst->GetDefinition()->NumericProperties.FindRef("HealAmount");
if (IAbilitySystemInterface* ASI = Cast<IAbilitySystemInterface>(User))
{
FGameplayEffectContextHandle Ctx = ASI->GetAbilitySystemComponent()->MakeEffectContext();
ASI->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(
*UUniversalItemStatics::MakeHealEffectSpec(HealAmount, Ctx)
);
}
}
}
};
Register custom components in the item definition's ItemComponents array.
The Item System uses a server-authoritative replication model:
// On UUniversalInventoryComponent:
UPROPERTY(ReplicatedUsing = OnRep_InventorySlots)
TArray<FUniversalItemStack> InventorySlots;
UFUNCTION()
void OnRep_InventorySlots(const TArray<FUniversalItemStack>& OldSlots);
| Technique | Description |
|---|---|
| Delta Replication | Only changed slots are replicated |
| Conditional Replication | Equipment only replicates when changed |
| Property Compression | Quantized quantities and enums |
| Client Prediction | Local add/remove predicted; server reconciles |
Target: <10 KB/s per character for full inventory + equipment replication.
All item operations are validated server-side:
bool UUniversalInventoryComponent::ServerAddItem_Validate(const FUniversalItemStack& Item) { return true; }
void UUniversalInventoryComponent::ServerAddItem_Implementation(const FUniversalItemStack& Item)
{
// Server validates: is the item definition valid? does it fit?
int32 Remaining;
if (AddItem(Item, Remaining))
{
ClientNotifyItemAdded(Item.Definition->ItemId, Item.Quantity - Remaining);
}
}
UCLASS(BlueprintType)
class UUniversalLootTable : public UDataAsset
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Loot")
TArray<FUniversalLootEntry> Entries;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Loot")
int32 MinDrops = 1;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Loot")
int32 MaxDrops = 3;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Loot")
bool bAllowDuplicates = true;
};
USTRUCT(BlueprintType)
struct FUniversalLootEntry
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSoftObjectPtr<UUniversalItemDefinition> ItemDefinition;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 MinQuantity = 1;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 MaxQuantity = 1;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
float Weight = 1.0f;
};
// In a loot chest, enemy drop, or container
void AMyLootChest::OnOpened(AActor* Interactor)
{
UUniversalItemSubsystem* Subsystem = GetWorld()->GetSubsystem<UUniversalItemSubsystem>();
TArray<FUniversalItemStack> GeneratedLoot = Subsystem->GenerateLoot(MyLootTable);
UUniversalInventoryComponent* PlayerInventory = Interactor->FindComponentByClass<UUniversalInventoryComponent>();
if (PlayerInventory)
{
for (const FUniversalItemStack& Loot : GeneratedLoot)
{
int32 Remaining;
PlayerInventory->AddItem(Loot, Remaining);
}
}
}
Loot tables can scale drop weights based on item rarity:
// The subsystem automatically applies rarity weight multipliers:
// Common 1.0x -- baseline
// Uncommon 0.6x -- 60% as likely as common
// Rare 0.3x -- 30% as likely
// Epic 0.1x -- 10% as likely
// Legendary 0.03x -- 3% as likely
| Event | Parameters | When Fired |
|---|---|---|
OnItemAdded |
ItemId (FGuid), Quantity (int32), SlotIndex (int32) |
Items successfully added |
OnItemRemoved |
ItemId (FGuid), Quantity (int32), SlotIndex (int32) |
Items removed |
OnItemUsed |
ItemId (FGuid), SlotIndex (int32) |
Item consumed/used |
OnInventoryFull |
-- | Add failed due to capacity |
OnInventoryChanged |
-- | Any modification |
OnSlotUpdated |
SlotIndex (int32) |
Specific slot changed |
| Event | Parameters | When Fired |
|---|---|---|
OnItemEquipped |
ItemId (FGuid), Slot (EUniversalEquipmentSlot) |
Item equipped |
OnItemUnequipped |
ItemId (FGuid), Slot (EUniversalEquipmentSlot) |
Item removed |
OnEquipmentChanged |
-- | Any slot modified |
OnStatModifiersApplied |
OldStats, NewStats |
Stats recalculated |
| Event | Parameters | When Fired |
|---|---|---|
OnDefinitionRegistered |
ItemId (FGuid) |
New item definition loaded |
OnLootGenerated |
Items (Array of ItemStacks), Source |
Loot table rolled |
This example shows the full lifecycle of creating, adding, using, and removing a health potion:
DA_HealthPotion Data Asset (class: UniversalItemDefinition)DisplayName = "Health Potion"MaxStackSize = 10Item.Consumable.PotionNumericProperties = { "HealAmount": 50.0 }bCanBeDropped = true, bCanBeTraded = trueUUniversalInventoryComponent* Inv = Character->FindComponentByClass<UUniversalInventoryComponent>();
FUniversalItemStack Stack;
Stack.Definition = DA_HealthPotion;
Stack.Quantity = 3;
int32 Remaining;
Inv->AddItem(Stack, Remaining);
// Find the potion in inventory
int32 Slot = Inv->FindItemSlot(DA_HealthPotion->ItemId);
if (Slot != INDEX_NONE)
{
Inv->UseItem(Slot, Character);
}
// In a custom UUniversalItemComponent::OnUsed():
float HealAmount = GetOwningInstance()->GetDefinition()->NumericProperties.FindRef("HealAmount");
// Apply healing via OmniShift's GAS:
UGASPAC_AttributeSet_Core* AttrSet = Character->GetAttributeSet();
AttrSet->SetHealth(FMath::Clamp(AttrSet->GetHealth() + HealAmount, 0.f, AttrSet->GetMaxHealth()));
OnItemUsed delegate fires (play sound, particle effect)OnSlotUpdated fires (UI refresh)OnItemRemoved fires| Feature | Impact | Notes |
|---|---|---|
| Definition caching | Low | Definitions loaded once and cached in the subsystem |
| Inventory slot iteration | Low | TArray-based, O(1) access by index |
| Equipment stat recalculation | Low | Only recalculates on equip/unequip |
| Loot generation | Medium | Depends on table complexity (typically <0.5ms) |
| Network replication | Medium | Only delta state is replicated |
| UI updates | Low | Uses MVVM for efficient data binding |
GameplayTags (e.g., Item.Weapon.Melee.Sword)InstanceProperties for per-instance state (durability, charges) -- never modify the shared definitionSaveGameEquipmentSlot over tag-based equipment for better type safetyPart of the OmniShift plugin by MountainLabs UG.
Last updated: May 9, 2026