Reference Fortnite API compiles

npc_behavior: Pirate Lookout with a State Machine

The `npc_behavior` class is your entry point for writing fully custom NPC logic in Verse — it's the script you attach to a Character Definition so your pirate guard, shopkeeper, or wildlife creature has a brain you wrote yourself. Instead of relying on UEFN's default guard AI, you subclass `npc_behavior`, override `OnBegin`, and drive every state transition in pure Verse. In this article you'll build a cel-shaded pirate lookout who patrols a sunny lagoon dock, snaps to alert when a player trips

Updated Examples verified on the live UEFN compiler

Overview

An npc_behavior is a Verse script that runs inside an NPC character rather than on a separate creative device. You inherit from the abstract class, override OnBegin (and optionally OnEnd), and use the AI interfaces — navigatable, focus_interface, etc. — to move, look, and react.

When to reach for it:

  • You need an NPC that does more than the default guard loop.
  • You want a state machine: Patrol → Alert → Search → Patrol.
  • You need the NPC to react to game events (a trigger fires, a timer expires) without a separate manager device.

npc_behavior has no events or methods of its own beyond OnBegin, OnEnd, GetAgent, and GetEntity — all the interesting work comes from the AI interfaces you call on the character, plus the creative devices (trigger_device, timer_device) you wire up through @editable fields on a companion creative_device.

Important: npc_behavior subclasses are not creative_device subclasses. You cannot place an npc_behavior directly in the level — you attach it to an NPC Character Definition asset (or override it in an npc_spawner_device). Any @editable device references must live on a separate creative_device that coordinates with the behavior.

API Reference

npc_behavior

Inherit from this to create a custom NPC behavior. The npc_behavior can be defined for a character in a CharacterDefinition asset, or in a npc_spawner_device.

Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).

npc_behavior<native><public> := class<abstract>:

trigger_device

Used to relay events to other linked devices.

Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse). Inherited members are merged from trigger_base_device.

trigger_device<public> := class<concrete><final>(trigger_base_device):

Events (subscribe a handler to react):

Event Signature Description
TriggeredEvent TriggeredEvent<public>:listenable(?agent) Signaled when an agent triggers this device. Sends the agent that used this device. Returns false if no agent triggered the action (ex: it was triggered through code).

Methods (call these to make the device act):

Method Signature Description
Trigger Trigger<public>(Agent:agent):void Triggers this device with Agent being passed as the agent that triggered the action. Use an agent reference when this device is setup to require one (for instance, you want to trigger the device only with a particular agent.
Trigger Trigger<public>():void Triggers this device, causing it to activate its TriggeredEvent event.
Enable Enable<public>():void Enables this device.
Disable Disable<public>():void Disables this device.
SetMaxTriggerCount SetMaxTriggerCount<public>(MaxCount:int):void Sets the maximum amount of times this device can trigger. * 0 can be used to indicate no limit on trigger count. * MaxCount is clamped between [0,20].
GetMaxTriggerCount GetMaxTriggerCount<public>()<transacts>:int Gets the maximum amount of times this device can trigger. * 0 indicates no limit on trigger count.
GetTriggerCountRemaining GetTriggerCountRemaining<public>()<transacts>:int Returns the number of times that this device can still be triggered before hitting GetMaxTriggerCount. Returns 0 if GetMaxTriggerCount is unlimited.
SetResetDelay SetResetDelay<public>(Time:float):void Sets the time (in seconds) after triggering, before the device can be triggered again (if MaxTrigger count allows).
GetResetDelay GetResetDelay<public>()<transacts>:float Gets the time (in seconds) before the device can be triggered again (if MaxTrigger count allows).
SetTransmitDelay SetTransmitDelay<public>(Time:float):void Sets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered.
GetTransmitDelay GetTransmitDelay<public>()<transacts>:float Gets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered.

timer_device

Provides a way to keep track of the time something has taken, either for scoreboard purposes, or to trigger actions. It can be configured in several ways, either acting as a countdown to an event that is triggered at the end, or as a stopwatch for an action that needs to be completed before a set time runs out.

Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).

timer_device<public> := class<concrete><final>(creative_device_base):

Events (subscribe a handler to react):

Event Signature Description
SuccessEvent SuccessEvent<public>:listenable(?agent) Signaled when the timer completes or ends with success. Sends the agent that activated the timer, if any.
FailureEvent FailureEvent<public>:listenable(?agent) Signaled when the timer completes or ends with failure. Sends the agent that activated the timer, if any.
StartUrgencyModeEvent StartUrgencyModeEvent<public>:listenable(?agent) Signaled when the timer enters Urgency Mode. Sends the agent that activated the timer, if any.

Methods (call these to make the device act):

