Overview
The prop_mover_device is the engine behind every moving platform, sliding gate, and swinging hazard in UEFN. You place it in the scene, attach it to a prop (configured in the device's editor properties), and then drive it entirely from Verse.
When to reach for it:
- A vault door that slides open when a player solves a puzzle
- A moving platform that players must jump between
- A pendulum trap that reverses direction on a timer
- A patrol hazard that knocks players back and fires an event when it does
The device exposes two categories of API:
| Category | What it gives you |
|---|---|
| Motion control | Begin, End, Advance, Reverse, SetTargetDistance, SetTargetSpeed |
| Reactive events | BeganEvent, EndedEvent, FinishedEvent, MovementModeChangedEvent, AgentHitEvent, AIHitEvent, PropHitEvent |
API Reference
prop_mover_device
Used to move around a building or prop, and customize responses to various collision event types.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
prop_mover_device<public> := class<concrete><final>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
EnabledEvent |
EnabledEvent<public>:listenable(tuple()) |
Signaled when this device is enabled. |
DisabledEvent |
DisabledEvent<public>:listenable(tuple()) |
Signaled when this device is disabled. |
BeganEvent |
BeganEvent<public>:listenable(tuple()) |
Signaled when the prop movement begins. |
EndedEvent |
EndedEvent<public>:listenable(tuple()) |
Signaled when the prop movement ends. |
FinishedEvent |
FinishedEvent<public>:listenable(tuple()) |
Signaled when the prop reaches its destination. |
MovementModeChangedEvent |
MovementModeChangedEvent<public>:listenable(tuple()) |
Signaled when the prop changes its direction. |
AgentHitEvent |
AgentHitEvent<public>:listenable(agent) |
Signaled when the prop hits an agent. Sends the agent hit by the prop. |
AIHitEvent |
AIHitEvent<public>:listenable(tuple()) |
Signaled when the prop hits a creature, animal, or NPC. |
PropHitEvent |
PropHitEvent<public>:listenable(tuple()) |
Signaled when the prop hits another prop. |
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. |
Begin |
Begin<public>():void |
Begins the prop moving. |
End |
End<public>():void |
Ends the prop moving. |
Advance |
Advance<public>():void |
Moves the prop forward based on this device's default configuration, ignoring the prop's previous movement. |
Reverse |
Reverse<public>():void |
Reverses the prop's moving direction. |
SetTargetDistance |
SetTargetDistance<public>(InDistance:float):void |
Sets the total distance (in meters) that the prop will move. |
GetTargetDistance |
GetTargetDistance<public>()<transacts>:float |
Returns the total distance (in meters) that the prop will move. |
SetTargetSpeed |
SetTargetSpeed<public>(Speed:float):void |
Sets the speed (in meters per second) at which the prop will move to its destination. |
GetTargetSpeed |
GetTargetSpeed<public>()<transacts>:float |
Returns the speed (in meters per second) at which the prop mover will move the prop to its destination. |
Walkthrough
Scenario: A Timed Crushing Trap
A heavy stone block slides across a corridor. When it hits a player, it reverses. When it reaches the far end, it pauses for two seconds and then advances again. A trigger plate activates the whole system.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Drives a sliding stone-block trap.
# Wire: TriggerPlate fires when a player steps on it;
# PropMover is attached to the stone block prop.
crushing_trap_device := class(creative_device):
# The prop mover driving the stone block
@editable
PropMover : prop_mover_device = prop_mover_device{}
# A trigger_device the player steps on to activate the trap
@editable
TriggerPlate : trigger_device = trigger_device{}
# How fast the block moves (m/s)
@editable
TrapSpeed : float = 4.0
# How far the block travels (m)
@editable
TrapDistance : float = 12.0
# -------------------------------------------------------
OnBegin<override>()<suspends> : void =
# Configure speed and distance before motion starts
PropMover.SetTargetSpeed(TrapSpeed)
PropMover.SetTargetDistance(TrapDistance)
# Subscribe to events
TriggerPlate.TriggeredEvent.Subscribe(OnPlateTriggered)
PropMover.AgentHitEvent.Subscribe(OnAgentHit)
PropMover.FinishedEvent.Subscribe(OnReachedEnd)
PropMover.MovementModeChangedEvent.Subscribe(OnDirectionChanged)
# Called when a player steps on the trigger plate
OnPlateTriggered(Agent : ?agent) : void =
PropMover.Enable()
PropMover.Begin()
# Called when the block crushes a player — reverse immediately
OnAgentHit(HitAgent : agent) : void =
PropMover.Reverse()
# Called when the block reaches the far end — pause then advance again
OnReachedEnd(Unused : tuple()) : void =
spawn { PauseAndAdvance() }
# Called whenever the block changes direction
OnDirectionChanged(Unused : tuple()) : void =
# Could play a sound cue, shake camera, etc.
# Here we just log the event conceptually — real logic goes here
var CurrentSpeed : float = PropMover.GetTargetSpeed()
# Optionally ramp speed: PropMover.SetTargetSpeed(CurrentSpeed + 0.5)
# Waits 2 seconds then sends the block forward again
PauseAndAdvance()<suspends> : void =
Sleep(2.0)
PropMover.Advance()
Line-by-line breakdown:
SetTargetSpeed/SetTargetDistance— called inOnBeginbefore motion starts so the device is fully configured before any player interaction.TriggerPlate.TriggeredEvent.Subscribe(OnPlateTriggered)— the plate's event hands a?agent; we don't need to unwrap it here because we're just starting the mover.PropMover.AgentHitEvent.Subscribe(OnAgentHit)—AgentHitEventis alistenable(agent)(not optional), so the handler receives a plainagentdirectly.FinishedEvent— fires when the prop reaches its destination. Wespawna coroutine soSleepdoesn't block the event handler.MovementModeChangedEvent— fires on every direction flip. Useful for SFX, camera shake, or speed escalation.Advance()— resets to the device's default forward direction regardless of previous movement, perfect for restarting a loop.
Common patterns
Pattern 1 — Enable/Disable and runtime speed scaling
A platform that speeds up each time it completes a lap, disabled by a button.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Escalating-speed moving platform.
escalating_platform_device := class(creative_device):
@editable
Platform : prop_mover_device = prop_mover_device{}
@editable
StopButton : button_device = button_device{}
var LapCount : int = 0
var BaseSpeed : float = 3.0
OnBegin<override>()<suspends> : void =
Platform.SetTargetSpeed(BaseSpeed)
Platform.Enable()
Platform.Begin()
StopButton.InteractedWithEvent.Subscribe(OnStopPressed)
Platform.FinishedEvent.Subscribe(OnLapComplete)
OnStopPressed(Agent : agent) : void =
Platform.End()
Platform.Disable()
OnLapComplete(Unused : tuple()) : void =
set LapCount = LapCount + 1
# Increase speed by 1 m/s per lap, cap at 15
var NewSpeed : float = BaseSpeed + (1.0 * LapCount)
if (NewSpeed > 15.0):
set NewSpeed = 15.0
Platform.SetTargetSpeed(NewSpeed)
Platform.Advance()
Key calls: Enable, Disable, Begin, End, SetTargetSpeed, GetTargetSpeed (pattern shows SetTargetSpeed driving escalation), Advance to restart the loop.
Pattern 2 — Reacting to AI and Prop collisions
A patrol hazard that logs when it hits an NPC or another prop (e.g. a boulder that knocks over barrels).
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Patrol hazard that reacts to hitting NPCs and other props.
patrol_hazard_device := class(creative_device):
@editable
Hazard : prop_mover_device = prop_mover_device{}
# How far the hazard patrols (m)
@editable
PatrolDistance : float = 20.0
OnBegin<override>()<suspends> : void =
Hazard.SetTargetDistance(PatrolDistance)
Hazard.SetTargetSpeed(5.0)
Hazard.Enable()
Hazard.Begin()
Hazard.AIHitEvent.Subscribe(OnHitAI)
Hazard.PropHitEvent.Subscribe(OnHitProp)
Hazard.EndedEvent.Subscribe(OnMovementEnded)
# Fires when the hazard hits a creature/NPC — reverse and keep going
OnHitAI(Unused : tuple()) : void =
Hazard.Reverse()
# Fires when the hazard hits another prop — slow down briefly
OnHitProp(Unused : tuple()) : void =
spawn { SlowAndResume() }
# Fires when End() is called externally
OnMovementEnded(Unused : tuple()) : void =
# Could re-enable after a delay
spawn { RestartAfterDelay() }
SlowAndResume()<suspends> : void =
var OldSpeed : float = Hazard.GetTargetSpeed()
Hazard.SetTargetSpeed(1.0)
Sleep(1.5)
Hazard.SetTargetSpeed(OldSpeed)
RestartAfterDelay()<suspends> : void =
Sleep(5.0)
Hazard.Enable()
Hazard.Begin()
Key calls: AIHitEvent, PropHitEvent, EndedEvent, Reverse, GetTargetSpeed, SetTargetSpeed.
Pattern 3 — Query distance and speed before starting
A puzzle door that reads its configured values and only starts if they're within safe bounds.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# Puzzle vault door — validates config before opening.
vault_door_device := class(creative_device):
@editable
Door : prop_mover_device = prop_mover_device{}
@editable
PuzzleSolvedTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends> : void =
PuzzleSolvedTrigger.TriggeredEvent.Subscribe(OnPuzzleSolved)
Door.BeganEvent.Subscribe(OnDoorStarted)
Door.FinishedEvent.Subscribe(OnDoorFullyOpen)
OnPuzzleSolved(Agent : ?agent) : void =
var Dist : float = Door.GetTargetDistance()
var Speed : float = Door.GetTargetSpeed()
# Safety clamp: door must travel at least 1m and no faster than 10 m/s
if (Dist < 1.0):
Door.SetTargetDistance(1.0)
if (Speed > 10.0):
Door.SetTargetSpeed(10.0)
Door.Enable()
Door.Begin()
OnDoorStarted(Unused : tuple()) : void =
# BeganEvent fires — could trigger a sound or particle effect here
var _ : float = Door.GetTargetSpeed() # read current speed if needed
OnDoorFullyOpen(Unused : tuple()) : void =
# Door reached destination — disable so it can't be re-triggered
Door.Disable()
Key calls: GetTargetDistance, GetTargetSpeed, SetTargetDistance, SetTargetSpeed, BeganEvent, FinishedEvent, Enable, Disable, Begin.
Gotchas
1. AgentHitEvent sends agent, not ?agent
AgentHitEvent is typed listenable(agent) — the handler receives a plain agent directly. You do not need to unwrap it with if (A := Agent?):. Contrast this with events like trigger_device.TriggeredEvent which sends ?agent and does require unwrapping.
# CORRECT — plain agent, no unwrap needed
OnAgentHit(HitAgent : agent) : void =
# use HitAgent directly
# WRONG — this won't compile
# OnAgentHit(HitAgent : ?agent) : void = ...
2. tuple() event handlers must accept the tuple argument
All events typed listenable(tuple()) — BeganEvent, EndedEvent, FinishedEvent, MovementModeChangedEvent, AIHitEvent, PropHitEvent — require a handler that accepts a tuple() parameter, even though you'll never use it.
# CORRECT
OnFinished(Unused : tuple()) : void =
PropMover.Advance()
# WRONG — missing the tuple parameter
# OnFinished() : void = ...
3. Never call Sleep directly inside an event handler
Event handlers are not coroutines — calling Sleep inside one will fail to compile. Always spawn a separate <suspends> function.
# CORRECT
OnFinished(Unused : tuple()) : void =
spawn { PauseAndRestart() }
PauseAndRestart()<suspends> : void =
Sleep(2.0)
PropMover.Advance()
# WRONG — Sleep requires <suspends>, event handlers don't have it
# OnFinished(Unused : tuple()) : void =
# Sleep(2.0) # compile error
4. Advance vs Begin — they are not the same
Begin()starts movement respecting the prop's current state and previous motion.Advance()always moves the prop forward from the device's default configuration, ignoring any previous movement. UseAdvanceto reliably restart a loop; useBeginto resume from a paused state.
5. Configure speed/distance before calling Begin
SetTargetSpeed and SetTargetDistance take effect immediately, but if you call them after Begin the prop may already be in motion. Set them in OnBegin (the Verse lifecycle method) before any player interaction is possible.
6. The device must be enabled before Begin will work
Calling Begin() on a disabled device has no effect. Always call Enable() first if the device starts disabled in the editor, or if you previously called Disable().