Reference Verse compiles

Guard Clause Validation with trigger_device: Reject Bad Input Early

On a sunny 2D cel-shaded pirate cove, a cannon on the clifftop should only fire when a REAL player steps on the plank — not a stray code-triggered ghost, not after the ammo runs out. Guard clause validation is the art of checking those conditions up front and bailing early, so your trigger_device only does its job when every precondition is met.

Updated Examples verified on the live UEFN compiler
Watch the Knottrigger_device in ~90 seconds.

Overview

A guard clause is a check placed at the very top of a handler that validates its inputs and exits early if something is wrong. Instead of nesting your real logic inside deep if pyramids, you flip the condition: "if the thing I need isn't here, stop now." What's left below the guards is code that runs only when all preconditions hold.

In Verse this maps perfectly onto failable expressions (if (X := Expr):) and the option unwrap. The classic case is a trigger_device: its TriggeredEvent is a listenable(?agent), meaning it may hand you an agent — or false when the device was fired from code with the no-argument Trigger(). You must guard against that before touching the player.

Reach for guard-clause validation whenever a device event depends on state that might not be valid: a cannon that should only fire while it has ammo (GetTriggerCountRemaining), a plank that must be Enabled, or an agent-only trap that must ignore ghost triggers. Our sunny-cove scenario: a plank on a pirate ship that arms a clifftop cannon — but only for real players, and only while shots remain.

API Reference

trigger_device

Used to relay events to other linked devices.

Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse). Inherited members are merged from trigger_base_device.

trigger_device<public> := class<concrete><final>(trigger_base_device):

Events (subscribe a handler to react):

Event Signature Description
TriggeredEvent TriggeredEvent<public>:listenable(?agent) Signaled when an agent triggers this device. Sends the agent that used this device. Returns false if no agent triggered the action (ex: it was triggered through code).

Methods (call these to make the device act):

Method Signature Description
Trigger Trigger<public>(Agent:agent):void Triggers this device with Agent being passed as the agent that triggered the action. Use an agent reference when this device is setup to require one (for instance, you want to trigger the device only with a particular agent.
Trigger Trigger<public>():void Triggers this device, causing it to activate its TriggeredEvent event.
Enable Enable<public>():void Enables this device.
Disable Disable<public>():void Disables this device.
SetMaxTriggerCount SetMaxTriggerCount<public>(MaxCount:int):void Sets the maximum amount of times this device can trigger. * 0 can be used to indicate no limit on trigger count. * MaxCount is clamped between [0,20].
GetMaxTriggerCount GetMaxTriggerCount<public>()<transacts>:int Gets the maximum amount of times this device can trigger. * 0 indicates no limit on trigger count.
GetTriggerCountRemaining GetTriggerCountRemaining<public>()<transacts>:int Returns the number of times that this device can still be triggered before hitting GetMaxTriggerCount. Returns 0 if GetMaxTriggerCount is unlimited.
SetResetDelay SetResetDelay<public>(Time:float):void Sets the time (in seconds) after triggering, before the device can be triggered again (if MaxTrigger count allows).
GetResetDelay GetResetDelay<public>()<transacts>:float Gets the time (in seconds) before the device can be triggered again (if MaxTrigger count allows).
SetTransmitDelay SetTransmitDelay<public>(Time:float):void Sets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered.
GetTransmitDelay GetTransmitDelay<public>()<transacts>:float Gets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered.

player

Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse). Inherited members are merged from agent.

player<native><public> := class<unique><persistent><module_scoped_var_weak_map_key><epic_internal>(agent):

vector3

3-dimensional vector with float components.

Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse).

vector3<native><public> := struct<concrete><computes><persistable><uht_comparable>:

Walkthrough

Here's the full scene. A PlankPlate trigger sits on the pirate ship's gangplank. When a pirate walks it, we validate the agent, confirm the cannon still has shots left, and only then fire the CannonTrigger — which is linked to the actual cannon VFX/damage device in the editor. Each guard clause is a one-line early exit.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

cove_cannon_device := class(creative_device):

    # The plank the player steps on.
    @editable
    PlankPlate : trigger_device = trigger_device{}

    # The cannon: firing it means calling Trigger on this linked device.
    @editable
    CannonTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends>:void =
        # Cannon starts with a limited magazine and a cooldown.
        CannonTrigger.SetMaxTriggerCount(5)
        CannonTrigger.SetResetDelay(1.5)
        # React to a pirate stepping on the plank.
        PlankPlate.TriggeredEvent.Subscribe(OnPlankStepped)

    OnPlankStepped(Agent : ?agent) : void =
        # GUARD 1: bail if no real agent triggered this (code trigger sends false).
        if (StepperAgent := Agent?):
            FireCannonFor(StepperAgent)

    FireCannonFor(Firer : agent) : void =
        # GUARD 2: bail if the cannon is out of ammo.
        Remaining := CannonTrigger.GetTriggerCountRemaining()
        if (Remaining > 0):
            # All guards passed — do the real work.
            CannonTrigger.Trigger(Firer)

