Quest Framework in Verse: Join and Reward with Live UI
Tutorial intermediate compiles

Quest Framework in Verse: Join and Reward with Live UI

Updated intermediate Code verified

What you'll learn

You'll learn how the experimental Quest Framework classes fit together and how to surface their results to players:

  • A quest_collection (from /Verse.org/Progression) is the source of truth for participation. It is class<unique> and IS instantiable — so quest_collection{} is valid.
  • quest is class<abstract> — you CANNOT write quest{}. You must declare a concrete subclass (here collect_coins_quest := class(quest):) and instantiate that.
  • An agent_quest_participant{ Agent := SomeAgent } wraps any agent. A player IS an agent, so you pass it straight in — no cast.
  • Collection.JoinQuest(...) is a <transacts> call that returns a result — NOT a <decides> function. Call it with parens and inspect the result.
  • Real player-UI calls (GetPlayerUI[], AddWidget, canvas, text_block) turn invisible quest logic into visible feedback.

How it works

The Quest Framework lives in /Verse.org/Progression — there is no "quest device." You work with the API classes directly:

  1. Hold one quest_collection and one concrete quest subclass on your device.
  2. On a button interaction, wrap the interacting agent as an agent_quest_participant.
  3. Call Collection.JoinQuest(Quest, Participant, Info).

The #1 mistake people make here: JoinQuest returns result(void, []join_quest_error). It is not a <decides> function, so you must NOT call it as JoinQuest[...] inside an if condition. Instead, call it with parens and READ the result:

JoinResult := Collection.JoinQuest(Quest, Participant, Info)
if (JoinResult.GetSuccess[]):   # GetSuccess[] IS the <decides> query on the result
    # join committed

GetSuccess[] is the fallible accessor exposed by result, so it belongs in the failure context (the if), while JoinQuest(...) itself is a plain side-effecting call.

Throughout, we fetch the player's UI with GetPlayerUI[player[Agent]] and AddWidget a canvas holding a text_block so the player gets immediate on-screen feedback — that's the part that turns logic into a real experience.

Let's build it

This device subscribes to a button. On interaction it joins the interacting player to the quest and shows the result on screen; a CompleteEvent subscription also notifies players when the quest finishes.

using { /Fortnite.com/Devices }
using { /Verse.org/Progression }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Simulation }
using { /Verse.org/Colors }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }

# quest is abstract — declare a CONCRETE subclass so we can instantiate it.
collect_coins_quest := class(quest):
    # Illustrative target shown in the join message.
    Target : int = 3

quest_framework_device := class<concrete>(creative_device):

    # Players interact with this to join the quest.
    @editable StartButton : button_device = button_device{}

    # The source of truth for participation. quest_collection IS instantiable.
    Collection : quest_collection = quest_collection{}

    # The shared concrete quest every joining player works on.
    Quest : collect_coins_quest = collect_coins_quest{ Target := 3 }

    OnBegin<override>()<suspends> : void =
        # Observe completion; Subscribe returns a cancelable we can ignore here.
        Collection.CompleteEvent.Subscribe(OnQuestCompleted)
        # Wire the button to our join handler.
        StartButton.InteractedWithEvent.Subscribe(OnStart)

    OnStart(Agent : agent) : void =
        # A player IS an agent, so it drops straight into a participant.
        Participant := agent_quest_participant{ Agent := Agent }
        # This participant can progress, observe, and earn rewards.
        Info := quest_participant_info:
            Contributes := true
            Observes := true
            Receives := true
        # JoinQuest RETURNS result(void, array{}join_quest_error) — call with parens,
        # then inspect with GetSuccess[] (the <decides> query on the result).
        JoinResult := Collection.JoinQuest(Quest, Participant, Info)
        if (JoinResult.GetSuccess[]):
            Notify(Agent, "Quest joined! Collect {Quest.Target} coins.")
        else:
            Notify(Agent, "Could not join quest.")

    # Fired by the collection when any quest in it completes.
    OnQuestCompleted(CompletedQuest : quest) : void =
        for (Player : GetPlayspace().GetPlayers()):
            Notify(Player, "Quest complete — reward granted!")

    # Push a text label onto the player's UI canvas (the real, visible feedback).
    Notify(Agent : agent, Msg : string) : void =
        if:
            P := player[Agent]            # narrow agent -> player (fallible)
            PlayerUI := GetPlayerUI[P]     # GetPlayerUI is <decides> — use []
        then:
            Label := text_block{ DefaultText := StringToMessage(Msg), DefaultTextColor := NamedColors.White }
            Root := canvas:
                Slots := array:
                    canvas_slot:
                        Anchors := anchors{ Minimum := vector2{ X := 0.5, Y := 0.1 }, Maximum := vector2{ X := 0.5, Y := 0.1 } }
                        Alignment := vector2{ X := 0.5, Y := 0.0 }
                        Widget := Label
            PlayerUI.AddWidget(Root)

    # Helper: text_block.DefaultText expects a message, so wrap the string.
    StringToMessage<localizes>(Value : string) : message = "{Value}"```

## Try it yourself

- Add a second `button_device` wired to an `AbandonQuest(Quest, Participant)` call and show its result the same way (it returns `result(void, []abandon_quest_error)`  inspect with `GetSuccess[]`).
- Track real progress: give the device a `trigger_device`, advance a per-participant counter, and signal completion from a custom objective subclass.
- Subscribe to `Collection.JoinEvent` too and print the `quest_membership.Quest` payload so you can confirm joins in the log.
- Style the feedback: change `DefaultTextColor` or add multiple `canvas_slot`s for a layout.

## Recap

- The Quest Framework lives in `/Verse.org/Progression`; instantiate a `quest_collection`, and a **concrete** subclass of the abstract `quest`.
- Wrap agents with `agent_quest_participant{ Agent := ... }`  a player is an agent.
- `JoinQuest(...)` **returns a `result`**  call it with **parens** and branch on `JoinResult.GetSuccess[]`. Calling `JoinQuest[...]` as a `<decides>` function is wrong and won't compile.
- Use `GetPlayerUI[player[Agent]]` (it's `<decides>`, so `[]`) and `AddWidget` a `canvas`/`text_block` to give players real, visible feedback.

Check your understanding

Test yourself with an interactive quiz and track your progress + earn XP — free for members.

Turn this into a guided course

Add Implementing the Quest Framework API in Verse to your free study plan — we'll suggest related pages and stitch the lot into one compile-checked, self-guided lesson with worked examples and quizzes.

Original tutorial generated by Verse Island from the Verse/UEFN knowledge base, with references to the Epic Games sources above. Code is validated against the knowledge base.

Comments

    Sign in to vote, comment, or suggest an edit. Sign in