Reference Devices

perception_trigger_device: Line-of-Sight Triggers for Jump Scares & Gaze Puzzles

Some of the best moments in Fortnite happen when the game reacts to where a player is looking — a statue's eyes glow the instant you stare at it, a monster lunges the moment it spots you. The perception_trigger_device makes that possible by firing Verse events based on line of sight between agents and the device. This article shows you its real API and wires it into a working haunted-gallery scenario.

Updated
The code on this reference page is provided as-is and did not pass the latest compile check — treat the examples as a starting point and verify in your project.
Watch the Knotperception_trigger_device in ~90 seconds.

Overview

The perception_trigger_device is a specialized trigger_base_device that fires output events based on line of sight between agents and the device. Unlike a normal trigger that needs a player to step on a volume, this device cares about seeing:

  • AgentLooksAtDeviceEvent / AgentLooksAwayFromDeviceEvent — a player's camera/eyeline has (or lost) a clear view of the device.
  • DeviceSeesAgentEvent / DeviceLosesSightOfAgentEvent — the device itself has (or lost) a clear view of an agent.

Reach for it whenever the game problem is "react to what the player is looking at" or "react to who the device can see": a portrait whose eyes follow you, a security camera that raises an alarm when it spots an intruder, a gaze-activated puzzle where staring at three idols in order unlocks a vault, or a classic jump scare that only fires when you look directly at the prop.

Because it derives from trigger_base_device, it also has the familiar trigger plumbing: Enable/Disable, SetMaxTriggerCount, SetResetDelay, SetTransmitDelay, and query methods like GetPerceivedAgents, IsLookingAtDevice, and IsPerceived. We'll often pair it with a plain trigger_device to relay the moment onward to other devices (audio, VFX, doors).

API Reference

perception_trigger_device

Specialized trigger_base_device that will fire output events based on line of sight between agents and the device.

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

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

Events (subscribe a handler to react):

Event Signature Description
AgentLooksAtDeviceEvent AgentLooksAtDeviceEvent<public>:listenable(agent) Signaled when an agent has direct line of sight to this device. Sends the agent that has seen this device.
AgentLooksAwayFromDeviceEvent AgentLooksAwayFromDeviceEvent<public>:listenable(agent) Signaled when an agent has lost direct line of sight to this device. Sends the agent that has lost sight of this device.
DeviceSeesAgentEvent DeviceSeesAgentEvent<public>:listenable(agent) Signaled when this device has direct line of sight to an agent. Sends the agent seen by this device.
DeviceLosesSightOfAgentEvent DeviceLosesSightOfAgentEvent<public>:listenable(agent) Signaled when this device loses direct line of sight to an agent. Sends the agent this device has lost sight of.

Methods (call these to make the device act):

Method Signature Description
GetPerceivedAgents GetPerceivedAgents<public>()<reads>:[]agent Returns an array of agents that are currently of the class defined by this device.
IsPerceived IsPerceived<public>(Agent:agent)<transacts><decides>:void Succeeds when Agent is registered to this checkpoint.
GetLookingAtDeviceAgents GetLookingAtDeviceAgents<public>()<reads>:[]agent Returns an array of agents that are currently of the class defined by this device.
IsLookingAtDevice IsLookingAtDevice<public>(Agent:agent)<transacts><decides>:void Succeeds when Agent is registered to this checkpoint.
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.

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.

Walkthrough

The scenario: a haunted gallery. A cursed portrait (the perception trigger) watches the hall. When a player makes the mistake of looking directly at it, we play a jump scare by relaying through a linked trigger_device (wired in UEFN to an audio + prop-mover) and show the player a creepy message. When they look away, we go quiet again. We only allow the scare to fire 3 times, with a short reset delay so it can't spam.

perception_gallery := class(creative_device):

    # The cursed portrait — drag your placed perception_trigger_device here.
    @editable
    Portrait : perception_trigger_device = perception_trigger_device{}

    # A plain trigger we relay the scare through (wire it to Audio/Prop devices in UEFN).
    @editable
    ScareRelay : trigger_device = trigger_device{}

    # Localized message helper — message params need a localized value, not a raw string.
    ScareText<localizes>(S:string):message = "{S}"

    OnBegin<override>()<suspends>:void =
        # Limit the scare to 3 times, with a 2 second cooldown between scares.
        Portrait.SetMaxTriggerCount(3)
        Portrait.SetResetDelay(2.0)
        Portrait.Enable()

        # React when a player's eyeline lands on the portrait.
        Portrait.AgentLooksAtDeviceEvent.Subscribe(OnLookedAt)
        # React when they look away.
        Portrait.AgentLooksAwayFromDeviceEvent.Subscribe(OnLookedAway)

    OnLookedAt(Agent:agent):void =
        # Fire the scare: relay through the trigger device, passing the agent along.
        ScareRelay.Trigger(Agent)

        # Show the looking player a message.
        if (Player := player[Agent], UI := GetPlayerUI[Player]):
            UI.ShowMessage(ScareText("You shouldn't have looked..."))

        # How many scares left?
        Remaining := Portrait.GetTriggerCountRemaining()
        Print("Scares remaining: {Remaining}")

    OnLookedAway(Agent:agent):void =
        # The player broke eye contact — the gallery goes quiet again.
        Print("An agent looked away from the cursed portrait.")

Line by line:

  • Portrait : perception_trigger_device = perception_trigger_device{} — the @editable field is mandatory. Calling a method on a bare local would fail with 'Unknown identifier'; the editable lets you assign the placed device in the Details panel.
  • ScareRelay : trigger_device — a second editable so we can relay the perception event to other (non-Verse) devices via UEFN wiring.
  • ScareText<localizes>(S:string):messageShowMessage takes a message (localized), not a string. This helper produces one. There is no StringToMessage.
  • Portrait.SetMaxTriggerCount(3) / SetResetDelay(2.0) — configure trigger limits at startup. SetResetDelay takes a float (2.0, not 2).
  • Portrait.Enable() — make sure the device is active.
  • AgentLooksAtDeviceEvent.Subscribe(OnLookedAt) — subscribe handler methods in OnBegin. The handler signature is (Agent:agent):void because this event is a listenable(agent) (note: not optional — the device always knows which agent looked).
  • ScareRelay.Trigger(Agent) — the agent-taking overload of Trigger, so downstream devices get the right player reference.
  • if (Player := player[Agent], UI := GetPlayerUI[Player]): — convert the agent to a player, then grab their UI, both as failable lookups in one if.
  • GetTriggerCountRemaining() — reads how many fires are left before hitting the max.

Common patterns

1. The watchful sentry — DeviceSeesAgentEvent + GetPerceivedAgents. Here the device does the seeing. When the camera spots any intruder, it relays an alarm and logs how many agents are currently in view.

security_camera := class(creative_device):

    @editable
    Camera : perception_trigger_device = perception_trigger_device{}

    @editable
    AlarmRelay : trigger_device = trigger_device{}

    OnBegin<override>()<suspends>:void =
        Camera.Enable()
        Camera.DeviceSeesAgentEvent.Subscribe(OnSpotted)
        Camera.DeviceLosesSightOfAgentEvent.Subscribe(OnLost)

    OnSpotted(Agent:agent):void =
        # Sound the alarm by relaying through the trigger.
        AlarmRelay.Trigger(Agent)
        # How many agents are currently in the camera's view?
        InView := Camera.GetPerceivedAgents()
        Print("Camera sees {InView.Length} intruder(s).")

    OnLost(Agent:agent):void =
        Print("Camera lost sight of an intruder.")

2. Gaze-gated query — IsLookingAtDevice + GetLookingAtDeviceAgents. A statue puzzle: every time a relay fires, we only react if a specific agent is currently looking at the idol.

gaze_idol := class(creative_device):

    @editable
    Idol : perception_trigger_device = perception_trigger_device{}

    OnBegin<override>()<suspends>:void =
        Idol.Enable()
        Idol.AgentLooksAtDeviceEvent.Subscribe(OnGaze)

    OnGaze(Agent:agent):void =
        # Confirm this agent is indeed registered as looking right now.
        if (Idol.IsLookingAtDevice[Agent]):
            Watchers := Idol.GetLookingAtDeviceAgents()
            Print("{Watchers.Length} player(s) are staring at the idol.")

3. Reading the trigger from a relay — TriggeredEvent + delay tuning. The downstream trigger_device can also be driven directly in Verse. Here we tune its transmit delay and react to its own TriggeredEvent.

scare_audio_relay := class(creative_device):

    @editable
    Relay : trigger_device = trigger_device{}

    OnBegin<override>()<suspends>:void =
        # Wait half a second before informing linked devices.
        Relay.SetTransmitDelay(0.5)
        Relay.Enable()
        Relay.TriggeredEvent.Subscribe(OnFired)
        Print("Transmit delay is {Relay.GetTransmitDelay()}")

    OnFired(MaybeAgent:?agent):void =
        # TriggeredEvent is listenable(?agent) — unwrap before using.
        if (Agent := MaybeAgent?):
            Print("Relay fired by a real agent.")
        else:
            Print("Relay fired through code (no agent).")

Gotchas

  • AgentLooksAtDeviceEvent is listenable(agent) — NOT optional. Its handler takes (Agent:agent):void and the agent is always present. But the plain trigger_device's TriggeredEvent is listenable(?agent), so that handler takes (MaybeAgent:?agent) and you must unwrap with if (A := MaybeAgent?):. Mixing these up causes type errors.
  • You must declare the device as an @editable field. A bare perception_trigger_device{} local won't reference your placed device — assign the real one in the Details panel.
  • IsLookingAtDevice and IsPerceived are <decides> (failable). Call them inside an if (...) with square brackets: if (Idol.IsLookingAtDevice[Agent]):. They don't return a logic.
  • Delays are floats. SetResetDelay(2.0) and SetTransmitDelay(0.5) — Verse never auto-converts int to float, so 2 will fail to compile.
  • message is localized. ShowMessage needs a message, not a string. Use a <localizes> helper; there is no StringToMessage.
  • SetMaxTriggerCount is clamped to [0,20], and 0 means unlimited. GetTriggerCountRemaining returns 0 when the max is unlimited — don't misread that as 'no fires left'.
  • Line of sight depends on device placement and settings. If events never fire, check that the device is Enable()d, is actually visible to players, and isn't occluded by geometry.

Device Settings & Options

The perception_trigger_device User Options panel in the UEFN editor — every setting you can tune.

Perception Trigger Device settings and options panel in the UEFN editor — Trigger Sound, Visible in Game, Operat Summ
Perception Trigger Device — User Options (1 of 2) in the UEFN editor: Trigger Sound, Visible in Game, Operat Summ
Perception Trigger Device settings and options panel in the UEFN editor — Trigger Sound, Visible in Game, Operat Summ
Perception Trigger Device — User Options (2 of 2) in the UEFN editor: Trigger Sound, Visible in Game, Operat Summ
⚙️ Settings on this device (3)

Setting names read from the panel above — may be partial; the screenshots are the source of truth.

Trigger Sound
Visible in Game
Operat Summ

Build your own lesson with perception_trigger_device

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 →