Passing Extra Parameters to Event Subscriptions? The Handler Class Pattern
Ten buttons share one chunk of handling logic, but you want to know which one was pressed? Subscribe (binding a device event to your logic — Blueprint's Bind Event) recognizes exactly one fixed shape of function, and there's simply no way to squeeze extra parameters in — one of the most common sticking points for Verse newcomers. This page covers the standard solution, distilled by the community and confirmed by Epic staff: the Handler class pattern.
1. The Problem: Subscribe Eats Only One Shape
A quick recap of event subscription (Bind Event in Blueprint): a button device's InteractedWithEvent.Subscribe accepts only one fixed shape of function — signature (Agent:agent):void, meaning "one agent comes in (the pin for the player who triggered the event, similar to Instigator), nothing goes out". When the event fires, the engine hands you the triggering player, and not one byte more. Which means this intuitive attempt is doomed:
# The intuitive attempt: trying to slip the button number in alongside
# ButtonA.InteractedWithEvent.Subscribe(OnPressed, 1) # Compile error
# ButtonB.InteractedWithEvent.Subscribe(OnPressed, 2) # Compile error
# Subscribe only takes functions shaped (Agent:agent):void — not one extra parameter allowed
Blueprint translation: this block (all comments, deliberately uncompilable) shows exactly the intuitive attempt that hits the wall — trying to stuff a button number 1 or 2 after the callback in Subscribe. But Subscribe's socket shape is welded shut; one extra parameter and it refuses.
In a language with closures, you'd casually wrap a nameless little function around it and "capture" the number inside. But as Lesson 19 said: current Verse can neither whip up such little functions on the spot (lambdas) nor let a function bundle up the surrounding variables and take them along. So — copy-paste one callback per button? Of course not.
2. The Standard Solution: Data into a Class, Callback as a Method
The pattern from the community guide is elegant and general: define a handler class (a small Blueprint class), store the "extra parameters" as its fields, and write the callback as one of its methods; at subscribe time, construct an instance of that class on the spot (like Blueprint's Construct Object) and hand over its method. When the method runs, it reaches the data stored in the fields through Self (its own self) — while the outward signature stays (Agent:agent):void, so Subscribe can't find a single thing to complain about.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Handler class: store the extra parameters as fields
button_handler := class:
Label:string
# The callback is a method: signature stays (agent):void, data comes from the fields
OnInteracted(Agent:agent):void =
Print("Button {Label} pressed!")
button_hub_device := class(creative_device):
@editable
ButtonA:button_device = button_device{}
@editable
ButtonB:button_device = button_device{}
OnBegin<override>()<suspends>:void =
ButtonA.InteractedWithEvent.Subscribe(button_handler{Label := "A"}.OnInteracted)
ButtonB.InteractedWithEvent.Subscribe(button_handler{Label := "B"}.OnInteracted)
Blueprint translation: button_handler is a small class holding a Label field and a callback method OnInteracted (whose signature is exactly the (agent):void Subscribe wants). button_hub_device is the device you place into the level; the two @editable button references are slots exposed to the Details panel where you assign concrete buttons in the level (that Instance Editable eye icon in Blueprint). In OnBegin (Event BeginPlay), each button gets bound to its own handler instance, each carrying a different Label.
Peel the subscribe line apart layer by layer: button_handler{Label := "A"} constructs an instance on the spot and fills its Label field with "A" (those curly braces get dissected next lesson); .OnInteracted pulls out that instance's method — no parentheses, so nothing gets called right here; what gets handed over is the method itself, for the engine to run later when the event fires. When button A is pressed, the engine runs A's instance's method, and the Label it reads is naturally "A". Ten buttons means ten instances, but the callback is written exactly once.
3. Why It's Equivalent to a Closure
A closure is, at heart, "a function plus an environment it remembers". The Handler pattern hand-builds that same structure: the instance's fields are that environment, the method is that function, and one object nails the two together and carries them everywhere. Every new instance you construct is one act of "remembering the current data" — add as many fields as data you want carried: device references, multipliers, colors all fit; store Device := Self in a field and the callback can even reach back and operate the device itself.
The pattern originates from the forum guide "Guide to event subscribing with additional parameters", cited thousands of times by the community, with an Epic employee (DiG) confirming in the thread that the official tutorials use the same pattern — in other words, this isn't a back-alley trick but the current version's officially blessed style. Later replies even include a variant that uses extension methods to squeeze the boilerplate further; having finished this lesson's other extra on extension methods, you might enjoy deriving it yourself.
One parting word of advice: every Subscribe (event binding) constructs a small object — that's deliberate, and it's cheap. Don't "save an object" by stuffing the data into a global variable and making the callback guess; that's a bug nursery.
You want a button callback to carry a "reward multiplier" parameter. What's the standard approach in current Verse?
When subscribing you write button_handler{Label := "A"}.OnInteracted — why no parentheses at the end?
Sources & Further Reading
Compiled from the Epic developer forums:
- Guide to event subscribing with additional parameters (handler functions) ↗ — the original Handler pattern guide, including Epic staff confirmation and the extension-method variant.