Overview
Most Fortnite AI ships with canned behavior, but the moment you want a guard on your 2D cel-shaded cove to idle → notice → focus on the intruder → give up and reset, you need a state machine you control. That's exactly what npc_behavior is for.
npc_behavior is an abstract Verse class in the /Fortnite.com/AI module. You subclass it, override its lifecycle hooks, and assign your subclass to an NPC — either on a CharacterDefinition asset or through an npc_spawner_device. When the NPC pops into the world, Verse calls your OnBegin<override>()<suspends>:void, and that suspending function is your state machine: an infinite loop that reads the world, decides which state the NPC is in, and drives actions like looking at a player.
Key pieces you get from the base class:
OnBegin()— runs when the NPC is added to the simulation. It suspends, so you can loop andSleephere forever.OnEnd()— runs when the NPC is removed; clean up here.GetAgent()— theagentfor this NPC, so you can find itsfort_characterand drive it.GetEntity()— the scene entity for this NPC.
Once you have the NPC's fort_character, the AI module hands you interfaces to act: GetFocusInterface() gives you a focus_interface whose MaintainFocus(Agent:agent) makes the NPC stare down a target. Reach for npc_behavior whenever you want an NPC that reacts to game state you define — proximity, health, round phase — rather than a fixed preset.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Our scenario: a guard NPC standing on the sunny dock. When a player steps within range, the guard snaps to attention and maintains focus on that player (the "alert" state). When the player leaves range, the guard drops focus and returns to a relaxed "patrol" state. This is a two-state machine — Patrol and Alert — living entirely inside OnBegin.
The npc_behavior subclass isn't a creative_device, so we put it in its own class. But you still need a placed device to make the walkthrough complete and compilable — here we use a creative_device that simply logs the wiring and holds our behavior alongside it. The real work happens in the behavior class.
using { /Fortnite.com }
using { /Fortnite.com/AI }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Game }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Our custom guard state machine. Assign this class to an NPC
# via a CharacterDefinition or an npc_spawner_device.
cove_guard_behavior<public> := class(npc_behavior):
# How close a player must get (in cm) to trigger the Alert state.
AlertRange<public>:float = 800.0
# Runs when the guard is added to the simulation. This IS the state machine.
OnBegin<override>()<suspends>:void =
# Grab the agent this behavior is driving.
if (GuardAgent := GetAgent[]):
# Get the fort_character so we can read its position and focus.
if (GuardChar := GuardAgent.GetFortCharacter[]):
loop:
# Only act while the guard is alive and in the world.
if (GuardChar.IsActive[]):
GuardPos := GuardChar.GetTransform().Translation
# Look for the nearest player within AlertRange.
if (Target := FindNearbyPlayer(GuardPos)):
EnterAlert(GuardChar, Target)
else:
Print("Guard patrolling the dock...")
Sleep(0.5)
# ALERT state: force the guard to stare at the intruder.
EnterAlert(Char:fort_character, Target:agent)<suspends>:void =
Print("Guard spotted an intruder! Locking focus.")
if (Focus := Char.GetFocusInterface[]):
# MaintainFocus never returns on its own; race it against a timeout
# so the guard re-evaluates after a moment.
race:
Focus.MaintainFocus(Target)
Sleep(2.0)
Print("Guard dropping focus, returning to patrol.")
# Scan every player in the game, return the closest one inside AlertRange.
FindNearbyPlayer(FromPos:vector3):?agent =
var Best:?agent = false
var BestDist:float = AlertRange
for (P : GetPlayspace().GetPlayers()):
if (PChar := P.GetFortCharacter[], PChar.IsActive[]):
D := Distance(FromPos, PChar.GetTransform().Translation)
if (D < BestDist):
set BestDist = D
set Best = option{P}
Best
# Called when the guard is removed from the simulation.
OnEnd<override>():void =
Print("Cove guard removed from the simulation.")
# A placed device so the project has an entry point and confirms wiring.
cove_guard_manager := class(creative_device):
OnBegin<override>()<suspends>:void =
Print("Cove guard manager online. Behavior assigned via spawner/CharacterDefinition.")
Line by line:
cove_guard_behavior<public> := class(npc_behavior):— we inherit from the abstract base. This is the class you pick in the NPC spawner or CharacterDefinition, NOT a device.AlertRange— a plain field acting as our tunable state-transition threshold.OnBegin<override>()<suspends>:void =— the lifecycle hook. Because it<suspends>, we canloopandSleepinside it indefinitely; that loop is the beating heart of the state machine.GetAgent[]— from the base class, returns the NPC'sagent. It<decides>, so we unwrap it withif (... := GetAgent[]).GuardAgent.GetFortCharacter[]— turns the agent into afort_characterwe can query and drive.- Inside
loop,GuardChar.IsActive[]guards against acting on a dead/removed NPC.GetTransform().Translationgives its world position. FindNearbyPlayerreturns a?agent; if it succeeds we transition to Alert, otherwise we stay in Patrol.EnterAlertgrabs thefocus_interfaceviaGetFocusInterface[]and callsMaintainFocus(Target). That call never completes on its own, so weraceit against aSleep(2.0)— after two seconds the race resolves, focus drops, and the loop re-evaluates.OnEnd<override>()cleans up when the NPC despawns.
Common patterns
Focus on a fixed shore location instead of a player
focus_interface has a second overload that takes a vector3 — great for a lookout NPC on the clifftop who always watches the cove entrance.
using { /Fortnite.com }
using { /Fortnite.com/AI }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/Diagnostics }
lookout_behavior<public> := class(npc_behavior):
# World point the lookout keeps watching (the cove mouth).
WatchPoint<public>:vector3 = vector3{X := 1200.0, Y := 300.0, Z := 100.0}
OnBegin<override>()<suspends>:void =
if (LookoutAgent := GetAgent[], LookoutChar := LookoutAgent.GetFortCharacter[]):
if (Focus := LookoutChar.GetFocusInterface[]):
Print("Lookout locking gaze on the cove entrance.")
# Maintains this look forever (until the NPC is removed).
Focus.MaintainFocus(WatchPoint)
OnEnd<override>():void =
Print("Lookout stood down.")
React to the NPC being eliminated
Use EliminatedEvent on the guard's own fort_character to run a death beat — like awarding the player who took it down.
using { /Fortnite.com }
using { /Fortnite.com/AI }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Game }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
mortal_guard_behavior<public> := class(npc_behavior):
OnBegin<override>()<suspends>:void =
if (GuardAgent := GetAgent[], GuardChar := GuardAgent.GetFortCharacter[]):
GuardChar.EliminatedEvent().Subscribe(OnGuardEliminated)
Print("Guard on duty, watching for elimination.")
OnGuardEliminated(Result:elimination_result):void =
Print("Guard down on the dock!")
if (Killer := Result.EliminatingCharacter?):
Print("A player took the guard out.")
OnEnd<override>():void =
Print("Guard removed.")
Drive a score device from an NPC behavior
An npc_behavior can't hold @editable device fields itself (it isn't a device), so the clean pattern is: a creative_device owns the score_manager_device and subscribes to a game event, awarding points. Here a placed manager grants score whenever its trigger fires.
using { /Fortnite.com }
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
guard_bounty_manager := class(creative_device):
@editable
ScoreManager:score_manager_device = score_manager_device{}
@editable
RewardTrigger:trigger_device = trigger_device{}
OnBegin<override>()<suspends>:void =
RewardTrigger.TriggeredEvent.Subscribe(OnBountyClaimed)
OnBountyClaimed(Agent:?agent):void =
if (Winner := Agent?):
# Grant the configured points to the player who defeated the guard.
ScoreManager.Activate(Winner)
Print("Bounty awarded for the cove guard.")
Gotchas
npc_behavioris NOT acreative_device. You cannot put@editabledevice references on it and you don't place it in the level. You assign the class to an NPC in a CharacterDefinition asset or thenpc_spawner_device. Put device-owning logic in a separatecreative_device.OnBeginsuspends,OnEnddoes not.OnBegin<override>()<suspends>:voidcan loop andSleep;OnEnd<override>():voidmust be synchronous — noSleep/raceinside it.MaintainFocusnever returns. Both overloads suspend forever until interrupted. If you don'traceit against aSleep(or another async op), your state machine loop will hang on that line and never re-evaluate.- Unwrap the
<decides>getters.GetAgent[],GetFortCharacter[],GetFocusInterface[], andIsActive[]all fail-able — use square brackets inside anif (... := ...)and never assume success. - Check
IsActive[]before acting. An NPC (or player) that's been eliminated or removed will make most actions silently fail; testIsActive[]so your loop doesn't spin on a corpse. ?agentpayloads must be unwrapped. Atrigger_device.TriggeredEventhands you(Agent:?agent); get the realagentwithif (Winner := Agent?):before passing it toScoreManager.Activate.- No int↔float auto-convert.
AlertRangeand distances are floats; don't mix them with int literals without an explicit conversion.