Combines

Documentation Unreal Engine Player Combat Inventory

Combines turn one item dropped onto another into new items: two herbs make a mixed herb, poison and arrows make poison arrows. Write them as recipe rows in a Combine Set, or as a Blueprint class.


Set it up

5 minutes
  1. 1

    Write the recipe

    Right-click in the Content Browser, UPC > Items > Combine Set, name it CombineSet_MyCrafting and add a Recipes row: Item A and Item B Item_UPC_Herb, Results one Item_UPC_MixedHerb, Seconds 1.
    Details
    CombineSet_MyCrafting
    Recipes
    Recipes
    1 Array element
    Index [ 0 ]
    Item A
    Item_UPC_Herb
    Count A
    1
    Item B
    Item_UPC_Herb
    Count B
    1
    Results
    1 Array element
    Index [ 0 ]
    Item
    Item_UPC_MixedHerb
    Count
    1
    Order Matters
    Required Tags
    0 Gameplay Tags
    Seconds
    1
    Combines
    Combines
    0 Array elements
    Your own set with one row: two herbs make one mixed herb, a second each.
  2. 2

    List it on a config

    Duplicate Demo/Items/Inventory/InventoryConfig_UPC_SurvivalGrid as InventoryConfig_MyGrid and replace CombineSet_UPC_Demo in its Combine Sets with your set.
    Details
    InventoryConfig_MyGrid
    Combines
    Combine Sets
    1 Array element
    Index [ 0 ]
    CombineSet_MyCrafting
    Timing
    Combine At Once
    Your copy of the survival grid config, with your set in place of the demo's. Combine At Once stays off, so each combine waits its Seconds behind a progress bar.
  3. 3

    Give the demo player the config and two herbs

    On BP_UPC_Character's InventoryComponent, set Config to InventoryConfig_MyGrid and add Item_UPC_Herb to Starting Items twice (herbs do not stack, so each lies in its own cell).
  4. 4

    Open your config on the grid screen

    In BPC_UPC_InventoryScreens (Demo/UI/Widgets/Inventory, on BP_UPC_PlayerController), add an Inventory Screen Classes row in Class Defaults: InventoryConfig_MyGrid with WBP_UPC_InventoryScreen_SurvivalGrid. Without a row the open world list opens, which pauses the game, so a timed combine cannot land (Inventory Screens).
  5. 5

    Drop one herb on the other

    Press Play in LVL_UPC_Demo, press I and drag one herb onto the other. A progress bar fills on its tile and a second later a mixed herb takes their place. Combine in one herb's actions, then the other herb, does the same.
The demo character has what combines need. Yours needs a UPC Equipment Manager (without it nothing combines) and a call to Notify Ability System Ready (without it a combine finishes at once, with no progress bar; Character Setup). A combine runs inside one inventory, so a drop between the inventory and a chest never combines.

Recipes: the rows you fill

SettingDoes
Item A, Count AFirst item and the units one combine uses.
Item B, Count BSecond item and units; may equal Item A (two herbs).
ResultsWhat one combine makes, each an item and count (an empty bottle can come back). The first result goes where the config's Combine Result Place says (default: onto the item it was combined onto). When that combine leaves a stack behind or the result does not fit, and for every other result, it lands as a pickup does: same-item stacks first, then the first free place in container order, else past the end of the first carried container that takes new items. A result goes on the belt only into a slot its ingredient emptied, if the Filter allows.
Order MattersOn: only Item A dropped onto Item B combines. Off (default): either way round.
Required TagsTags the character must hold (a learned skill, a workbench zone), added and removed by your code (a trigger zone). Without them the drop is refused with UPC.Inventory.Refused.Locked. Empty needs none.
SecondsTime per combine behind a progress bar; moving stops it. 0 (default), or a config with Combine At Once, makes it instant. A combine of your own sets time with Unit Timing.
A drop runs the row as many times as the dragged units and other stack allow. The demo's Dip Arrows turns one poison and five arrows into five poison arrows, so twelve arrows dragged onto three flasks run it twice: ten poison arrows, two arrows and a flask left. A row can require a workbench and return the bottle:
Details
CombineSet_MyWorkbench
Recipes
Recipes
1 Array element
Index [ 0 ]
Item A
Item_MyHerb
Count A
2
Item B
Item_MyBottle
Count B
1
Results
2 Array elements
Index [ 0 ]
Item
Item_MyPotion
Count
1
Index [ 1 ]
Item
Item_MyEmptyBottle
Count
1
Order Matters
Required Tags
1 Gameplay Tag
MyGame.State.AtWorkbench
Seconds
2
Combines
Combines
0 Array elements
A set with one row: two herbs and a bottle make a potion at a workbench, and the empty bottle comes back. The row is turned down with Locked until the character holds the tag.

