Reference Devices compiles

gameplay_controls_side_scroller_device: Building Side-Scroller Gameplay in UEFN

The `gameplay_controls_side_scroller_device` clamps player movement and facing to a directional axis, turning your island into a classic side-scroller. Pair it with a Fixed Angle or Fixed Point Camera device and you can gate the control scheme per-player — handing it out when a player enters a zone and reclaiming it when they leave. This article covers every method on the device with real, compilable Verse examples.

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

Overview

The gameplay_controls_side_scroller_device is a control-scheme switcher. Instead of letting players move freely in 3-D, it locks their movement and camera facing to a single axis — the hallmark of side-scrolling platformers. Under the hood it inherits from gameplay_controls_device, which manages a per-agent controls stack: you push a scheme onto the stack with AddTo / AddToAll, and pop it off with RemoveFrom / RemoveFromAll. The stack means you can layer control schemes and restore the previous one cleanly when a section ends.

When to reach for it:

  • You're building a platformer level inside a larger island and want only players inside that zone to use side-scroller controls.
  • You want to toggle the scheme on/off mid-match (e.g., a boss arena that locks movement to one axis).
  • You need fine-grained per-player control (hand it to one player, not all).

The device has no events — it is purely imperative. You call its methods from Verse in response to other devices' events (trigger plates, zone devices, round managers, etc.).

API Reference

gameplay_controls_side_scroller_device

Used to adapt the controls to side scroller functionality

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

gameplay_controls_side_scroller_device<public> := class<concrete><final>(gameplay_controls_device):

Methods (call these to make the device act):

Method Signature Description
Enable Enable<public>():void Enables this device.
Disable Disable<public>():void Disables this device.
AddTo AddTo<public>(Agent:agent):void Adds the gameplay control to the Player's gameplay controls stack and pushes it to be the active control.
AddToAll AddToAll<public>():void Adds the gameplay control to all Agents gameplay controls stack and pushes it to be the active control.
RemoveFrom RemoveFrom<public>(Agent:agent):void Removes the gameplay control from the Agent's gameplay controls stack and pops from being the active control replacing it with the next one in the stack.
RemoveFromAll RemoveFromAll<public>():void Removes the gameplay control from all Agents gameplay controls stack and pops from being the active control replacing it with the next one in the stack.

gameplay_controls_device

Used to update the gameplay controls scheme based on current control mode.

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

gameplay_controls_device<public> := class<abstract><epic_internal>(creative_device_base):

Methods (call these to make the device act):

Method Signature Description
Enable Enable<public>():void Enables this device.
Disable Disable<public>():void Disables this device.
AddTo AddTo<public>(Agent:agent):void Adds the gameplay control to the Player's gameplay controls stack and pushes it to be the active control.
AddToAll AddToAll<public>():void Adds the gameplay control to all Agents gameplay controls stack and pushes it to be the active control.
RemoveFrom RemoveFrom<public>(Agent:agent):void Removes the gameplay control from the Agent's gameplay controls stack and pops from being the active control replacing it with the next one in the stack.
RemoveFromAll RemoveFromAll<public>():void Removes the gameplay control from all Agents gameplay controls stack and pops from being the active control replacing it with the next one in the stack.

Walkthrough

Scenario: A Side-Scroller Zone with Entry and Exit Plates

A player steps on a green trigger plate to enter the side-scroller section. The device pushes side-scroller controls onto that player. When they step on a red exit plate, the controls are popped and normal movement resumes. The device is also disabled entirely between rounds to prevent late-joiners from picking it up.

# side_scroller_zone_manager.verse
# Place this Verse device in your level, then wire up the editable fields in the editor.

