Reference Verse compiles

State Machine with NPC Behavior: A Pirate Lookout on the Clifftop

UEFN's `npc_behavior` class is your blank canvas for writing fully custom NPC logic in Verse. By combining it with `trigger_device` and `timer_device` you can build a proper finite-state machine: a pirate lookout who idles on the clifftop dock, snaps to alert when a player trips a trigger, and resets after a countdown — all in cel-shaded, sun-drenched style.

Updated Examples verified on the live UEFN compiler

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 and Sleep here forever.
  • OnEnd() — runs when the NPC is removed; clean up here.
  • GetAgent() — the agent for this NPC, so you can find its fort_character and 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 can loop and Sleep inside it indefinitely; that loop is the beating heart of the state machine.
  • GetAgent[] — from the base class, returns the NPC's agent. It <decides>, so we unwrap it with if (... := GetAgent[]).
  • GuardAgent.GetFortCharacter[] — turns the agent into a fort_character we can query and drive.
  • Inside loop, GuardChar.IsActive[] guards against acting on a dead/removed NPC. GetTransform().Translation gives its world position.
  • FindNearbyPlayer returns a ?agent; if it succeeds we transition to Alert, otherwise we stay in Patrol.
  • EnterAlert grabs the focus_interface via GetFocusInterface[] and calls MaintainFocus(Target). That call never completes on its own, so we race it against a Sleep(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_behavior is NOT a creative_device. You cannot put @editable device references on it and you don't place it in the level. You assign the class to an NPC in a CharacterDefinition asset or the npc_spawner_device. Put device-owning logic in a separate creative_device.
  • OnBegin suspends, OnEnd does not. OnBegin<override>()<suspends>:void can loop and Sleep; OnEnd<override>():void must be synchronous — no Sleep/race inside it.
  • MaintainFocus never returns. Both overloads suspend forever until interrupted. If you don't race it against a Sleep (or another async op), your state machine loop will hang on that line and never re-evaluate.
  • Unwrap the <decides> getters. GetAgent[], GetFortCharacter[], GetFocusInterface[], and IsActive[] all fail-able — use square brackets inside an if (... := ...) and never assume success.
  • Check IsActive[] before acting. An NPC (or player) that's been eliminated or removed will make most actions silently fail; test IsActive[] so your loop doesn't spin on a corpse.
  • ?agent payloads must be unwrapped. A trigger_device.TriggeredEvent hands you (Agent:?agent); get the real agent with if (Winner := Agent?): before passing it to ScoreManager.Activate.
  • No int↔float auto-convert. AlertRange and distances are floats; don't mix them with int literals without an explicit conversion.

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 →