A combine of your own

For what a row cannot say (rounds counted into a magazine), make a Blueprint child of UPC Inventory Combine and list it in a set's Combines. Characters share one copy, so keep no state in it. The demo's Load Holder and Coat Weapon are two to copy (Inventory Rules and Combines in Blueprint).
FunctionAnswers
MatchesWhether it handles Item dropped onto Target, from the two items.
Get UnitsUnits the drop makes now, 0 to the context's Count; 0 refuses, Out Reason a tag under UPC.Inventory.Refused. Default: every unit asked.
Apply UnitMakes one unit, on the server.
Unit TimingOptional: seconds per unit, sounds, and whether moving stops it (default half a second, stopped by moving).
Matches gets the two items. Get Units and Apply Unit get a Context: the Inventory, the dropped Item, the Target it landed on (not a node's Target pin) and Count, the units still asked for. One Apply Unit is one unit, here one round loaded. Ten rounds dropped on a magazine holding 25 of 30: Get Units gets Count 10 and answers 5 (the room left), so five units run, asking again before each with Count 5, then 4, down to 1.

A magazine, step by step

These steps build on the quick start's InventoryConfig_MyGrid and BP_UPC_Character.
  1. Make Item_MyRound (Max Stack Size 60), Item_MyMagazine (Max Stack Size 1, so each magazine is its own item) and a gameplay tag MyGame.Ammo.Loaded. Each magazine counts its rounds in that tag as a stat tag, a count kept on one owned item.
  2. Make BP_MagazineCombine with Instance Editable variables Round and Magazine (UPC Item Definition) and Capacity (Integer, 30), and override the three functions:
BP_MagazineCombine · Matches
Matches
Item
Target
Get Item Def
Target
Return Value
Round
==
Get Item Def
Target
Return Value
Magazine
==
AND
Return Node
Return Value
BP_MagazineCombine · Get Units: the least of the rounds dragged, the rounds in the stack and the room left
Get Units
Context
Break UPC Inventory Combine Context
UPC Inventory Combine Context
Inventory
Item
Target
Count
Get Item Stack Count
Target
Instance
Return Value
MIN
Capacity
Get Stat Tag Stack Count
Target
TagMyGame.Ammo.Loaded
Return Value
-
MIN
Return Node
Out ReasonUPC.Inventory.Refused.Full
Return Value
BP_MagazineCombine · Apply Unit: one round into the magazine
Apply Unit
Context
Break UPC Inventory Combine Context
UPC Inventory Combine Context
Inventory
Item
Target
Count
Add Stat Tag Stack
Target is UPC Inventory Item Instance
Target
TagMyGame.Ammo.Loaded
Stack Count1
Consume Item Instance
Target is UPC Inventory
Target
Instance
Count1
Return Value
  1. Make CombineSet_MyShooter, add your combine to its Combines, and pick your two items:
Details
CombineSet_MyShooter
Recipes
Recipes
0 Array elements
Combines
Combines
1 Array element
Index [ 0 ]
BP_MagazineCombine
Round
Item_MyRound
Magazine
Item_MyMagazine
Capacity
30
Unit Timing
Seconds
0.5
Cancel on Move
Your own set with the magazine combine listed. Each entry carries its own Unit Timing: half a second per round, stopped by moving, unless you change it here.
  1. Add CombineSet_MyShooter to InventoryConfig_MyGrid's Combine Sets, below your crafting set. In BP_UPC_Character's Starting Items, add one Item_MyMagazine and ten Item_MyRound entries (they join into one stack of 10). Drag the rounds onto the magazine on the grid screen to load it, one round every half second.
AdvancedTest it without a screen
Give BP_UPC_Character two graphs: B sends the Request Move a screen's drag sends, and the load's end prints the rounds loaded:
BP_UPC_Character · B drops the rounds on the magazine
B
Pressed
Released
Key
Inventory Component
Find First Item by Definition
Target
Item DefItem_MyRound
Return Value
Find First Item by Definition
Target
Item DefItem_MyMagazine
Return Value
Get Item Placement
Target
Item
Out Placement
Return Value
Request Move
Target is UPC Inventory
Target
Item
Count10
Destination
BP_UPC_Character · print the load when it ends
On Equip Sequence Ended (EquipmentManagerComponent)
Cancelled
Reason
Inventory Component
Find First Item by Definition
Target
Item DefItem_MyMagazine
Return Value
Get Stat Tag Stack Count
Target
TagMyGame.Ammo.Loaded
Return Value
Print String
In String
Press Play, then B. Five seconds later the print shows 10; walk off mid-load and it shows the rounds loaded so far.

Combine from a button, and show it

NodeDoes
Request CombineCombines the whole stack of Item with Target, an item wherever it is (poison reaches the sword in hand).
Can Combine (UPC Inventory Statics)Request Combine's check: Out Results is what one combine of a recipe makes, Out Reason why not.
On Combined (inventory)As units land: the two items, the recipe or combine, and Units landed together (1 per unit of a timed combine, the full count when all land at once).
On Equip Step Started (equipment manager)As each unit starts, with its seconds, for a progress bar.
On Equip Sequence EndedThe load finished or stopped, with a reason.
An item's Details panel lists recipes naming it under Combines With ("Herb + Herb -> Mixed Herb"). The screens' Combine mode is on Inventory Screens.

Debug it

Log LogUPCInventory Verbose names each request the server turned down.
AdvancedHow a combine runs
  • A drop or Request Combine asks the config's Combine Sets in order, each set's recipes before its combines. The first match decides, ahead of going into a bag, joining a stack or trading places.
  • Can Place answers Combine with the units allowed, or Refused with the reason, and Request Move runs the combine as one equipment change, a unit at a time.
  • Before each unit the inventory asks again: Get Units for a combine, the stacks and Required Tags for a recipe. A 0 stops the rest with its reason, so leaving a workbench mid-load stops it with Locked. Units already made stay, whatever stops the load.
  • A load also stops before its next unit when the player moves (Unit Timing's Cancel on Move), a screen calls the equipment manager's Request Cancel Sequence with either item, either item leaves the inventory, the character gains one of the equipment action's Cancel Tags or UPC.State.EquipmentBlocked, or another animation cuts it off. On Equip Sequence Ended gives a reason under UPC.Equipment.Cancelled, or the combine's own.
  • Every unit lands together, with no step events and no unit sounds, when the unit time is 0, the config's Combine At Once is on (Containers and Rules), or the character has no equipment action. On Combined then fires once, with the full count.
  • Get Playing Step answers how far the current unit has got, 0 to 1, and how many have landed, for a widget opened mid-load.
  • On Equip Step Started's step carries the target as Source Item, the dropped item as Used Item, and the unit's seconds as Timing, counted from Start Time.
  • One equipment change runs at a time, so the belt refuses a press during a load, and Block Actions While Running on the equipment action shuts the action graph for the load. A unit Get Units refuses as it lands has already played its commit sound.
  • The inventory judges reach and Access Tags when the player asks, not before each unit.
  • A result lands in stacks within its Max Stack Size. A row with several results judges each against the inventory as it stands.
  • Find Combine Recipe returns a set's row for two items, and Get Recipes For Item every row of a config's sets that uses or makes an item, for a tooltip or a recipe book.
  • A drop asks about the item under the dropped cell, whatever the dragged item's size, so a wide box of rounds still loads a magazine at a grid's edge.
  • Saving a set warns in the Message Log about a row or a result naming no item, a row making nothing, and two rows taking the same pair (the first wins). Saving a config warns about a result no container taking new items lets in.
AdvancedMore combines to write
  • Magazines by caliber. List one combine per caliber, or have Matches compare a caliber tag both items carry in their Slot Tags.
  • A key opening a locked box. Get Units answers 1 while the key and the box share a lock tag in their Slot Tags (MyGame.Lock.Crypt) and the box lacks a MyGame.Box.Unlocked stat tag, else 0 with UPC.Inventory.Refused.NotAdmitted. Apply Unit adds that stat tag and consumes the key.
The demo
Details
CombineSet_UPC_Demo
Recipes
Recipes
2 Array elements
Index [ 0 ]
Item A
Item_UPC_Herb
Count A
1
Item B
Item_UPC_Herb
Count B
1
Results
1 Array element
Index [ 0 ]
Item
Item_UPC_MixedHerb
Count
1
Order Matters
Required Tags
0 Gameplay Tags
Seconds
1
Index [ 1 ]
Item A
Item_UPC_Poison
Count A
1
Item B
Item_UPC_Arrow
Count B
5
Results
1 Array element
Index [ 0 ]
Item
Item_UPC_PoisonArrow
Count
5
Order Matters
Required Tags
0 Gameplay Tags
Seconds
1
Combines
Combines
2 Array elements
Index [ 0 ]
BP_UPC_Combine_LoadHolder
Unit Timing
Seconds
0.3
Start Sound
None
Commit Sound
None
Cancel on Move
Index [ 1 ]
BP_UPC_Combine_CoatWeapon
Poison
Item_UPC_Poison
Weapon Tags
1 Gameplay Tag
UPC.Slot.Weapon
Poisoned Tag
UPC.Inventory.Stat.Poisoned
Strikes
3
Unit Timing
Seconds
1.5
Start Sound
None
Commit Sound
None
Cancel on Move
The set all three demo styles list. Mix Herbs and Dip Arrows are plain rows; Load Holder and Coat Weapon are Blueprint combines.
The party RPG and open world list combine at once; the survival grid shows a progress bar.
MultiplayerWhat runs where
  • The server makes every unit; the owning player's machine asks the same sets, so its Can Place and Can Combine match the server early. It plays its copy of the load from the server's step about half a round trip later and stops with the server's reason.
  • On Combined fires on the server and the owning player's machine, once per landing.
C++C++
The magazine as a C++ subclass:
UCLASS()
class UMyMagazineCombine : public UUPCInventoryCombine
{
	GENERATED_BODY()
 
public:
	UPROPERTY(EditAnywhere, Category = "Magazine")
	TObjectPtr<UUPCItemDefinition> Round;
 
	UPROPERTY(EditAnywhere, Category = "Magazine")
	TObjectPtr<UUPCItemDefinition> Magazine;
 
	UPROPERTY(EditAnywhere, Category = "Magazine")
	int32 Capacity = 30;
 
	virtual bool Matches_Implementation(const UUPCInventoryItemInstance* Item, const UUPCInventoryItemInstance* Target) const override
	{
		return Item->GetItemDef() == Round && Target->GetItemDef() == Magazine;
	}
 
	virtual int32 GetUnits_Implementation(const FUPCInventoryCombineContext& Context, FGameplayTag& OutReason) const override
	{
		const int32 Room = Capacity - Context.Target->GetStatTagStackCount(TAG_MyGame_Ammo_Loaded);
		const int32 Units = FMath::Min3(Context.Count, Context.Inventory->GetItemStackCount(Context.Item), Room);
		if (Units < 1)
		{
			OutReason = TAG_UPC_Inventory_Refused_Full;
		}
		return Units;
	}
 
	virtual void ApplyUnit_Implementation(const FUPCInventoryCombineContext& Context) const override
	{
		Context.Target->AddStatTagStack(TAG_MyGame_Ammo_Loaded, 1);
		Context.Inventory->ConsumeItemInstance(Context.Item, 1);
	}
};
  • Inventory/UPCInventoryTypes.h declares the refusal tags; TAG_MyGame_Ammo_Loaded is your own native tag.
  • UUPCInventoryConfig::CombineSets lists UUPCCombineSet assets, each holding FUPCCombineRecipe rows in Recipes and instanced UUPCInventoryCombine objects in Combines. UUPCInventoryConfig::FindCombine returns the first match as an FUPCCombineMatch.
  • The load is one FUPCEquipStep with Repeats set to the units, SourceItem the target and UsedItem the dropped item.
  • UUPCInventoryComponent::CanCombine fills the FUPCCombineMatch; OnCombined carries an FUPCCombineOutcome.
AdvancedTroubleshooting
  • A drop merges or swaps instead. Nothing in the config's Combine Sets takes the pair, or the character uses another config.
  • Nothing happens. Another equipment change is running, or the pawn has no UPC Equipment Manager (a warning under LogUPCInventory).
  • A drop is refused. Print Out Reason from Can Combine or Can Place: NoCombine, NotEnough (a stack holds too few for one combine), Locked (Required Tags missing), or a reason your combine sets, such as Full.
  • A load stops at once. Get Units answered 0 for the next unit, or the player moved during a combine that stops on a move.