Reference Devices compiles

conversation_device: Branching NPC Dialogue That Drives Your Game

The conversation_device turns a Conversation Graph into living, choice-driven gameplay: a player talks to an NPC, picks a response, and your Verse code reacts in real time. This article shows you how to start, watch, react to, show/hide, and end conversations from Verse using the device's real API.

Updated Examples verified on the live UEFN compiler
Watch the Knotconversation_device in ~90 seconds.

Overview

The conversation_device plays a conversation tree you author in the Conversation Graph and assign to the device in UEFN. On its own, it can pop a dialogue bubble over an NPC and let the player click choices. The power comes from Verse: you can start a conversation on cue (after a cutscene, when a quest unlocks), listen for which choice the player picked via OnConversationEvent, and end or hide the conversation when gameplay demands it.

Reach for this device whenever you want narrative or menu-style choices to change the game — give a quest, open a door, grant a weapon, branch an objective. Each Conversation Event node you place in the graph carries an integer EventNumber; when a player selects the choice tied to that node, your Verse handler receives the agent who chose and that number, and you run the matching logic.

Key mental model:

  • InitiateConversation(Agent) — start the assigned conversation for one player.
  • OnConversationEvent — fires tuple(agent, int) when a choice node is selected. This is your branch dispatcher.
  • EndEvent / CancelEvent — tell you the conversation finished normally vs. was force-ended.
  • ShowConversation / HideConversation — toggle the UI for an agent.
  • IsAgentInConversation, CanInitiateConversation, GetActiveConversationsCount — guard rails so you don't double-start or talk to a busy player.

API Reference

conversation_device

Used to set and trigger conversations made via the Conversation Graph. The conversation triggered is assigned to the device.

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

conversation_device<public> := class<concrete><final>(creative_device_base, enableable):

Events (subscribe a handler to react):

Event Signature Description
OnConversationEvent OnConversationEvent<public>:listenable(tuple(agent, int)) Signaled when a choice node tied to this event is selected by an agent. Sends the agent that triggered the event, and EventNumber is the number specified in the Conversation Event node.
EndEvent EndEvent<public>:listenable(agent) Signalled when a conversation has ended.
CancelEvent CancelEvent<public>:listenable(agent) Signalled when a conversation has ended early by EndConversation or EndConversationForAll.

Methods (call these to make the device act):

Method Signature Description
InitiateConversation InitiateConversation<public>(Agent:agent):void Begins the assigned conversation with the agent. The conversation will not start if the device or the agent are otherwise incapable of having a conversation.
IsAgentInConversation IsAgentInConversation<public>(Agent:agent)<transacts><decides>:void Check if an agent is in an active conversation.
CanInitiateConversation CanInitiateConversation<public>(Agent:agent)<transacts><decides>:void Check if an agent can initiate a conversation.
EndConversation EndConversation<public>(Agent:agent):void Ends the assigned conversation with the agent.
EndConversationForAll EndConversationForAll<public>():void Ends all active conversations with this device.
ShowConversation ShowConversation<public>(Agent:agent):void Show the conversation UI for an 'agent'. This will work with Box and Custom UI.
HideConversation HideConversation<public>(Agent:agent):void Hide the conversation UI for an 'agent'. Responses cannot be selected while the UI is hidden. This will work with Box and Custom UI.
GetActiveConversationsCount GetActiveConversationsCount<public>()<transacts>:int Returns the count of currently-active conversations with this device.

Walkthrough

Scenario: A village quest-giver NPC. A trigger volume in front of the NPC starts the conversation when a player walks up. The conversation graph has two choice nodes wired to Conversation Event nodes: EventNumber 1 = "Accept the quest" (we grant a weapon) and EventNumber 2 = "Decline" (we just end the talk). When the conversation finishes either way, we clean up.

using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

quest_giver := class(creative_device):

    # The conversation_device with the quest dialogue graph assigned to it.
    @editable
    QuestTalk : conversation_device = conversation_device{}

    # A trigger placed in front of the NPC — starts the chat when stepped on.
    @editable
    ApproachTrigger : trigger_device = trigger_device{}

    # Hands the player a weapon when they accept the quest.
    @editable
    SwordGranter : item_granter_device = item_granter_device{}

    OnBegin<override>()<suspends> : void =
        # When a player approaches the NPC, try to start the conversation.
        ApproachTrigger.TriggeredEvent.Subscribe(OnApproach)
        # React to whichever choice node the player selects.
        QuestTalk.OnConversationEvent.Subscribe(OnChoiceMade)
        # Clean-up hooks for normal end and forced end.
        QuestTalk.EndEvent.Subscribe(OnTalkEnded)
        QuestTalk.CancelEvent.Subscribe(OnTalkCancelled)

    OnApproach(Agent : ?agent) : void =
        # The trigger hands us an optional agent — unwrap it first.
        if (Player := Agent?):
            # Only start if this player isn't already mid-conversation and can talk.
            if:
                not QuestTalk.IsAgentInConversation[Player]
                QuestTalk.CanInitiateConversation[Player]
            then:
                QuestTalk.InitiateConversation(Player)

    # OnConversationEvent hands us a tuple of (who chose, which EventNumber).
    OnChoiceMade(Result : tuple(agent, int)) : void =
        Player := Result(0)
        EventNumber := Result(1)
        if (EventNumber = 1):
            # "Accept the quest" branch — grant the sword.
            SwordGranter.GrantItem(Player)
        else if (EventNumber = 2):
            # "Decline" branch — end this player's conversation cleanly.
            QuestTalk.EndConversation(Player)

    OnTalkEnded(Agent : agent) : void =
        # Conversation finished on its own. Log how many are still active.
        Active := QuestTalk.GetActiveConversationsCount()
        Print("Conversation ended. Still active: {Active}")

    OnTalkCancelled(Agent : agent) : void =
        Print("Conversation was force-ended for a player.")