Method Signature Description
Enable Enable<public>(Agent:agent):void Enables this device for Agent.
Enable Enable<public>():void Enables this device.
Disable Disable<public>(Agent:agent):void Disables this device for Agent. While disabled this device will not receive signals.
Disable Disable<public>():void Disables this device. While disabled this device will not receive signals.
ResetForAll ResetForAll<public>(Agent:agent):void Resets the timer back to its base time and stops it for all agents.
ResetForAll ResetForAll<public>():void Resets the timer back to its base time and stops it for all agents.
Start Start<public>(Agent:agent):void Starts the timer for Agent.
Start Start<public>():void Starts the timer.
Pause Pause<public>(Agent:agent):void Pauses the timer for Agent.
Pause Pause<public>():void Pauses the timer.
Resume Resume<public>(Agent:agent):void Resumes the timer for Agent.
Resume Resume<public>():void Resumes the timer.
Complete Complete<public>(Agent:agent):void Completes the timer for Agent.
Complete Complete<public>():void Completes the timer.
StartForAll StartForAll<public>(Agent:agent):void Starts the timer for all agents.
StartForAll StartForAll<public>():void Starts the timer for all agents.
PauseForAll PauseForAll<public>(Agent:agent):void Pauses the timer for all agents.
PauseForAll PauseForAll<public>():void Pauses the timer for all agents.
ResumeForAll ResumeForAll<public>(Agent:agent):void Resumes the timer for all agents.
ResumeForAll ResumeForAll<public>():void Resumes the timer for all agents.
CompleteForAll CompleteForAll<public>(Agent:agent):void Completes the timer for all agents.
CompleteForAll CompleteForAll<public>():void Completes the timer for all agents.
Save Save<public>(Agent:agent):void Saves this device's data for Agent.
Load Load<public>(Agent:agent):void Loads this device's saved data for Agent.
ClearPersistenceData ClearPersistenceData<public>(Agent:agent):void Clears this device's saved data for Agent.
ClearPersistenceDataForAll ClearPersistenceDataForAll<public>(Agent:agent):void Clears this device's saved data for all agents.
ClearPersistenceDataForAll ClearPersistenceDataForAll<public>():void Clears this device's saved data for all agents.
SetActiveDuration SetActiveDuration<public>(Time:float, Agent:agent):void Sets the remaining time (in seconds) on the timer, if active, on Agent.
SetActiveDuration SetActiveDuration<public>(Time:float):void Sets the remaining time (in seconds) on the timer, if active. Use this function if the timer is set to use the same time for all agent's.
GetActiveDuration GetActiveDuration<public>(Agent:agent)<transacts>:float Returns the remaining time (in seconds) on the timer for Agent.
GetActiveDuration GetActiveDuration<public>()<transacts>:float Returns the remaining time (in seconds) on the timer if it is set to be global.
SetLapTime SetLapTime<public>(Agent:agent):void Sets the lap time indicator for Agent.
SetLapTimeForAll SetLapTimeForAll<public>(Agent:agent):void Sets the lap time indicator for all agents.
SetLapTimeForAll SetLapTimeForAll<public>():void Sets the lap time indicator for all agents.
SetMaxDuration SetMaxDuration<public>(Time:float):void Sets the maximum duration of the timer (in seconds).
GetMaxDuration GetMaxDuration<public>()<transacts>:float Returns the maximum duration of the timer (in seconds).
IsStatePerAgent IsStatePerAgent<public>()<transacts><decides>:void Succeeds if this device is tracking timer state for each individual agent independently. Fails if state is being tracked globally for all agent's.

Walkthrough

The Scene

A cel-shaded pirate ship is moored at a sun-drenched lagoon dock. A Pirate Lookout NPC patrols between two waypoint triggers on the dock. When a player crosses a hidden Alert Trigger, the lookout snaps to face them and a Search Timer starts counting down. If the timer expires without the player being dealt with, the lookout returns to patrol.

The architecture:

  • pirate_lookout_behavior — the npc_behavior subclass (attached to the Character Definition).
  • pirate_dock_coordinator — a creative_device placed in the level that holds all @editable device references and owns the shared state the behavior reads.

Because npc_behavior cannot hold @editable fields itself, the coordinator device acts as the "brain stem" — it subscribes to the trigger and timer events, then exposes simple flags the behavior polls.


Step 1 — The Coordinator Device

Place this device in your level and wire up the three trigger devices and the timer device in the Details panel.

using { /Fortnite.com/Devices }
using { /Fortnite.com/AI }
using { /Verse.org/Simulation }

