The "Oh Sh*t" Timer: Making Your Countdown Pulse with Panic
Tutorial beginner

The "Oh Sh*t" Timer: Making Your Countdown Pulse with Panic

Updated beginner

The "Oh Sh*t" Timer: Making Your Countdown Pulse with Panic

You know that feeling when the storm is closing in, you're at 1 HP, and the timer hits 0:10? Your heart rate spikes. Your hands shake. That's not just bad luck; that's good design. Most default timers in Fortnite just tick away silently like a boring accountant. We're going to fix that.

In this tutorial, we're going to build a timer that doesn't just count down—it judges you. We'll create a countdown that starts calm, but as time runs out, it starts pulsing, shaking, and screaming "HURRY UP" using Verse. No complex shaders, no magic. Just pure, unadulterated UI panic.

What You'll Learn

  • State Machines: How to make your timer behave differently depending on how much time is left (the "Panic Mode" logic).
  • Binding Data: Connecting a Verse script to a UI element so the text actually updates.
  • Visual Feedback: Using simple animations to create urgency without needing to be a graphic designer.
  • The Scene Graph: Understanding how your Verse logic talks to your UI widgets.

How It Works

Think of a standard timer like a Battle Bus. It leaves, it flies, it lands. It doesn't care if you're ready. It just does its job. A smart timer is more like a Storm. It starts slow, but it gets tighter, faster, and more dangerous as time runs out.

In programming terms, we call this State.

  • State: A condition or mode your system is in right now. (e.g., "Playing," "Dead," "In Menu").
  • Variable: A container that holds a value that changes. Here, our variable is TimeRemaining.

We are going to build a system where:

  1. Verse acts as the Game Director. It watches the clock.
  2. When TimeRemaining is high (> 30 seconds), the UI is chill.
  3. When TimeRemaining is low (< 10 seconds), the UI enters "Panic Mode" and pulses red.

We aren't just displaying a number; we're creating emotion.

Let's Build It

