Docs/Recipes/Multi-device pack from scratch
Recipe: Multi-device pack from scratch
Build an Economy Provider + Shop Consumer pack. The Provider owns coins; the Shop calls try_buy via an @editable binding wired in UEFN.
Last updated 2026-06-06
Ingredients
- Two devices:
Economy(Provider) andShop(Consumer). - Provider variables:
coins(int, player, persist on). - Provider exports:
award_coins(amount:int),try_buy(price:int):logic. - Consumer binding:
Economy(links to the Provider). - UEFN devices: 1 Elimination Manager (for the Provider), 1 Button Device + 1 Item Granter (for the Shop).
Build it
Create the Provider
Add a new device file Economy. In Game Data → Module, mark it asProviderand add two Exports:award_coins(amount:int):voidandtry_buy(price:int):logic.Provider rules: earn
In Economy, addWHEN On Elimination → DO Give Currency coins +10. Earn rule lives on the Provider because the variable does.Provider helper: try_buy
The Composer wirestry_buyto a generated helper that checks balance and decrements. You don't hand-write Verse — the export signature drives the codegen.Create the Consumer
Add a new device file Shop. In Game Data → Module, mark itConsumer, add a Binding namedEconomytargeting the Provider, with consumed portstry_buy.Consumer rules: spend
In Shop, addWHEN On Button Pressed → DO Call Binding Port (Economy.try_buy, 100), Grant Item shield_potion. The Shop never touchescoinsdirectly — it goes through the Provider's API.
💡 Tip
The Shop has no idea where coins are stored — that's the point. Swap the Provider implementation tomorrow, the Shop keeps working as long as the export signature stays.
Generated Verse
On the Provider, try_buy becomes a public <decides><transacts>method:
economy_device := class(creative_device):
# ... coins state + helpers ...
TryBuy<public>(Player:agent, Price:int)<decides><transacts>:void=
Current := GetCoins[Player]
Current >= Price
SetCoins(Player, Current - Price)
On the Consumer, the binding is an @editable field; the call goes through it:
shop_device := class(creative_device):
@editable Economy:economy_device = economy_device{}
@editable ShopButton:button_device = button_device{}
@editable ItemGranter:item_granter_device = item_granter_device{}
OnBegin<override>()<suspends>:void=
ShopButton.InteractedWithEvent.Subscribe(OnPress)
OnPress(Agent:agent):void=
if (Economy.TryBuy[Agent, 100]):
ItemGranter.GrantItem(Agent)
UEFN wiring
- Place the
economy_deviceactor anywhere on the map. It has no visible footprint. - Place the
shop_deviceactor. In the Details panel, theEconomyproperty accepts aneconomy_devicereference — drag the Economy actor in. - Wire the Consumer's
ShopButtonandItemGranterto the corresponding placed devices.
Gotchas
⚠️ Watch out
Unwired bindings = silent fail. If the Shop's
Economy binding isn't pointed at an Economy actor in UEFN, the first TryBuy call will crash. Always wire bindings before testing.⚠️ Watch out
Exports are one-way. The Provider doesn't know about its Consumers. If you need the Provider to call back into a Consumer, that Consumer also needs an exported method and a binding pointed at it from the Provider.
📌 Note
The Provider's state lives in the Provider's device context. Variables you read or write in an export run as if the rule were in the Provider — not the Consumer that called it.
See also