VerseBuilderDocs

Docs/Game data/Module system

Module system

Configure this device for multi-device packs — add exports (Provider) or bindings (Consumer) and let the Composer wire the @editable plumbing.

Last updated 2026-06-14

What is this?

The Module tab is the configuration sheet for the multi-device wiring of this device. It doesn't define behavior — it tells the Composer what public API to emit, and which other devices this one talks to.

“Provider” and “Consumer” are roles you fall into by what you add, not a toggle you flip: add Exports → you expose a public API (Provider); add a Binding → you call another device's API (Consumer). The labels next to the Exports and Bindings sections just name those roles.

Conceptual background lives in Concepts: Multi-device. Read that first if “Provider” / “Consumer” aren't familiar terms.

Provider mode — exports

On a Provider device, the Exports list declares which methods are public. Each export is one row:

  • ID — snake_case name (e.g. award_coins); the Verse name and internal helper are derived from it automatically.
  • Verse name — PascalCase method name, derived from the ID and shown read-only (e.g. award_coinsAwardCoins).
  • Params — list of (name, type). The implicit first param is always the agent (Player).
  • Return typevoid, int, logic, etc.
  • Internal helper — the generated helper function the public export delegates to (also derived from the ID, e.g. AwardCoinsHelper). Add your own body under the export's Logic tab, or leave it to auto-call.

Consumer mode — bindings

On a Consumer device, the Bindings list declares which Providers this device depends on. Each binding row:

  • Target device — the Provider device in your project.
  • Editable name — PascalCase Verse variable for the binding (e.g. Economy).
  • Consumed ports — the Provider's exports this consumer actually calls (checkboxes populated from the target device's exports).

📌 Note

The “Consumer mode” checkbox is separate. It does not make a device a consumer — adding a binding does that. It only tells the Composer to skip shared type declarations (inventory_slot, etc.) that the Provider already emits, so they aren't duplicated. Tick it on a device that binds to a Provider which owns those shared types.

💡 Tip

The Composer reads the Provider's exports to emit a correctly-typed binding and call, so a renamed or missing port is caught at compose time rather than in UEFN.

Walkthrough: economy pack

  1. On the Provider device

    Open the Module tab, set a Verse Class Name (e.g. economy_device), then add two Exports: award_coins(Amount:int):void and try_buy(Price:int):logic. Adding exports is what makes it a Provider.
  2. On the Consumer device

    Open the Module tab and add a Binding: target economy_device, Editable Name Economy, and tick the try_buy consumed port. (Tick Consumer mode too if the Provider owns shared types this device reuses.)
  3. Use the binding in a rule

    In the Consumer, add a rule: WHEN On Button Pressed → DO Call Binding Port. Set Binding Name Economy, Port Method Name TryBuy (the PascalCase Verse name), and Arguments 100. Use Return Value to capture the result if needed.
  4. Wire in UEFN

    After import, the Consumer's Details panel exposes an Economy field. Drag the Provider actor into it. Pack is wired.

Gotchas

⚠️ Watch out

Provider, then Consumer. You can't configure a Consumer binding to a Provider that doesn't exist yet. Always create and configure the Provider device first.

⚠️ Watch out

Renaming an export = breaking change. If the Provider renames an exported method, every Consumer that listed it as a consumed port needs updating. The Composer surfaces a binding warning (e.g. a target with no class name / no public exports) rather than silently shipping broken wiring.

📌 Note

Single-device games never need the Module tab — leave it untouched. Pack presets (Tycoon, Economy Shop, …) seed it for you when loaded.

What gets generated?

Provider exports become <public> methods that wrap internal helpers. Consumer bindings become typed @editable fields:

economy_device := class(creative_device):

    AwardCoins<public>(Player:agent, Amount:int):void=
        AwardCoinsHelper(Player, Amount)
Provider — public export wraps the helper.
shop_device := class(creative_device):

    @editable Economy:economy_device = economy_device{}

    OnPress(Agent:agent):void=
        if (Economy.TryBuy[Agent, 100]):
            # success path
            ...
Consumer — typed binding + call via the binding.

See also