Reference Verse

Clamp: Keeping Numbers in Bounds on Your Island

Every game eventually needs to stop a number from flying off the rails — a health bar that won't go above 100, a speed that won't drop below zero, a countdown that won't tick past its limit. Verse's built-in `Clamp` function is the one-liner that handles all of that. In this article you'll learn exactly how `Clamp` works and wire it into a real pirate-island moment: a player races across a sun-bleached dock before the tide timer runs out, and the remaining-time display is clamped so it never sho

Updated
The code on this reference page is provided as-is and did not pass the latest compile check — treat the examples as a starting point and verify in your project.
Watch the Knottrigger_device in ~90 seconds.

Overview

On a bright, cel-shaded pirate cove, you constantly deal with values that must stay inside a range. The cannon's elevation should live between 0° and 60°. The treasure-hunt meter should never drop below 0 or climb past its max. A player's boost speed should be capped so they don't rocket off the dock.

That's exactly what Clamp does: it takes a value and two bounds and returns the value squeezed into [A, B]. Its integer siblings Min and Max pick the smaller or larger of two whole numbers — perfect for counting collected doubloons or capping a wave counter.

Reach for these when:

  • A running total could overshoot a limit (score, meter, ammo).
  • You compute a position/angle/speed and need to keep it sane.
  • You want "never below zero" or "never above max" in one clean line.

These are built-in Verse math functions, not placed devices. You call them directly from any Verse code — no @editable field required for the math itself (though you still declare devices you want to drive with the result).

API Reference

(API surface could not be resolved for this device.)

Walkthrough

The scene: A sunny lagoon dock. Players step on a trigger plate to "crank" a treasure-hunt meter upward. Each crank adds points, but the meter caps at 100 — and if you idle, it drifts back down but never below 0. We drive a real hud_message_device to show the clamped value.

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

# A localized-text helper: `message` params need this, not a raw string.
treasure_text<localizes>(S : string) : message = "{S}"

treasure_meter := class(creative_device):

    # The plate players step on to crank the meter up.
    @editable
    CrankPlate : trigger_device = trigger_device{}

    # Shows the current clamped meter value to players.
    @editable
    MeterHud : hud_message_device = hud_message_device{}

    # The raw, unclamped running total. Can drift out of range...
    var RawMeter : float = 0.0

    # ...but this is what we actually show, always inside [0, 100].
    MeterMin : float = 0.0
    MeterMax : float = 100.0

    OnBegin<override>()<suspends>:void =
        # Each step on the plate cranks the meter.
        CrankPlate.TriggeredEvent.Subscribe(OnCrank)
        # Run a slow drift-down loop in the background.
        spawn{ DriftLoop() }

    OnCrank(Agent : ?agent) : void =
        # Add a chunk of progress to the raw total.
        set RawMeter = RawMeter + 15.0
        ShowMeter()

    DriftLoop()<suspends> : void =
        loop:
            Sleep(1.0)
            # Bleed the raw meter down over time.
            set RawMeter = RawMeter - 4.0
            ShowMeter()

    ShowMeter() : void =
        # Clamp guarantees the shown value is always in [0, 100],
        # no matter how far RawMeter has drifted.
        Shown := Clamp(RawMeter, MeterMin, MeterMax)
        MeterHud.SetText(treasure_text("Treasure: {Round[Shown]}%"))

Line by line:

  • treasure_text<localizes> turns a plain string into a message, which SetText requires. There is no StringToMessage.
  • CrankPlate and MeterHud are @editable device fields — you assign real placed devices in the UEFN details panel.
  • RawMeter is a var because it changes. It's allowed to go negative or above 100; that's fine because we never show it directly.
  • In OnCrank, the trigger_device's TriggeredEvent hands us (Agent : ?agent). We don't even need the agent here, so we just add progress.
  • DriftLoop runs forever in a spawn, subtracting a little each second so an idle meter falls back toward empty.
  • ShowMeter is the star: Clamp(RawMeter, MeterMin, MeterMax) returns RawMeter squeezed into [0.0, 100.0]. Crank past 100 and it shows 100; drift below 0 and it shows 0.
  • Round[Shown] converts the clamped float to a clean integer for display.

Common patterns

Capping collected doubloons with Min

