Reference Verse

Print & log_channel: Debug Signals from Your Sunny Cove

Every island has mysteries — why didn't that chest open? Why did the wave counter skip from 2 to 5? Verse's `Print()` function and `log_channel` class are your flashlight: they beam messages into the Output Log so you can watch your game's internal state in real time. This article teaches you both tools through a bright, cel-shaded cove scenario where a player swims to a dock, triggers a trap, and you watch every step in the log.

Updated
The code on this reference page is provided as-is and did not pass the latest compile check — treat the examples as a starting point and verify in your project.
Watch the Knotprint_for_debugging in ~90 seconds.

Overview

When something goes wrong on your island — a trigger fires twice, a score never increments, a device seems to do nothing — you need a way to see what Verse is actually doing at runtime. Two complementary tools handle this:

  • Print(s : string) — the quickest tool in the shed. Writes a string to the Output Log (and briefly to the HUD in Play-In-Editor). Zero setup required.
  • log / log_channel — the production-grade sibling. You declare a named channel class, create a log instance bound to it, and call .Print() on that instance. Channel-tagged messages are easier to filter in a busy log, and you can control verbosity per channel.

Neither tool affects gameplay — they are pure observation instruments. Both are invisible to players in published experiences.

When to reach for each:

Situation Tool
Quick one-off sanity check Print()
Multi-system island with lots of log noise log + named log_channel
You want to filter by system in the Output Log log + named log_channel
Shipping — neither shows to players Either (both are dev-only)

API Reference

(API surface could not be resolved for this device.)

Walkthrough

The Scenario: A sun-drenched cove. A wooden dock juts over turquoise water. When a player steps onto a pressure plate at the end of the dock, a trap device activates. You want to log every stage: device startup, the moment a player triggers the plate, and whether the trap actually fired.

This example uses:

  • Print() for the earliest startup message (before the log channel is warm)
  • A custom log_channel + log instance for all subsequent tagged messages
  • A trigger_device (pressure plate) and a trap_device wired together
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Simulation }

# ── Named log channel for the dock trap system ──────────────────────────────
dock_trap_log_channel := class(log_channel){}

