VerseBuilderDocs

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) and Shop (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

  1. Create the Provider

    Add a new device file Economy. In Game Data → Module, mark it asProvider and add two Exports: award_coins(amount:int):void and try_buy(price:int):logic.
  2. Provider rules: earn

    In Economy, add WHEN On Elimination → DO Give Currency coins +10. Earn rule lives on the Provider because the variable does.
  3. Provider helper: try_buy

    The Composer wires try_buy to a generated helper that checks balance and decrements. You don't hand-write Verse — the export signature drives the codegen.
  4. Create the Consumer

    Add a new device file Shop. In Game Data → Module, mark itConsumer, add a Binding named Economy targeting the Provider, with consumed ports try_buy.
  5. Consumer rules: spend

    In Shop, add WHEN On Button Pressed → DO Call Binding Port (Economy.try_buy, 100), Grant Item shield_potion. The Shop never touches coins directly — 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)
economy_device.verse — Provider side, exposed export.

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)
shop_device.verse — Consumer side, binding + call.

UEFN wiring

  • Place the economy_device actor anywhere on the map. It has no visible footprint.
  • Place the shop_device actor. In the Details panel, the Economy property accepts an economy_device reference — drag the Economy actor in.
  • Wire the Consumer's ShopButton and ItemGranter to 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