A chest can only hold so much gold. When a player deposits coins, use Min so the stored amount never exceeds the chest's capacity — and grant the difference back later if you like.

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

doubloon_text<localizes>(S : string) : message = "{S}"

treasure_chest := class(creative_device):

    @editable
    DepositButton : button_device = button_device{}

    @editable
    ChestHud : hud_message_device = hud_message_device{}

    var Stored : int = 0
    Capacity : int = 50

    OnBegin<override>()<suspends>:void =
        DepositButton.InteractedWithEvent.Subscribe(OnDeposit)

    OnDeposit(Agent : agent) : void =
        # Player tries to add 12 coins, but the chest is capped.
        Wanted := Stored + 12
        # Min keeps us at or below Capacity.
        set Stored = Min(Wanted, Capacity)
        ChestHud.SetText(doubloon_text("Gold: {Stored}/{Capacity}"))

Min(Wanted, Capacity) returns whichever is smaller, so Stored can never pass 50. Note button_device's InteractedWithEvent hands the agent directly (not an option).

Enforcing a floor with Max

When a shark bites, the crew loses morale — but morale should never go below zero. Max gives you a clean "never below" floor.

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

morale_text<localizes>(S : string) : message = "{S}"

crew_morale := class(creative_device):

    @editable
    SharkTrigger : trigger_device = trigger_device{}

    @editable
    MoraleHud : hud_message_device = hud_message_device{}

    var Morale : int = 100

    OnBegin<override>()<suspends>:void =
        SharkTrigger.TriggeredEvent.Subscribe(OnBite)

    OnBite(Agent : ?agent) : void =
        # Lose 30 morale, but Max clamps the floor to 0.
        set Morale = Max(Morale - 30, 0)
        MoraleHud.SetText(morale_text("Crew Morale: {Morale}"))

Max(Morale - 30, 0) returns 0 whenever the subtraction would go negative, so morale bottoms out gracefully.

Clamping a cannon's aim angle

A cannon should only tilt between 0° and 60°. Whatever raw angle you compute from player input, Clamp keeps the barrel from pointing at the sea floor or straight up.

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

aim_text<localizes>(S : string) : message = "{S}"

pirate_cannon := class(creative_device):

    @editable
    RaiseButton : button_device = button_device{}

    @editable
    AimHud : hud_message_device = hud_message_device{}

    var AimAngle : float = 0.0
    MinAngle : float = 0.0
    MaxAngle : float = 60.0

    OnBegin<override>()<suspends>:void =
        RaiseButton.InteractedWithEvent.Subscribe(OnRaise)

    OnRaise(Agent : agent) : void =
        # Each press nudges the barrel up 25 degrees...
        Raw := AimAngle + 25.0
        # ...but Clamp keeps it between 0 and 60.
        set AimAngle = Clamp(Raw, MinAngle, MaxAngle)
        AimHud.SetText(aim_text("Cannon Aim: {Round[AimAngle]} deg"))

After three presses the raw angle would be 75°, but Clamp pins it to 60° — the barrel stops at its physical limit.

Gotchas

  • Clamp is float-only; Min/Max shown here are int. Verse does not auto-convert int↔float. Clamp(RawMeter, 0.0, 100.0) needs float literals (0.0, not 0). For integers use Min/Max — or convert with Round[...], Floor[...], or Int[...].
  • message params never take raw strings. SetText wants a message. Declare a <localizes> helper like treasure_text<localizes>(S:string):message = "{S}" and pass treasure_text("..."). There is no StringToMessage.
  • Argument order doesn't break Clamp. The docs note it "robustly handles different argument orderings" — Clamp(V, 100.0, 0.0) still constrains to the 0–100 range. But write bounds low-then-high for readability.
  • Clamp the displayed value, not necessarily the stored one. In the walkthrough RawMeter is free to drift out of range; we only clamp when showing. This keeps the underlying math simple and avoids sticky values pinned at a boundary.
  • Call devices from @editable fields. The math functions are global built-ins, but the HUD/trigger/button you drive with the result MUST be @editable fields on your creative_device, subscribed in OnBegin. A bare SomeDevice.Method() on an undeclared device fails with 'Unknown identifier'.
  • trigger_device gives you ?agent; button_device gives you agent. Match your handler signature to the event. If you get an option, unwrap with if (A := Agent?): before using it.

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 →