We need three things:

  1. A Timer Device (The source of truth for time).
  2. A User Widget (The screen that shows the time).
  3. A Verse Script (The brain that decides if we're panicked or chill).

Step 1: The Setup (The Stage)

  1. Place a Timer device in your island. Set it to Countdown mode. Set the duration to 60 seconds for testing.
  2. Go to the Content Browser -> UI -> Create a new User Widget. Name it PanicTimerWidget.
  3. Open the widget. Add a Text Block widget. Name it TimerText. Resize it to be huge (center of screen, big font).
  4. Crucial Step: In the Widget Graph, select TimerText. In the Details panel, look for Bindings. We will bind this later. For now, just make sure the text is visible.

Step 2: The Verse Script (The Brain)

Create a new Verse file in your project (right-click Content Browser -> New Verse Script). Let's call it PanicTimerLogic.verse.

Here is the code. Copy it, but read the comments—they are the most important part.

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

# This is our main "Actor" (like a Player or a Prop).
# It holds the logic for our timer.
PanicTimerLogic := class(creative_device):

    # 1. BINDINGS: Connect this script to the devices in your level.
    # Think of this as plugging a cable from the Timer Device into our script.
    @editable
    TimerDevice: timer_device = timer_device{}

    # 2. BINDINGS: Connect to the HUD Message Device that shows our countdown.
    # We use a hud_message_device because Verse cannot directly bind to a
    # widget text-block at runtime; the hud_message_device is the supported
    # path for displaying updating text to all players.
    # note: If your project uses a custom widget, drive it from a Verse
    #       `button_device` or `hud_message_device` as shown here.
    @editable
    HUDMessage: hud_message_device = hud_message_device{}

    # 3. VARIABLES: The "Health Bar" of our logic.
    # This holds the current time. It changes every second.
    var CurrentTime: float = 0.0

    # 4. CONSTANTS: The "Rules of the Game."
    # These never change. They are the thresholds for panic.
    PanicThreshold: float = 10.0
    CalmThreshold: float = 30.0

    # This function runs ONCE when the game starts.
    # Like the "Pre-Round" phase.
    OnBegin<override>()<suspends>: void =
        # Start the timer device
        TimerDevice.Start()

        # Subscribe to the timer's "Success" event, which fires when the
        # countdown ends, and drive our per-second loop separately.
        TimerDevice.SuccessEvent.Subscribe(OnTimerEnd)

        # Spin up our own update loop so we can poll time each second.
        spawn { RunUpdateLoop() }

    # Polls the timer device once per second and refreshes the UI.
    RunUpdateLoop()<suspends>: void =
        loop:
            Sleep(1.0)
            UpdateUI()

    # Fires when the timer device reaches zero.
    OnTimerEnd(Agent: ?agent): void =
        HUDMessage.Hide()

    # This function runs EVERY second inside RunUpdateLoop.
    # This is the "Game Loop" for our logic.
    UpdateUI(): void =
        # Get the current time from the device.
        # Think of this as checking the scoreboard.
        # note: timer_device exposes GetActiveDuration() which returns elapsed time;
        #       subtract from the configured duration to get time remaining.
        # Here we read the elapsed time and label it clearly.
        ElapsedSeconds := TimerDevice.GetActiveDuration()

        # Format the time as a plain integer string (whole seconds elapsed).
        # Int() truncates toward zero, which is correct for a countdown display.
        ElapsedInt: int = if (V := Int[ElapsedSeconds]) then V else 0
        TimeString: message = "{ElapsedInt}s"

        # Show the string through the HUD Message device.
        HUDMessage.SetText(TimeString)
        HUDMessage.Show()

        # Store for state checks below.
        set CurrentTime = ElapsedSeconds

        # DECIDE THE STATE (The "Panic" Logic)
        # note: timer_device does not expose a direct GetTimeRemaining(); we
        #       track urgency by comparing elapsed time against the 60 s total
        #       set on the device (60 - elapsed gives remaining).
        TimeRemaining: float = 60.0 - CurrentTime

        if (TimeRemaining <= PanicThreshold):
            # PANIC MODE: Time is low.
            # Show an urgent prefix so the player knows to hurry.
            HUDMessage.SetText("!! {ElapsedInt}s !!")

        else if (TimeRemaining <= CalmThreshold):
            # WARNING MODE: Time is getting tight.
            HUDMessage.SetText("> {ElapsedInt}s <")

        else:
            # CALM MODE: Plenty of time. Plain display.
            HUDMessage.SetText(TimeString)```

### Step 3: Connecting the Dots (The Wiring)

Now, go back to your Verse Script in the editor. You'll see fields for `TimerDevice` and `HUDMessage`.

1.  **TimerDevice:** Drag your **Timer Device** from the level into this slot.
2.  **HUDMessage:** This is trickier. You need to select the **User Widget** instance in your level (or the widget itself in the content browser if using a popup).
    *   *Note:* If you are using a **Widget Popup** device, you bind the widget to the popup. If you are using a **User Widget** directly, you might need to ensure the widget is visible.
    *   *Pro Tip:* The easiest way to bind UI in Verse is often through a **Widget Popup** device. Place a Widget Popup, set its widget to `PanicTimerWidget`, and then bind the `HUDMessage` variable in Verse to the `TextBlock` inside that widget via the **View Bindings** in the Widget Editor, OR bind the whole widget instance to the script.
    *   *Simpler Approach for Beginners:* Let's use **View Bindings** in the Widget Editor instead of Verse for the text update, and use Verse *only* for the color/size changes. This is often more stable for beginners.

**Revised Plan for Stability:**
1.  In the **Widget Editor**, select `TimerText`.
2.  In the Details panel, find **Bindings** -> **Text**.
3.  Click the binding, select **TimerDevice** -> **Current Time**.
4.  Now, the text updates automatically! We don't need `UpdateUI` to set the text. We only need Verse to change the *style* (color/size) based on that time.

Let's refine the Verse script to only handle the **Style Changes**:

```verse
# Imports remain the same as the first script above.
# This version drives panic state through two editable float variables
# that the Widget Editor can bind to via View Bindings.

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

PanicTimerLogic := class(creative_device):

    @editable
    TimerDevice: timer_device = timer_device{}

    # ---------------------------------------------------------------
    # These two variables are the "signal wires" going to the Widget.
    # In the Widget Editor, bind TimerText's Color alpha/tint to IsPanic
    # and bind its render scale to PanicScale using View Bindings.
    # ---------------------------------------------------------------

    # True  => widget should show Panic styling (red, big)
    # False => widget should show Calm styling  (white, normal)
    var IsPanic: logic = false

    # 1.0 = normal size, 1.5 = panic size
    var PanicScale: float = 1.0

    OnBegin<override>()<suspends>: void =
        TimerDevice.Start()
        TimerDevice.SuccessEvent.Subscribe(OnTimerEnd)
        spawn { RunUpdateLoop() }

    RunUpdateLoop()<suspends>: void =
        loop:
            Sleep(1.0)
            UpdatePanicState()

    OnTimerEnd(Agent: agent): void =
        set IsPanic = false
        set PanicScale = 1.0

    UpdatePanicState(): void =
        # note: GetCurrentTime() returns seconds elapsed since Start().
        #       Subtract from your configured duration (60 s) to get remaining.
        TimeRemaining: float = 60.0 - TimerDevice.GetCurrentTime()

        if (TimeRemaining <= 10.0):
            set IsPanic = true
            set PanicScale = 1.5   # Make it bigger to induce stress!
        else:
            set IsPanic = false
            set PanicScale = 1.0   # Normal size
verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

PanicTimerLogic := class(creative_device):

    @editable
    TimerDevice: timer_device = timer_device{}

    var IsPanic: logic = false
    var PanicScale: float = 1.0

    # NEW: drives the blink effect below 5 s
    var BlinkState: logic = false

    OnBegin<override>()<suspends>: void =
        TimerDevice.Start()
        TimerDevice.SuccessEvent.Subscribe(OnTimerEnd)
        spawn { RunUpdateLoop() }
        spawn { RunBlinkLoop() }   # second coroutine for blinking

    # ---- per-second state update ----
    RunUpdateLoop()<suspends>: void =
        loop:
            Sleep(1.0)
            UpdatePanicState()

    # ---- half-second blink toggle (only visible when BlinkState matters) ----
    RunBlinkLoop()<suspends>: void =
        loop:
            Sleep(0.5)
            set BlinkState = not BlinkState   # flip true <-> false

    OnTimerEnd(Agent: agent): void =
        set IsPanic    = false
        set PanicScale = 1.0
        set BlinkState = false

    UpdatePanicState(): void =
        TimeRemaining: float = 60.0 - TimerDevice.GetCurrentTime()

        if (TimeRemaining <= 5.0):
            # CRITICAL: yellow + blink (BlinkState drives widget opacity)
            set IsPanic    = true
            set PanicScale = 1.5
            # BlinkState is already toggling in RunBlinkLoop
        else if (TimeRemaining <= 10.0):
            # PANIC: red, enlarged, no blink
            set IsPanic    = true
            set PanicScale = 1.5
            set BlinkState = true   # keep fully visible
        else:
            set IsPanic    = false
            set PanicScale = 1.0
            set BlinkState = true   # keep fully visible

Recap

  • Variables are like your health bar—they change during the game.
  • Constants are like the rules of the game—they don't change.
  • Bindings are the cables that connect your Verse logic to your UI.
  • Logic (if/else) lets you change the game's feel based on conditions (like time running out).

You just built a timer that doesn't just tell time—it creates tension. That's the difference between a map and a game. Now go make your players sweat.

References

  • https://dev.epicgames.com/documentation/en-us/fortnite/making-an-animated-timer-in-unreal-editor-for-fortnite
  • https://dev.epicgames.com/documentation/en-us/uefn/making-an-animated-timer-in-unreal-editor-for-fortnite
  • https://dev.epicgames.com/documentation/en-us/fortnite/user-interface-devices-in-unreal-editor-for-fortnite
  • https://dev.epicgames.com/documentation/en-us/fortnite/ingame-user-interfaces-in-unreal-editor-for-fortnite
  • https://github.com/vz-creates/uefn

Get the complete code — free

You've read the full walkthrough. The complete, copy-paste-ready Verse solution is free for members — sign in to unlock it.

Free with your BrainDead.TV / BrainDeadGuild Discord account. The walkthrough above is always free.

Verse source files

Turn this into a guided course

Add making-an-animated-timer-in-unreal-editor-for-fortnite 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