Quest Framework in Verse: Join and Reward with Live UI
Tutorialintermediatecompiles

Quest Framework in Verse: Join and Reward with Live UI

Updated intermediateCode 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