Overview
The sentry_device generates an AI bot that spawns at a fixed location and attacks players (and creatures) that come within its range. It's the go-to device for guarded vaults, ambush rooms, boss fights, and escape-room set pieces where you want a threat the player has to fight or sneak past.
Where it really shines is scripted combat. Instead of letting the sentry just sit there, Verse lets you:
- Spawn / DestroySentry it on a trigger (e.g. only after the player grabs the key).
- Enable / Disable the whole device.
- JoinTeam / ResetTeam so the sentry temporarily fights for the player, then turns hostile again.
- Target / Pacify / EnableAlert / ResetAlertCooldown to micromanage who it shoots and when.
- React to AlertedEvent, AttackingEvent, EliminatedEvent, EliminatingAgentEvent and more to drive cinematics, score, and follow-up waves.
Reach for it whenever you need an enemy that responds to where the player is — not a scripted projectile, but a living guard you can choreograph.
API Reference
sentry_device
Generates an AI bot that spawns in a location and usually attacks players when they come in range.
Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).
sentry_device<public> := class<concrete><final>(creative_device_base):
Events (subscribe a handler to react):
| Event | Signature | Description |
|---|---|---|
AlertedEvent |
AlertedEvent<public>:listenable(agent) |
Signaled when the sentry is alerted to an agent. Sends the agent who alerted the sentry. |
ExitsAlertEvent |
ExitsAlertEvent<public>:listenable(tuple()) |
Signaled when the sentry exists the alert state. |
EntersAlertCooldownEvent |
EntersAlertCooldownEvent<public>:listenable(tuple()) |
Signaled when the sentry enters the alert state. |
AttackingEvent |
AttackingEvent<public>:listenable(agent) |
Signaled when a sentry attacks an agent. Sends the agent who is being attacked. |
EliminatedEvent |
EliminatedEvent<public>:listenable(?agent) |
Signaled when a sentry is eliminated. Sends the agent that eliminated the sentry. If the sentry was eliminated by a non-agent then false is returned. |
EliminatingACreatureEvent |
EliminatingACreatureEvent<public>:listenable(tuple()) |
Signaled when the sentry eliminates a creature. |
EliminatingAgentEvent |
EliminatingAgentEvent<public>:listenable(agent) |
Signaled when a sentry eliminates an agent. Sends the agent who was eliminated by the sentry. |
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. |
Spawn |
Spawn<public>():void |
Spawns the sentry. |
DestroySentry |
DestroySentry<public>():void |
Destroys the current sentry. |
JoinTeam |
JoinTeam<public>(Agent:agent):void |
Sets the sentry to the same team Agent is on. |
ResetTeam |
ResetTeam<public>():void |
Resets the sentry to the original team designated in the device options. |
Pacify |
Pacify<public>():void |
Puts the sentry into the pacify state, preventing from entering the alert (attacking) state. |
EnableAlert |
EnableAlert<public>():void |
Puts the sentry into the alert state. |
Target |
Target<public>(Agent:agent):void |
Sets the sentry to target Agent. The sentry will not target agents on the same team as the sentry. |
ResetAlertCooldown |
ResetAlertCooldown<public>():void |
Resets the alert state. |
Walkthrough
Let's build a vault guardian. The sentry stays hidden until the player steps on a pressure plate. When the plate fires, the sentry spawns and we force it to target the triggering player. When the player finally eliminates the sentry, we open the vault (a prop_mover_device) and award the win. We also log alerts so you can see the state machine in action.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
vault_guardian := class(creative_device):
@editable
Guardian : sentry_device = sentry_device{}
@editable
StartPlate : trigger_device = trigger_device{}
@editable
VaultDoor : prop_mover_device = prop_mover_device{}
Logger : log = log{Channel := vault_log}
OnBegin<override>()<suspends>:void =
# Sentry is off until the player triggers the fight.
Guardian.Disable()
# When the player steps on the plate, start the encounter.
StartPlate.TriggeredEvent.Subscribe(OnPlateStepped)
# React to the sentry's combat lifecycle.
Guardian.AlertedEvent.Subscribe(OnAlerted)
Guardian.AttackingEvent.Subscribe(OnAttacking)
Guardian.EliminatedEvent.Subscribe(OnSentryEliminated)
OnPlateStepped(Agent : ?agent):void =
Guardian.Enable()
Guardian.Spawn()
# Force the sentry onto whoever started the fight.
if (Player := Agent?):
Guardian.Target(Player)
Logger.Print("Vault guardian spawned and locked onto the intruder!")
OnAlerted(Attacker : agent):void =
Logger.Print("Guardian is alerted!")
OnAttacking(Victim : agent):void =
Logger.Print("Guardian is firing on a player!")
OnSentryEliminated(MaybeAgent : ?agent):void =
# The sentry is down — open the vault.
VaultDoor.Begin()
if (Winner := MaybeAgent?):
Logger.Print("Guardian defeated! Vault is opening.")
else:
Logger.Print("Guardian destroyed by the environment. Vault opening.")
vault_log := class(log_channel){}
Line by line:
- The
@editablefields (Guardian,StartPlate,VaultDoor) let you drag the placed devices into the script from the Details panel. Without the@editablefield you cannot call the device's methods. - In
OnBeginwe immediatelyDisable()the sentry so it doesn't exist until the fight starts. - We
Subscribethe plate'sTriggeredEventand three of the sentry's events. Handlers are plain methods at class scope. OnPlateSteppedreceives(Agent : ?agent)from the trigger. WeEnable()thenSpawn()the sentry, then unwrap the agent withif (Player := Agent?)and callTarget(Player)so the bot zeroes in on the intruder.OnSentryEliminatedreceives?agentbecause the eliminator might be a non-agent (fall damage, a trap). We unwrap it; either way we callVaultDoor.Begin()to open the vault.vault_logis the log channel referenced by theLoggerfield.
Common patterns
Make the sentry fight FOR the player, then betray them
JoinTeam puts the sentry on the player's team (it won't shoot teammates). ResetTeam snaps it back to its original hostile team. Great for an allied-turret-turns-traitor twist.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
turncoat_sentry := class(creative_device):
@editable
Sentry : sentry_device = sentry_device{}
@editable
AllyButton : button_device = button_device{}
@editable
BetrayButton : button_device = button_device{}
OnBegin<override>()<suspends>:void =
Sentry.Spawn()
AllyButton.InteractedWithEvent.Subscribe(OnRecruit)
BetrayButton.InteractedWithEvent.Subscribe(OnBetray)
OnRecruit(Agent : agent):void =
# Sentry joins the player's team and stops attacking them.
Sentry.JoinTeam(Agent)
OnBetray(Agent : agent):void =
# Back to the original hostile team.
Sentry.ResetTeam()
Pacify during a cutscene, then re-alert
Pacify stops the sentry from entering its attack state — perfect while a cinematic plays. EnableAlert forces it back into combat afterward, and ResetAlertCooldown clears the cooldown so it can re-acquire immediately.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
scripted_sentry := class(creative_device):
@editable
Sentry : sentry_device = sentry_device{}
@editable
CutsceneStart : trigger_device = trigger_device{}
@editable
CutsceneEnd : trigger_device = trigger_device{}
OnBegin<override>()<suspends>:void =
Sentry.Spawn()
CutsceneStart.TriggeredEvent.Subscribe(OnCutsceneStart)
CutsceneEnd.TriggeredEvent.Subscribe(OnCutsceneEnd)
OnCutsceneStart(Agent : ?agent):void =
# Freeze the sentry so it won't shoot mid-cutscene.
Sentry.Pacify()
OnCutsceneEnd(Agent : ?agent):void =
# Wake it up and let it acquire targets right away.
Sentry.EnableAlert()
Sentry.ResetAlertCooldown()
Track kills and creature cleanup
React to what the sentry eliminates. EliminatingAgentEvent sends the downed player; EliminatingACreatureEvent carries no payload (it's tuple()).
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
kill_tracker := class(creative_device):
@editable
Sentry : sentry_device = sentry_device{}
Logger : log = log{Channel := kill_log}
OnBegin<override>()<suspends>:void =
Sentry.Spawn()
Sentry.EliminatingAgentEvent.Subscribe(OnEliminatedPlayer)
Sentry.EliminatingACreatureEvent.Subscribe(OnEliminatedCreature)
OnEliminatedPlayer(Victim : agent):void =
Logger.Print("The sentry eliminated a player!")
OnEliminatedCreature():void =
Logger.Print("The sentry cleared out a creature.")
kill_log := class(log_channel){}
Gotchas
Spawn≠Enable.Enable()turns the device on;Spawn()actually creates the bot. A disabled device won't spawn. In the walkthrough we callEnable()beforeSpawn(). If you set Spawn on Game Start to false in the device options, you must callSpawn()from Verse for the bot to appear.EliminatedEventhands you?agent, notagent. The sentry might be killed by something with no agent (a trap, fall damage). Always unwrap withif (Winner := MaybeAgent?):before using the eliminator. The same is true of trigger handlers(Agent : ?agent).AttackingEvent/AlertedEvent/EliminatingAgentEventhand you a plainagent(already unwrapped) — don't try to?-unwrap those or you'll get a type error.Targetonly sticks if the agent is reachable. The sentry will refuse to target an agent outside its activation radius, out of line-of-sight, on its own team, or Down-But-Not-Out. CallingTargeton an out-of-range player silently does nothing.JoinTeammakes the sentry ignore that agent's whole team. If you want it hostile again, you must callResetTeam()— there's no automatic timeout.Pacifyblocks alert;EnableAlertoverrides it. If you pacified the sentry and want it fighting again, callEnableAlert()(and optionallyResetAlertCooldown()), not justEnable().- Event handlers must be class-scope methods, and you subscribe in
OnBegin. A bareSentry.Spawn()outside acreative_deviceclass with an@editablefield fails with 'Unknown identifier'.