Line by line:

  • PlankPlate and CannonTrigger are two placed trigger_devices exposed with @editable. You can't call methods on a device unless it's an editable field on the creative_device class — a bare Trigger() would fail with Unknown identifier.
  • In OnBegin, SetMaxTriggerCount(5) gives the cannon a 5-shot magazine (clamped 0–20; 0 means unlimited). SetResetDelay(1.5) forces a 1.5-second cooldown between shots.
  • PlankPlate.TriggeredEvent.Subscribe(OnPlankStepped) wires the plank's event to our handler method.
  • GUARD 1 lives in OnPlankStepped. The handler receives Agent : ?agent — an option. if (StepperAgent := Agent?): succeeds only when a genuine agent is present. A Trigger() fired from code (no agent) sends false, so this guard silently rejects ghost triggers.
  • GUARD 2 lives in FireCannonFor. We read GetTriggerCountRemaining() and only proceed if it's above zero. Once the magazine is empty the cannon simply won't fire.
  • Only after both guards pass do we call CannonTrigger.Trigger(Firer), passing the validated agent so the cannon fires for that specific pirate.

Common patterns

Pattern 1 — Enable/Disable as a state guard. Instead of checking a boolean flag, use the device's own enabled state. Here a lever Trigger arms the plank; before that it's Disabled so steps do nothing.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

cove_arming_device := class(creative_device):

    @editable
    LeverTrigger : trigger_device = trigger_device{}

    @editable
    PlankPlate : trigger_device = trigger_device{}

    OnBegin<override>()<suspends>:void =
        # Plank is inert until the lever is pulled.
        PlankPlate.Disable()
        LeverTrigger.TriggeredEvent.Subscribe(OnLeverPulled)

    OnLeverPulled(Agent : ?agent) : void =
        # Guard: only a real pirate can arm the trap.
        if (Puller := Agent?):
            PlankPlate.Enable()

Pattern 2 — Reject the shot when transmit delay hasn't been configured. You can read back a setter's value and guard on it. Here we only fire if the cannon's reset delay is long enough to be "safe" (a silly but illustrative validation), and we tune transmit delay first.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

cove_delay_guard_device := class(creative_device):

    @editable
    CannonTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends>:void =
        CannonTrigger.SetTransmitDelay(0.5)
        CannonTrigger.SetResetDelay(2.0)
        # Guard on configured state before ever firing.
        Reset := CannonTrigger.GetResetDelay()
        Transmit := CannonTrigger.GetTransmitDelay()
        if (Reset >= 1.0, Transmit >= 0.0):
            # Both conditions in one guard — fire the opening salvo.
            CannonTrigger.Trigger()

Pattern 3 — Compare against the magazine size. Use GetMaxTriggerCount alongside GetTriggerCountRemaining to detect "first shot" vs "last shot" and guard accordingly.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

cove_last_shot_device := class(creative_device):

    @editable
    PlankPlate : trigger_device = trigger_device{}

    @editable
    CannonTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends>:void =
        CannonTrigger.SetMaxTriggerCount(3)
        PlankPlate.TriggeredEvent.Subscribe(OnStepped)

    OnStepped(Agent : ?agent) : void =
        if (Firer := Agent?):
            Max := CannonTrigger.GetMaxTriggerCount()
            Remaining := CannonTrigger.GetTriggerCountRemaining()
            # Guard: only fire while shots remain, and give the final salvo priority.
            if (Remaining > 0):
                CannonTrigger.Trigger(Firer)

Gotchas

  • ?agent is an option, not an agent. TriggeredEvent hands you Agent : ?agent. You must unwrap with if (A := Agent?): before using it. Skipping this is the #1 guard-clause mistake — and it's exactly the case where a code-side Trigger() (no agent) sends false.
  • Trigger() vs Trigger(Agent). The no-arg overload fires the event with no agent, so downstream ?agent guards will reject it. If you need the event consumer to know who fired, always pass an agent with Trigger(Firer).
  • GetTriggerCountRemaining returns 0 for unlimited. If you set SetMaxTriggerCount(0) (unlimited), GetTriggerCountRemaining() also returns 0. A naive if (Remaining > 0): guard would then never fire. Guard on GetMaxTriggerCount() too if you allow unlimited magazines.
  • SetMaxTriggerCount is clamped 0–20. Passing 50 silently becomes 20. Read it back with GetMaxTriggerCount() if the exact value matters to your logic.
  • Verse doesn't auto-convert int↔float. GetTriggerCountRemaining() returns int; GetResetDelay() returns float. Don't compare or mix them without an explicit conversion.
  • Guards must be early exits, not deep nesting. The point is that the failable if wraps the rest of the work — anything after a failed guard simply doesn't run. Keep validated logic flat and readable.

Guides & scripts that use trigger_device

Step-by-step tutorials that put this object to work.

Build your own lesson with trigger_device

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →