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.