Overview
The grind_powerup_device lets agents slide along any surface — walls, floors, ramps — with built-in visual and audio flair. Mechanically it is a powerup_device: a pickup that applies a timed effect to whoever grabs it.
Reach for it whenever movement is the gameplay: a lava-floor escape where sliding is the only way to cross, a speed-parkour course, or a reward you grant from a button when a player clears a checkpoint. Because it inherits from powerup_device, you get a clean toolkit: Spawn/Despawn to control whether the pickup exists in the world, Pickup(Agent) to hand the effect directly to a player (no walking over it required), SetDuration/GetDuration to tune how long the slide lasts, and GetRemainingTime/HasEffect to query a player's current state. The ItemPickedUpEvent fires whenever someone collects it, so you can react with score, sounds, or chained logic.
The big advantage over the device-settings-only approach: in Verse you can grant the powerup conditionally — only to the player who finished a lap, only if they don't already have it, with a duration that scales to difficulty.
API Reference
grind_powerup_device
Used to let
agents slide on any surface with accompanying visual and audio effects.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse). Inherited members are merged from powerup_device.
grind_powerup_device<public> := class<concrete><final>(powerup_device):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
ItemPickedUpEvent |
ItemPickedUpEvent<public>:listenable(agent) |
Signaled when the powerup is picked up by an agent. Sends the agent that picked up the powerup. |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Spawn |
Spawn<public>():void |
Spawns the powerup into the experience so users can interact with it. |
Despawn |
Despawn<public>():void |
Despawns this powerup from the experience. |
SetDuration |
SetDuration<public>(Time:float):void |
Updates the Duration for this powerup, clamped to the Min and Max defined in the device. Will not apply to any currently applied effects. |
GetDuration |
GetDuration<public>()<transacts>:float |
Returns the Duration that this powerup will be active for on any player it is applied to. |
GetRemainingTime |
GetRemainingTime<public>(Agent:agent)<transacts>:float |
If the Agent has the effect applied to them, this will return the remaining time the effect has. Returns -1.0 if the effect has an infinite duration. Returns 0.0 if the Agent does not have the effect applied. |
HasEffect |
HasEffect<public>(Agent:agent)<transacts><decides>:void |
Returns the Agent has the powerup's effect (or another of the same type) applied to them. |
IsSpawned |
IsSpawned<public>()<transacts><decides>:void |
Succeeds if the powerup is currently spawned. |
Pickup |
Pickup<public>(Agent:agent):void |
Grants this powerup to Agent. |
Pickup |
Pickup<public>():void |
Grants this powerup without an agent reference. Requires Apply To set to All Players. |
powerup_device
Base class for various powerup devices offering common events like
ItemPickedUpEvent.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
powerup_device<public> := class<abstract><epic_internal>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
ItemPickedUpEvent |
ItemPickedUpEvent<public>:listenable(agent) |
Signaled when the powerup is picked up by an agent. Sends the agent that picked up the powerup. |
Methods (call these to make the device act):
| Method | Signature | Description |
|---|---|---|
Spawn |
Spawn<public>():void |
Spawns the powerup into the experience so users can interact with it. |
Despawn |
Despawn<public>():void |
Despawns this powerup from the experience. |
SetDuration |
SetDuration<public>(Time:float):void |
Updates the Duration for this powerup, clamped to the Min and Max defined in the device. Will not apply to any currently applied effects. |
GetDuration |
GetDuration<public>()<transacts>:float |
Returns the Duration that this powerup will be active for on any player it is applied to. |
GetRemainingTime |
GetRemainingTime<public>(Agent:agent)<transacts>:float |
If the Agent has the effect applied to them, this will return the remaining time the effect has. Returns -1.0 if the effect has an infinite duration. Returns 0.0 if the Agent does not have the effect applied. |
HasEffect |
HasEffect<public>(Agent:agent)<transacts><decides>:void |
Returns the Agent has the powerup's effect (or another of the same type) applied to them. |
IsSpawned |
IsSpawned<public>()<transacts><decides>:void |
Succeeds if the powerup is currently spawned. |
Pickup |
Pickup<public>(Agent:agent):void |
Grants this powerup to Agent. |
Pickup |
Pickup<public>():void |
Grants this powerup without an agent reference. Requires Apply To set to All Players. |
Walkthrough
Let's build a lava-floor crossing. A trigger sits at the entrance. When a player steps on it, we grant them the grind powerup directly (so they can slide across), but only if they don't already have it — and we lengthen the slide for the challenge. We also listen for the natural pickup event to play feedback, and we make sure the world pickup is actually spawned at game start.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
lava_floor_grind := class(creative_device):
# The Grind Powerup placed in the level.
@editable
GrindPowerup : grind_powerup_device = grind_powerup_device{}
# The trigger at the entrance to the lava floor.
@editable
EntranceTrigger : trigger_device = trigger_device{}
OnBegin<override>()<suspends>:void =
# Make sure the pickup actually exists in the world.
if (not GrindPowerup.IsSpawned[]):
GrindPowerup.Spawn()
# A 12-second slide is plenty to cross the lava.
GrindPowerup.SetDuration(12.0)
Print("Grind lasts {GrindPowerup.GetDuration()} seconds")
# React when a player steps on the entrance trigger.
EntranceTrigger.TriggeredEvent.Subscribe(OnEntrance)
# React when anyone naturally picks the powerup up.
GrindPowerup.ItemPickedUpEvent.Subscribe(OnPickedUp)
# trigger_device.TriggeredEvent hands us a (Agent : ?agent).
OnEntrance(MaybeAgent : ?agent):void =
if (Player := MaybeAgent?):
# Don't double-grant if they already have the effect.
if (not GrindPowerup.HasEffect[Player]):
GrindPowerup.Pickup(Player)
Print("Granted grind to a crosser!")
else:
Remaining := GrindPowerup.GetRemainingTime(Player)
Print("Player still has {Remaining} seconds of grind")
# ItemPickedUpEvent hands us a plain agent.
OnPickedUp(Player : agent):void =
Print("A player picked up the grind powerup!")
Line by line:
GrindPowerup : grind_powerup_device = grind_powerup_device{}andEntranceTrigger : trigger_deviceare@editablefields — that's what lets Verse talk to devices you placed in the level. You bind them in the Details panel.- In
OnBegin,if (not GrindPowerup.IsSpawned[])checks whether the pickup is already in the world;IsSpawnedis a<decides>function, so it goes in[]and can succeed or fail. If it isn't spawned, we callSpawn(). SetDuration(12.0)sets how long the slide effect lasts. Note the float literal12.0— Verse won't accept12.GetDuration()reads it back for our log.- We
Subscribetwo handlers inOnBegin: one to the trigger, one to the powerup'sItemPickedUpEvent. OnEntrancereceives?agentfrom the trigger, so we unwrap withif (Player := MaybeAgent?). We then testHasEffect[Player](also<decides>, so[]) — if they don't have it,Pickup(Player)grants the powerup straight to them. Otherwise we callGetRemainingTime(Player)to see how long they have left.OnPickedUpreceives a plainagent(the powerup event islistenable(agent), not?agent), so no unwrap is needed.
Common patterns
Despawn the world pickup once someone grabs it (single-use reward):
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
single_use_grind := class(creative_device):
@editable
GrindPowerup : grind_powerup_device = grind_powerup_device{}
OnBegin<override>()<suspends>:void =
GrindPowerup.Spawn()
GrindPowerup.ItemPickedUpEvent.Subscribe(OnGrabbed)
OnGrabbed(Player : agent):void =
# Take the pickup out of the world so nobody else can use it.
GrindPowerup.Despawn()
Print("Grind claimed — pickup removed.")
Grant to all players at once (Apply To = All Players):
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
race_start_grind := class(creative_device):
@editable
GrindPowerup : grind_powerup_device = grind_powerup_device{}
@editable
StartButton : button_device = button_device{}
OnBegin<override>()<suspends>:void =
GrindPowerup.SetDuration(20.0)
StartButton.InteractedWithEvent.Subscribe(OnStart)
OnStart(Player : agent):void =
# No agent argument: grants to everyone. Requires Apply To = All Players.
GrindPowerup.Pickup()
Print("Everyone gets the grind boost — race on!")
Poll remaining time and warn a player when it's almost gone:
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
grind_timer_watch := class(creative_device):
@editable
GrindPowerup : grind_powerup_device = grind_powerup_device{}
OnBegin<override>()<suspends>:void =
GrindPowerup.Spawn()
GrindPowerup.ItemPickedUpEvent.Subscribe(OnGrabbed)
OnGrabbed(Player : agent):void =
spawn { WatchEffect(Player) }
WatchEffect(Player : agent)<suspends>:void =
loop:
Sleep(1.0)
if (not GrindPowerup.HasEffect[Player]):
Print("Grind expired!")
break
Remaining := GrindPowerup.GetRemainingTime(Player)
if (Remaining <= 3.0):
Print("Grind ending in {Remaining}s!")
Gotchas
SetDurationdoesn't touch active effects. Per the API, it only changes the duration applied to future pickups. If a player is already sliding, changing the duration won't extend them — grant the powerup again (afterHasEffectchecks out) if you need to refresh.- Duration is clamped.
SetDurationclamps to the Min/Max set on the device in the editor. CallingSetDuration(999.0)won't give you 999 seconds unless the device's Max allows it. Read the real value back withGetDuration(). Pickup()with no agent needs the right setting. The argument-less overload only works when the device's Apply To option is set to All Players. Otherwise nothing happens — usePickup(Agent)for a specific player.<decides>functions go in square brackets.HasEffect[Agent]andIsSpawned[]succeed-or-fail; call them inside anif (...), and usenotto invert. Calling them with()won't compile.- Two different event payloads.
ItemPickedUpEventislistenable(agent)— your handler gets a plainagent, no?unwrap. Thetrigger_device.TriggeredEventislistenable(?agent), so that one needsif (A := MaybeAgent?). Don't mix them up. - Float vs int.
SetDurationand the magnitude helpers takefloat. Write12.0, never12— Verse does not auto-convert. GetRemainingTimereturn codes. It returns-1.0for infinite-duration effects and0.0when the agent has no effect — so a value of0.0is not 'about to expire', it's 'doesn't have it'. Guard withHasEffectfirst.