Line by line:

  • The three @editable fields let you drag your placed devices onto this script in UEFN. QuestTalk is the conversation device; the others drive the scenario.
  • In OnBegin we subscribe handlers for the trigger and for three conversation signals. Subscriptions must happen here, in the async OnBegin.
  • OnApproach receives ?agent from the trigger; if (Player := Agent?) unwraps the option. We then guard with IsAgentInConversation (skip if already talking) and CanInitiateConversation — both are <decides>, so they go inside an if as [Player] failable calls.
  • InitiateConversation(Player) starts the assigned graph for that player.
  • OnChoiceMade is the heart of branching. We pull Result(0) (the agent) and Result(1) (the EventNumber set on the Conversation Event node) and dispatch. EventNumber = 1 grants the sword; EventNumber = 2 ends the talk.
  • EndEvent and CancelEvent give us tidy hooks; GetActiveConversationsCount() returns a plain int we can log.

Common patterns

Pattern 1 — Show / Hide the UI during a scripted beat. Hide the dialogue while a short cinematic plays, then bring it back so the player can choose.

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

cutscene_dialogue := class(creative_device):

    @editable
    Talk : conversation_device = conversation_device{}

    @editable
    StartButton : button_device = button_device{}

    OnBegin<override>()<suspends> : void =
        StartButton.InteractedWithEvent.Subscribe(OnPressed)

    OnPressed(Agent : agent) : void =
        # Begin the conversation, hide the UI for a beat, then reveal it.
        Talk.InitiateConversation(Agent)
        spawn { RevealAfterBeat(Agent) }

    RevealAfterBeat(Agent : agent)<suspends> : void =
        Talk.HideConversation(Agent)   # no responses selectable while hidden
        Sleep(3.0)                     # play your cinematic here
        Talk.ShowConversation(Agent)   # bring the choices back

Pattern 2 — A global "close all dialogue" panic button. When a round ends, force-end every open conversation. The CancelEvent from the previous example would fire for each affected player.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

round_end_cleanup := class(creative_device):

    @editable
    Talk : conversation_device = conversation_device{}

    @editable
    EndRoundButton : button_device = button_device{}

    OnBegin<override>()<suspends> : void =
        EndRoundButton.InteractedWithEvent.Subscribe(OnEndRound)

    OnEndRound(Agent : agent) : void =
        # How many conversations are open right now?
        Open := Talk.GetActiveConversationsCount()
        Print("Closing {Open} active conversations.")
        # Force every one of them shut.
        Talk.EndConversationForAll()

Pattern 3 — Guarded re-initiate. Only start a conversation if the player is allowed to and isn't already in one — useful when several triggers could fire the same NPC.

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

guarded_npc := class(creative_device):

    @editable
    Talk : conversation_device = conversation_device{}

    @editable
    Proximity : trigger_device = trigger_device{}

    OnBegin<override>()<suspends> : void =
        Proximity.TriggeredEvent.Subscribe(OnNear)

    OnNear(Agent : ?agent) : void =
        if (Player := Agent?):
            if (Talk.CanInitiateConversation[Player]):
                # CanInitiateConversation already fails if they're busy,
                # so a single guard is enough here.
                Talk.InitiateConversation(Player)

Gotchas

  • The graph still lives in UEFN. Verse starts/stops/reads the conversation, but the actual dialogue text, choice nodes, and Conversation Event nodes are authored in the Conversation Graph and assigned to the device. The EventNumber you switch on in OnConversationEvent is the integer you typed into each Conversation Event node — keep your Verse if (EventNumber = …) values in sync with the graph.
  • OnConversationEvent is listenable(tuple(agent, int)), not listenable(?agent). Its handler takes one tuple(agent, int) parameter — index it with Result(0) and Result(1). There is no option to unwrap here, unlike trigger events.
  • Trigger events DO hand you ?agent. TriggeredEvent gives ?agent, so always if (Player := Agent?): before using the player. Forgetting this is the #1 "Unknown identifier / type mismatch" error.
  • IsAgentInConversation, CanInitiateConversation, and IsReferenced are <decides>. Call them inside an if (...) or if: block as failable expressions with square brackets (Talk.IsAgentInConversation[Player]). Calling them like a normal void function won't compile.
  • HideConversation blocks input. While hidden, the player cannot select responses. Always pair a HideConversation with a later ShowConversation (or an end) so players aren't stuck staring at a frozen NPC.
  • You must declare the device as an @editable field. A bare conversation_device.InitiateConversation(...) fails with 'Unknown identifier'. Drag the placed device onto the field in the Details panel.
  • GetActiveConversationsCount() returns int, not a count per player. It's the total across the device. Use IsAgentInConversation[Player] when you need to know about one specific player.

Device Settings & Options

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

Conversation Device settings and options panel in the UEFN editor — Conversation Type, Speaker Name, Show Name When Nearby, Show Indicator Bubble, Indicator Bubble Range
Conversation Device — User Options in the UEFN editor: Conversation Type, Speaker Name, Show Name When Nearby, Show Indicator Bubble, Indicator Bubble Range
⚙️ Settings on this device (5)

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

Conversation Type
Speaker Name
Show Name When Nearby
Show Indicator Bubble
Indicator Bubble Range

Guides & scripts that use conversation_device

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

Build your own lesson with conversation_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 →