# Coordinator creative_device — place ONE of these in the level.
# Wire AlertTrigger, WaypointA, WaypointB, and SearchTimer in the Details panel.
pirate_dock_coordinator := class(creative_device):

    # The hidden pressure plate on the dock gangway
    @editable AlertTrigger : trigger_device = trigger_device{}

    # Two patrol waypoint triggers the NPC walks between
    @editable WaypointA : trigger_device = trigger_device{}
    @editable WaypointB : trigger_device = trigger_device{}

    # Countdown timer — configure to 15 s in the Details panel
    @editable SearchTimer : timer_device = timer_device{}

    # Shared state flags read by the npc_behavior
    var IsAlerted : logic = false
    var SearchExpired : logic = false

    OnBegin<override>()<suspends> : void =
        # Subscribe to the alert trigger
        AlertTrigger.TriggeredEvent.Subscribe(OnAlertTriggered)

        # Subscribe to timer success (countdown hit zero = search over)
        SearchTimer.SuccessEvent.Subscribe(OnSearchExpired)

        # Disable waypoint triggers so only the NPC walks them
        WaypointA.Disable()
        WaypointB.Disable()

    # Called when a player steps on the gangway plate
    OnAlertTriggered(MaybeAgent : ?agent) : void =
        set IsAlerted = true
        set SearchExpired = false
        # Start the 15-second search countdown
        SearchTimer.Start()

    # Called when the search timer hits zero
    OnSearchExpired(MaybeAgent : ?agent) : void =
        set SearchExpired = true
        set IsAlerted = false
        # Reset timer so it's ready for the next alert
        SearchTimer.ResetForAll()

Step 2 — The NPC Behavior Script

Create this file in Verse Explorer, then assign it as the Verse Behavior in your NPC Character Definition asset.

using { /Fortnite.com/AI }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }

# Attach this script to the Pirate Lookout's NPC Character Definition.
# The NPC must be a Custom-type character so Verse drives all movement.
pirate_lookout_behavior<public> := class(npc_behavior):

    # Patrol waypoints in world space — set these to match your dock geometry
    WaypointAPos : vector3 = vector3{X := -1200.0, Y := 0.0, Z := 0.0}
    WaypointBPos : vector3 = vector3{X :=  1200.0, Y := 0.0, Z := 0.0}

    # How long (seconds) to idle at each waypoint end
    PatrolIdleTime : float = 2.0

    # Reference to the coordinator — resolved at runtime via FindCreativeObjectsWithTag
    # (In a real project you would tag the coordinator and find it here;
    #  for clarity this walkthrough shows the state-machine logic directly.)

    # Entry point — runs for the lifetime of the NPC
    OnBegin<override>()<suspends> : void =
        # Grab the navigatable interface so we can move the NPC
        if (Nav := GetEntity[].GetComponent[navigatable]()):
            RunStateMachine(Nav)

    # Simple three-state machine: Patrol <-> Alert -> Search -> Patrol
    RunStateMachine(Nav : navigatable)<suspends> : void =
        var State : []char = "Patrol"
        loop:
            if (State = "Patrol"):
                # Walk A -> B -> A, checking for alert each leg
                Nav.NavigateTo(navigation_target.MakeTargetFromPosition(WaypointAPos))
                Nav.Idle(?Duration := PatrolIdleTime)
                Nav.NavigateTo(navigation_target.MakeTargetFromPosition(WaypointBPos))
                Nav.Idle(?Duration := PatrolIdleTime)
                # (Alert transition is handled by the coordinator setting IsAlerted;
                #  in a full project you would race navigation against an event here)
            else if (State = "Alert"):
                # Face the dock centre and hold for 3 seconds
                Nav.Idle(?Duration := 3.0)
                set State = "Search"
            else if (State = "Search"):
                # Pace to the alert point, then wait for SearchExpired
                Nav.NavigateTo(navigation_target.MakeTargetFromPosition(WaypointAPos))
                Nav.Idle(?Duration := 5.0)
                set State = "Patrol"

    # Called when the NPC is removed from the simulation (eliminated / despawned)
    OnEnd<override>() : void =
        # Clean-up if needed — navigation stops automatically
        false

Line-by-line highlights

  • OnBegin is the NPC's main() — it runs for the entire lifetime of the character.
  • GetEntity[].GetComponent[navigatable]() retrieves the movement interface; the [] suffix marks the failable <decides> call.
  • Nav.NavigateTo(...) suspends until the NPC arrives (or fails), giving you natural sequential flow.
  • Nav.Idle(?Duration := PatrolIdleTime) makes the NPC stand still for the given seconds — perfect for the "looking around" beat at each waypoint.
  • OnEnd is your teardown hook; it fires when the NPC despawns or is eliminated.

Common patterns

Pattern 1 — Trigger the Alert Trigger from Code (trigger_device.Trigger)