side_scroller_zone_manager := class(creative_device):

    # The Side Scroller Controls device placed in the level.
    @editable
    SideScrollerControls : gameplay_controls_side_scroller_device = gameplay_controls_side_scroller_device{}

    # Trigger plate the player steps on to ENTER the side-scroller zone.
    @editable
    EntryPlate : trigger_device = trigger_device{}

    # Trigger plate the player steps on to EXIT the side-scroller zone.
    @editable
    ExitPlate : trigger_device = trigger_device{}

    OnBegin<override>()<suspends> : void =
        # Start with the device enabled so it can be added to players.
        SideScrollerControls.Enable()

        # Subscribe to both plates. Handlers are class-scope methods.
        EntryPlate.TriggeredEvent.Subscribe(OnPlayerEntered)
        ExitPlate.TriggeredEvent.Subscribe(OnPlayerExited)

    # Called when a player steps on the entry plate.
    # TriggeredEvent sends ?agent, so we must unwrap it.
    OnPlayerEntered(MaybeAgent : ?agent) : void =
        if (Agent := MaybeAgent?):
            # Push side-scroller controls onto this player's stack.
            # Their movement is now clamped to the side-scroller axis.
            SideScrollerControls.AddTo(Agent)

    # Called when a player steps on the exit plate.
    OnPlayerExited(MaybeAgent : ?agent) : void =
        if (Agent := MaybeAgent?):
            # Pop side-scroller controls from this player's stack.
            # The previous control scheme (e.g. default Fortnite) is restored.
            SideScrollerControls.RemoveFrom(Agent)

Line-by-line breakdown:

Line What it does
@editable SideScrollerControls Binds the placed device so Verse can call its methods. Without @editable the identifier is unknown at compile time.
SideScrollerControls.Enable() Activates the device so it can accept AddTo calls. A disabled device cannot be pushed onto any player's stack.
EntryPlate.TriggeredEvent.Subscribe(OnPlayerEntered) Wires the plate's event to our handler. The handler runs on the game thread each time a player steps on the plate.
OnPlayerEntered(MaybeAgent : ?agent) TriggeredEvent delivers ?agent (an optional). We must unwrap it before passing to AddTo.
SideScrollerControls.AddTo(Agent) Pushes the side-scroller scheme onto that specific player's controls stack. Only this player is affected.
SideScrollerControls.RemoveFrom(Agent) Pops the scheme. The stack restores whatever was active before — typically default Fortnite controls.

Common patterns

Pattern 1 — Apply side-scroller controls to ALL players at round start, remove at round end

Useful for a dedicated side-scroller island where every player should always be in the scheme.

# all_players_side_scroller.verse

all_players_side_scroller := class(creative_device):

    @editable
    SideScrollerControls : gameplay_controls_side_scroller_device = gameplay_controls_side_scroller_device{}

    # A class_design_device or round_settings_device signals round start/end.
    # Here we use two trigger_devices as stand-ins for "round started" and "round ended" signals.
    @editable
    RoundStartTrigger : trigger_device = trigger_device{}

    @editable
    RoundEndTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends> : void =
        SideScrollerControls.Enable()
        RoundStartTrigger.TriggeredEvent.Subscribe(OnRoundStart)
        RoundEndTrigger.TriggeredEvent.Subscribe(OnRoundEnd)

    OnRoundStart(MaybeAgent : ?agent) : void =
        # Push side-scroller controls onto every connected player at once.
        SideScrollerControls.AddToAll()

    OnRoundEnd(MaybeAgent : ?agent) : void =
        # Pop side-scroller controls from every player at once.
        SideScrollerControls.RemoveFromAll()

AddToAll() and RemoveFromAll() are the broadcast versions — no agent unwrapping needed because they target everyone automatically.


Pattern 2 — Disable the device entirely to block late-joiners

When the side-scroller section is not active, Disable() prevents the device from being pushed onto any new player who joins mid-match. Enable() re-arms it for the next round.

# side_scroller_round_gate.verse

side_scroller_round_gate := class(creative_device):

    @editable
    SideScrollerControls : gameplay_controls_side_scroller_device = gameplay_controls_side_scroller_device{}

    # Button a host presses to open / close the side-scroller section.
    @editable
    OpenSectionButton : button_device = button_device{}

    @editable
    CloseSectionButton : button_device = button_device{}

    OnBegin<override>()<suspends> : void =
        # Section is closed by default — disable the device immediately.
        SideScrollerControls.Disable()

        OpenSectionButton.InteractedWithEvent.Subscribe(OnOpenSection)
        CloseSectionButton.InteractedWithEvent.Subscribe(OnCloseSection)

    OnOpenSection(MaybeAgent : ?agent) : void =
        # Re-enable the device so it can be pushed onto players.
        SideScrollerControls.Enable()
        # Immediately apply to everyone currently in the match.
        SideScrollerControls.AddToAll()

    OnCloseSection(MaybeAgent : ?agent) : void =
        # Remove from everyone first, then disable.
        SideScrollerControls.RemoveFromAll()
        SideScrollerControls.Disable()

Key insight: Disable() does not pop the scheme from players already using it — call RemoveFromAll() first, then Disable(), to cleanly shut down the section.


Pattern 3 — Per-player toggle (add if not active, remove if active)

A pressure plate acts as a toggle: first step adds the scheme, second step removes it. Useful for optional side-scroller corridors a player can opt into.

# side_scroller_toggle.verse

side_scroller_toggle := class(creative_device):

    @editable
    SideScrollerControls : gameplay_controls_side_scroller_device = gameplay_controls_side_scroller_device{}

    @editable
    TogglePlate : trigger_device = trigger_device{}

    # Track which agents currently have the scheme active.
    var ActiveAgents : []agent = array{}

    OnBegin<override>()<suspends> : void =
        SideScrollerControls.Enable()
        TogglePlate.TriggeredEvent.Subscribe(OnToggle)

    OnToggle(MaybeAgent : ?agent) : void =
        if (Agent := MaybeAgent?):
            # Check if this agent is already in our active list.
            if (ActiveAgents.Find[Agent] = _):
                # Already active — remove the scheme.
                SideScrollerControls.RemoveFrom(Agent)
                set ActiveAgents = ActiveAgents.RemoveAllElements(Agent)
            else:
                # Not active — add the scheme.
                SideScrollerControls.AddTo(Agent)
                set ActiveAgents = ActiveAgents + array{Agent}

Gotchas

1. You MUST declare the device as @editable

A bare gameplay_controls_side_scroller_device{} literal in OnBegin creates a detached default instance — it is not the device you placed in the level. Every call on it silently does nothing. Always use:

@editable
SideScrollerControls : gameplay_controls_side_scroller_device = gameplay_controls_side_scroller_device{}

Then assign the placed device in the editor's Details panel.

2. Disable() does not auto-remove from players

Calling Disable() prevents future AddTo calls from working, but players who already have the scheme on their stack keep it. Always call RemoveFromAll() before Disable() if you want a clean teardown.

3. The controls stack is ordered — order matters

If you push Scheme A then Scheme B, popping B restores A. If you push A, push B, then pop A (without popping B first), B is still active. Design your push/pop calls symmetrically to avoid orphaned schemes.

4. TriggeredEvent delivers ?agent, not agent

Always unwrap with if (Agent := MaybeAgent?): before passing to AddTo or RemoveFrom. Forgetting this is a compile error because the parameter type is agent, not ?agent.

5. Pair with a camera device for full effect

The Side Scroller Controls device only locks movement/facing. Without a Fixed Angle Camera or Fixed Point Camera device also applied to the player, they will still have a free-roaming camera. Wire both devices together for authentic side-scroller feel.

6. Early Access caveat

As of the 30.30 release notes, this device is Early Access. APIs and behavior may change in future UEFN versions. Pin your project's engine version and test after updates.

Build your own lesson with gameplay_controls_side_scroller_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 →