# ── Main device ─────────────────────────────────────────────────────────────
dock_trap_device := class(creative_device):

    # Wire these up in the UEFN Details panel
    @editable
    DockPlate : trigger_device = trigger_device{}

    @editable
    CoveTrap : trick_tile_device = trick_tile_device{}

    # One log instance bound to our named channel
    Logger : log = log{Channel := dock_trap_log_channel}

    OnBegin<override>()<suspends> : void =
        # Print() for the very first heartbeat — visible immediately
        Print("[DockTrap] Device is awake — sunny cove ready!")

        # Subscribe to the pressure plate
        DockPlate.TriggeredEvent.Subscribe(OnDockPlateTriggered)

        # Tagged log so we can filter in a busy Output Log
        Logger.Print("[DockTrap] Subscribed to DockPlate.TriggeredEvent")

    # Called when any agent steps on the dock plate
    OnDockPlateTriggered(TriggeringAgent : ?agent) : void =
        Logger.Print("[DockTrap] Plate triggered!")

        if (A := TriggeringAgent):
            # Agent confirmed — arm the trap
            Logger.Print("[DockTrap] Valid agent on dock — activating cove trap!")
            CoveTrap.Enable()
        else:
            Logger.Print("[DockTrap] Plate fired but no agent found — skipping trap.")```

**Line-by-line breakdown:**

1. `dock_trap_log_channel := class(log_channel){}`  declares a zero-field subclass of `log_channel`. The class *name* becomes your filter keyword in the Output Log.
2. `Logger : log = log{Channel := dock_trap_log_channel{}}`  creates a `log` instance at field scope, bound to our channel. Every `Logger.Print(...)` call is tagged `[dock_trap_log_channel]` in the log.
3. `Print("[DockTrap] Device is awake…")`  bare built-in `Print`, no channel, fires the moment `OnBegin` runs. Good for "am I even running?" checks.
4. `DockPlate.TriggeredEvent.Subscribe(OnDockPlateTriggered)`  hooks the plate. The event delivers `?agent` (an *option* agent).
5. `if (A := TriggeringAgent?):`  the mandatory option-unwrap. Without this, the compiler won't let you pass `TriggeringAgent` to `CoveTrap.Activate()`.
6. `CoveTrap.Activate(A)`  only fires when a real agent is confirmed; the else-branch logs the edge case so you notice it during playtesting.

## Common patterns

### Pattern 1 — Startup heartbeat with `Print()`

The simplest possible debug signal: prove your device loaded and `OnBegin` ran.

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

cove_heartbeat_device := class(creative_device):

    OnBegin<override>()<suspends> : void =
        # Bare Print — no channel, no setup, instant feedback
        Print("[CoveHeartbeat] OnBegin fired — island is live!")
        # Simulate some async work (e.g. waiting for a wave to start)
        Sleep(3.0)
        Print("[CoveHeartbeat] 3 seconds elapsed — wave incoming!")

Use this pattern when you just need to confirm execution order. The message appears in the Output Log and briefly on the HUD during PIE.


Pattern 2 — Named log_channel for a multi-system island

When your island has a treasure system, a wave system, and an NPC patrol all printing to the same log, named channels let you filter. Search lagoon_treasure_log_channel in the Output Log to see only treasure messages.

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

# Declare a channel — one per logical system
lagoon_treasure_log_channel := class(log_channel){}

lagoon_treasure_device := class(creative_device):

    @editable
    TreasureButton : button_device = button_device{}

    @editable
    TreasureGranter : item_granter_device = item_granter_device{}

    TreasureLog : log = log{Channel := lagoon_treasure_log_channel{}}

    OnBegin<override>()<suspends> : void =
        TreasureLog.Print("[Treasure] System initialised — lagoon chest armed.")
        TreasureButton.InteractedWithEvent.Subscribe(OnTreasureButtonPressed)

    OnTreasureButtonPressed(Agent : agent) : void =
        TreasureLog.Print("[Treasure] Button pressed — granting loot!")
        TreasureGranter.GrantItem(Agent)
        TreasureLog.Print("[Treasure] GrantItem called for agent.")

Notice button_device.InteractedWithEvent delivers a non-optional agent directly — no unwrap needed here.


Pattern 3 — Logging a numeric game state with string interpolation

String interpolation "{Value}" lets you embed variables directly into Print or Logger.Print messages — essential for tracking counters, scores, and timers.

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

pirate_wave_log_channel := class(log_channel){}

pirate_wave_counter_device := class(creative_device):

    @editable
    WaveTrigger : trigger_device = trigger_device{}

    WaveLog : log = log{Channel := pirate_wave_log_channel{}}

    var WaveCount : int = 0

    OnBegin<override>()<suspends> : void =
        WaveLog.Print("[Waves] Pirate ship wave counter started.")
        WaveTrigger.TriggeredEvent.Subscribe(OnWaveTriggered)

    OnWaveTriggered(TriggeringAgent : ?agent) : void =
        set WaveCount += 1
        # Interpolate the int directly into the log string
        WaveLog.Print("[Waves] Wave {WaveCount} has begun — brace the clifftop!")
        if (WaveCount >= 3):
            WaveLog.Print("[Waves] WARNING: 3+ waves reached — boss spawn threshold hit!")

"{WaveCount}" converts the int to its string representation inline. No cast function needed.

Gotchas

1. Print() is a dev tool, not a player UI

Print() writes to the Output Log and shows a brief on-screen message only in Play-In-Editor. It does NOT display to players in a published island. If you want in-game text, use a hud_message_device or notification_device instead.

2. You MUST subclass log_channel — you cannot instantiate the base

log{Channel := log_channel{}} will not compile because log_channel is abstract. Always declare your own my_channel := class(log_channel){} first.

3. TriggeredEvent delivers ?agent, not agent

The trigger_device.TriggeredEvent is a listenable(?agent). Your handler signature must be (TriggeringAgent : ?agent) and you must unwrap with if (A := TriggeringAgent?): before using the agent. Skipping this is the #1 compile error beginners hit.

4. String interpolation is your friend — but watch types

"{MyFloat}" and "{MyInt}" both work in interpolation. However, Verse does not auto-convert int to float or vice versa in arithmetic — only in string interpolation contexts. Keep your math types consistent.

5. log.Print vs built-in Print — same output, different filterability

Both write to the Output Log. The difference is that log.Print tags the line with your channel name, making it grep-able. On a large island with dozens of devices all calling bare Print(), the log becomes unreadable fast. Adopt named channels early.

6. Verse Debug Draw requires an Island Settings toggle

If you later graduate to visual debugging (debug_draw_channel), remember to enable Verse Debug Draw in Island Settings → Debug tab. Debug draw shapes are also invisible in published experiences.

Guides & scripts that use print_for_debugging

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

Build your own lesson with print_for_debugging

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 →