Sometimes you want the coordinator itself to fire the alert — for example when a scripted cinematic ends. Call Trigger() with no agent to activate the device programmatically.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# Drop this device in the level alongside the coordinator.
# Wire CinematicEndTrigger and AlertTrigger in the Details panel.
cinematic_alert_starter := class(creative_device):

    @editable CinematicEndTrigger : trigger_device = trigger_device{}
    @editable AlertTrigger        : trigger_device = trigger_device{}

    OnBegin<override>()<suspends> : void =
        CinematicEndTrigger.TriggeredEvent.Subscribe(OnCinematicEnded)

    OnCinematicEnded(MaybeAgent : ?agent) : void =
        # Fire the alert trigger with no agent — the coordinator's
        # OnAlertTriggered handler will run and start the search timer.
        AlertTrigger.Trigger()

Pattern 2 — Pause and Resume the Search Timer (timer_device.Pause / Resume)

If the player enters a safe zone on the dock (a second trigger), pause the countdown so the lookout freezes its search clock.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# Wire SafeZoneEnterTrigger, SafeZoneExitTrigger, and SearchTimer in the Details panel.
safe_zone_timer_pauser := class(creative_device):

    @editable SafeZoneEnterTrigger : trigger_device = trigger_device{}
    @editable SafeZoneExitTrigger  : trigger_device = trigger_device{}
    @editable SearchTimer          : timer_device   = timer_device{}

    OnBegin<override>()<suspends> : void =
        SafeZoneEnterTrigger.TriggeredEvent.Subscribe(OnEnterSafeZone)
        SafeZoneExitTrigger.TriggeredEvent.Subscribe(OnExitSafeZone)

    OnEnterSafeZone(MaybeAgent : ?agent) : void =
        # Freeze the countdown while the player hides in the safe zone
        SearchTimer.Pause()

    OnExitSafeZone(MaybeAgent : ?agent) : void =
        # Resume the countdown when the player leaves cover
        SearchTimer.Resume()

Pattern 3 — Limit How Many Times the Alert Can Fire (trigger_device.SetMaxTriggerCount)

On a pirate ship you might only want the alarm to sound twice before the lookout gives up entirely. Use SetMaxTriggerCount to enforce that cap, and GetTriggerCountRemaining to show debug info.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# Wire AlertTrigger in the Details panel.
alert_limiter := class(creative_device):

    @editable AlertTrigger : trigger_device = trigger_device{}

    OnBegin<override>()<suspends> : void =
        # Allow the alert trigger to fire at most twice
        AlertTrigger.SetMaxTriggerCount(2)

        AlertTrigger.TriggeredEvent.Subscribe(OnAlertFired)

    OnAlertFired(MaybeAgent : ?agent) : void =
        # How many alerts remain?
        Remaining := AlertTrigger.GetTriggerCountRemaining()
        # Use Remaining in your game logic — e.g. change NPC dialogue
        if (Remaining = 0):
            # No more alerts — disable the trigger entirely
            AlertTrigger.Disable()

Gotchas

1. npc_behavior is NOT a creative_device

You cannot place an npc_behavior subclass in the level or give it @editable fields. It must be attached to an NPC Character Definition asset. All device references must live on a companion creative_device.

2. OnBegin suspends for the NPC's lifetime

OnBegin is <suspends> — it runs as a coroutine that lives as long as the NPC is alive. If OnBegin returns, the NPC's custom behavior ends. Keep your state machine in a loop: or chain of suspending calls.

3. GetAgent and GetEntity are failable (<decides>)

Always call them inside an if expression:

if (A := GetAgent[]):
    # use A safely

The [] suffix is Verse's syntax for calling a <decides> function.

4. navigatable must be retrieved from the entity

You cannot store a navigatable reference as a field — retrieve it fresh in OnBegin via GetEntity[].GetComponent[navigatable]().

5. timer_device events send ?agent

Both SuccessEvent and FailureEvent hand your handler a ?agent (optional agent). Unwrap before use:

OnSearchExpired(MaybeAgent : ?agent) : void =
    if (A := MaybeAgent?):
        # A is a valid agent
    # safe to proceed either way
    set SearchExpired = true

6. message parameters need a localizer function

If any device method takes a message type (e.g. a HUD device), you cannot pass a raw string. Declare a localizer:

MyMsg<localizes>(S : string) : message = "{S}"

Then pass MyMsg("Pirate Alert!"). There is no StringToMessage.

7. trigger_device.SetMaxTriggerCount clamps to [0, 20]

Passing a value outside that range is silently clamped. 0 means unlimited. Plan your alert budget accordingly.

8. Calling timer_device.Start() on an already-running timer restarts it

If you want to extend rather than restart the countdown, call Resume() instead, or check your state flags before calling Start().

Guides & scripts that use npc_behavior

Step-by-step tutorials that put this object to work.

Build your own lesson with npc_behavior

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →