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_devicethat will fire output events based on line of sight betweenagents 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@editablefield 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):message—ShowMessagetakes amessage(localized), not astring. This helper produces one. There is noStringToMessage.Portrait.SetMaxTriggerCount(3)/SetResetDelay(2.0)— configure trigger limits at startup.SetResetDelaytakes afloat(2.0, not2).Portrait.Enable()— make sure the device is active.AgentLooksAtDeviceEvent.Subscribe(OnLookedAt)— subscribe handler methods inOnBegin. The handler signature is(Agent:agent):voidbecause this event is alistenable(agent)(note: not optional — the device always knows which agent looked).ScareRelay.Trigger(Agent)— theagent-taking overload ofTrigger, so downstream devices get the right player reference.if (Player := player[Agent], UI := GetPlayerUI[Player]):— convert theagentto aplayer, then grab their UI, both as failable lookups in oneif.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
AgentLooksAtDeviceEventislistenable(agent)— NOT optional. Its handler takes(Agent:agent):voidand the agent is always present. But the plaintrigger_device'sTriggeredEventislistenable(?agent), so that handler takes(MaybeAgent:?agent)and you must unwrap withif (A := MaybeAgent?):. Mixing these up causes type errors.- You must declare the device as an
@editablefield. A bareperception_trigger_device{}local won't reference your placed device — assign the real one in the Details panel. IsLookingAtDeviceandIsPerceivedare<decides>(failable). Call them inside anif (...)with square brackets:if (Idol.IsLookingAtDevice[Agent]):. They don't return alogic.- Delays are floats.
SetResetDelay(2.0)andSetTransmitDelay(0.5)— Verse never auto-convertsinttofloat, so2will fail to compile. messageis localized.ShowMessageneeds amessage, not astring. Use a<localizes>helper; there is noStringToMessage.SetMaxTriggerCountis clamped to [0,20], and0means unlimited.GetTriggerCountRemainingreturns0when 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.