Quest Framework: Join, Track, and Reward Players
Tutorial intermediate compiles

Quest Framework: Join, Track, and Reward Players

Updated intermediate Code verified

What you'll learn

You'll learn how to use the real /Verse.org/Progression Quest Framework to:

  • Define a concrete quest subclass (because quest is abstract).
  • Create a quest_collection and join players to a quest via JoinQuest, inspecting the result it returns.
  • Track progress from a trigger_device and call Complete() when the goal is reached.
  • Display progress to each player on their HUD using the Player UI API (GetPlayerUI[] + a canvas with a text_block widget).

How it works

The framework has three moving parts:

  1. quest_collection — the source of truth for participation. It is class<unique> and is instantiable, so you create one and keep it on your device.
  2. quest — a completable goal. It is class<abstract>, so you must subclass it (collect_items_quest := class(quest)) and instantiate the subclass. Call Complete() to finish it and fire CompleteEvent.
  3. agent_quest_participant — wraps an agent (a player is an agent) so it can join a quest. quest_participant_info carries three logic flags: Contributes, Observes, Receives.

Key correctness note: JoinQuest returns result(void, []join_quest_error). It is not a <decides> function — call it with parens and then inspect the result with GetSuccess[] inside an if. Calling JoinQuest[...] is the classic mistake that fails to compile.

For the HUD, GetPlayerUI[Player] is fallible (bind it in an if), then you build a canvas holding a text_block and call AddWidget. We track a per-player text widget so we can update its text as progress changes.

Let's build it

This device defines a 5-item collect quest, joins every player, draws a progress label on each player's HUD, advances progress on each ProgressTrigger pulse, and completes the quest when the target is hit.

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

# `quest` is abstract — we MUST subclass it to get a concrete, instantiable quest.
collect_items_quest := class(quest):
    Target : int = 5

# Main device: drives join / track / reward + live HUD.
quest_reward_device := class<concrete>(creative_device):

    # Each trigger pulse = one unit of progress.
    @editable
    ProgressTrigger : trigger_device = trigger_device{}

    # The source of truth for participation (unique, but instantiable).
    Collection : quest_collection = quest_collection{}

    # The shared quest every player works on.
    Quest : collect_items_quest = collect_items_quest{ Target := 5 }

    # Per-quest progress counter (a real game would key this per participant).
    var Progress : int = 0

    # Remember each player's HUD label so we can update it as progress changes.
    var Labels : [player]text_block = map{}

    OnBegin<override>()<suspends> : void =
        # Observe completion so we can hand out rewards.
        Quest.CompleteEvent.Subscribe(OnQuestCompleted)

        # Enroll every current player and show their HUD.
        for (Player : GetPlayspace().GetPlayers()):
            JoinPlayerToQuest(Player)

        # Track progress: each trigger pulse advances the quest by one.
        loop:
            ProgressTrigger.TriggeredEvent.Await()
            AdvanceProgress()

    # Wrap the player as a participant and JOIN it to the quest.
    JoinPlayerToQuest(Player : player) : void =
        Participant := agent_quest_participant{ Agent := Player }
        Info := quest_participant_info:
            Contributes := true
            Observes := true
            Receives := true
        # JoinQuest returns result(...) — call with PARENS, then inspect GetSuccess[].
        JoinResult := Collection.JoinQuest(Quest, Participant, Info)
        if (JoinResult.GetSuccess[]):
            ShowProgressHUD(Player)

    # Build a HUD label on this player's screen using the Player UI API.
    ShowProgressHUD(Player : player) : void =
        if (PlayerUI := GetPlayerUI[Player]):
            Label := text_block{ DefaultText := MakeProgressText() }
            Canvas := canvas:
                Slots := array:
                    canvas_slot:
                        Anchors := anchors{ Minimum := vector2{ X := 0.05, Y := 0.1 }, Maximum := vector2{ X := 0.05, Y := 0.1 } }
                        Widget := Label
            PlayerUI.AddWidget(Canvas)
            # Remember the label so AdvanceProgress can refresh it.
            if (set Labels[Player] = Label) {}

    # Add one unit of progress, refresh HUDs, and complete on target.
    AdvanceProgress() : void =
        set Progress = Progress + 1
        for (Player -> Label : Labels):
            Label.SetText(MakeProgressText())
        if (Progress >= Quest.Target):
            Quest.Complete()

    # Compose the on-screen progress message via interpolation.
    MakeProgressText() : message = StringToMessage("Collected {Progress} / {Quest.Target}")

    # Helper: a string interpolation produces the message we display.
    StringToMessage<localizes>(Value : string) : message = "{Value}"

    # Fired when the quest completes.
    OnQuestCompleted(CompletedQuest : quest) : void =
        for (Player -> Label : Labels):
            Label.SetText(StringToMessage("Quest complete! Rewards granted."))```

## Try it yourself

- Drag a **Trigger** device into your level and assign it to `ProgressTrigger`, then step on it five times to watch the HUD count up and the completion message appear.
- Change `Quest := collect_items_quest{ Target := 5 }` to a different target and re-test the count.
- Subscribe to `Collection.JoinEvent` in `OnBegin` to log every join, and call `GetPlayspace().PlayerAddedEvent().Subscribe(...)` to auto-enroll latecomers.
- In `OnQuestCompleted`, grant a real reward (e.g. trigger an Item Granter device) instead of just updating text.

## Recap

- `quest` is abstract  subclass it to get a concrete quest you can instantiate.
- `quest_collection{}` is instantiable and is the source of truth for participation.
- `JoinQuest` returns a `result`  call it with parens and check `GetSuccess[]`; it is **not** `<decides>`.
- A `player` is an `agent`, so it slots straight into `agent_quest_participant`.
- Use `GetPlayerUI[Player]` (fallible) + a `canvas` with a `text_block` to put live progress on the HUD, and `Quest.Complete()` to finish the goal.

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 Quest Logic with the Quest Framework API 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