Skip to content
Krish edited this page Oct 8, 2026 · 3 revisions

Timer System

Overview

The timer system tracks how long a run has lasted and how long the player has spent in each dungeon. It feeds the HUD, achievements, the save file, and the load screen.

Class Package Role
GameTime services Frame delta and time scale
RunTimer services Total run time and per-dungeon times
TimerDisplay components.gamearea HUD panel showing the times
RoomManager components.rooms Starts and stops dungeon timing as rooms change

GameTime

GameTime wraps libGDX's frame delta.

Method Returns
getDeltaTime() Seconds since the last frame, multiplied by the time scale
getRawDeltaTime() Seconds since the last frame, ignoring the time scale
getTime() Milliseconds since this GameTime was created
getTimeSince(lastTime) getTime() - lastTime
setTimeScale(scale) 1.0 is normal speed and 0.0 stops time

RunTimer

State

  • Run clock (totalTime): seconds elapsed in the run.
  • Dungeon clock (dungeonTime): seconds in the dungeon currently being timed.
  • Dungeon times (dungeonTimes): a map of dungeon id to seconds, kept in insertion order.

Lifecycle

Method Behaviour
startRun() Resets everything and starts the run clock. Does nothing if the run is already running.
registerDungeon(id) Adds the dungeon with 0 seconds so unvisited dungeons still show in the save list. Ignores null or blank ids.
startDungeon(id) Stops any running dungeon, then continues from that dungeon's stored time. Does nothing if that dungeon is already running.
stopDungeon() Stores the current dungeon's time and clears the current dungeon id.
update() Called once per frame from MainGameScreen.render(). Adds the delta to the run clock and recalculates the dungeon clock.
stopRun() Stops the run clock and the current dungeon. Called when the player dies.
restoreRun(seconds, dungeonTimes) Loads saved times, then continues counting. Throws IllegalArgumentException for negative, NaN or infinite values, or for blank dungeon ids.

How dungeon time works

The dungeon clock is derived from the run clock rather than counted separately:

dungeonStartTotal = totalTime - savedDungeonTime   // set in startDungeon
dungeonTime       = totalTime - dungeonStartTotal  // set every update

So re-entering a dungeon continues from its previous time, and both clocks always advance together.

Delta clamp

update() caps each frame's delta at 0.25f seconds (MAX_DELTA). A long hitch, such as dragging the window, therefore adds at most a quarter of a second.

Pausing

requestPause() sets a flag and requestResume() clears it. While paused, update() returns early and neither clock advances. Pausing is separate from GameTime.setTimeScale.

Synced display time

getDungeonSyncedTime() returns the dungeon time aligned to the run clock's whole-second boundaries, so the run and dungeon labels tick over in the same frame.

Formatting

  • formatTimeWithNoMilliSec(seconds) returns MM:SS.
  • formatTime(seconds) returns MM:SS.hh.

Minutes do not roll over into hours, so 100 minutes displays as 100:00.

TimerDisplay (HUD)

  • It is a panel in the bottom-left corner of the screen, scaled to 0.9.
  • It shows RUN: with the total time, and DUNGEON with the dungeon time.
  • Outside a dungeon the dungeon row shows --:--. Inside one the caption becomes DUNGEON: <n>, using the mapping below.
  • Initial visibility comes from UserSettings.get().showTimer.
  • Pressing T (handled by TimerDisplay.ToggleInput) shows or hides the panel. The timers keep running while it is hidden, and the choice is not saved to settings.
Dungeon id Label
dungeonOne to dungeonFour 1 to 4
finalDungeon END
anything else empty

Integration

RoomManager

  • The constructor registers every room's dungeonId.
  • create() starts timing the starting room's dungeon.
  • switchToRoom(...) calls stopDungeon() and then startDungeon(...) when the dungeon id changes.
  • completeDungeon() passes getDungeonTime() to the achievement system as the clear time.

Saving and loading

  • On save, playTimeSeconds is set to getTotalTime() and dungeonTimesSeconds to getDungeonTimes(). That method includes the dungeon currently being timed, so no stopDungeon() call is needed first.
  • On load, MainGameScreen calls restoreRun(playTimeSeconds, dungeonTimesSeconds).
  • The load screen shows play time as MM:SS and the per-dungeon times as a |-separated list. See [Save System](Save-System).

UML diagrams

Class diagram

classDiagram
    direction LR

    class GameTime {
        -long startTime
        -float timeScale
        +setTimeScale(float)
        +getDeltaTime() float
        +getRawDeltaTime() float
        +getTime() long
        +getTimeSince(long) long
    }

    class RunTimer {
        -float totalTime
        -float dungeonTime
        -Map~String,Float~ dungeonTimes
        -String currentDungeonId
        -boolean runRunning
        -boolean dungeonRunning
        -boolean paused
        -float dungeonStartTotal
        -float dungeonSyncedTime
        -MAX_DELTA = 0.25f
        +startRun()
        +restoreRun(float, Map)
        +registerDungeon(String)
        +startDungeon(String)
        +stopDungeon()
        +stopRun()
        +update()
        +requestPause()
        +requestResume()
        +getTotalTime() float
        +getDungeonTime() float
        +getDungeonTimes() Map
        +getDungeonSyncedTime() float
        +getCurrentDungeonId() String
        +formatTime(float) String
        +formatTimeWithNoMilliSec(float) String
    }

    class TimerDisplay {
        -RunTimer runTimer
        -boolean visible
        -Label runTime
        -Label dungeonTime
        -Label dungeonCaption
        +create()
        +draw(SpriteBatch)
        +toggle()
        +dispose()
    }

    class ToggleInput["TimerDisplay.ToggleInput"] {
        +keyDown(int) boolean
    }

    class UIComponent
    class InputComponent

    class RoomManager {
        -RunTimer runTimer
        +create()
        -switchToRoom(RoomConfig, PositionConfig)
        -completeDungeon()
    }

    class MainGameScreen {
        -RunTimer runTimer
        +render(float)
        -saveRun()
    }

    class GameSaveData {
        +float playTimeSeconds
        +Map~String,Float~ dungeonTimesSeconds
    }

    class LoadGameScreen {
        -formatPlayTime(float) String
        -formatDungeonTimes(Map) String
    }

    class ServiceLocator {
        +getRunTimer() RunTimer
        +registerRunTimer(RunTimer)
    }

    RunTimer --> GameTime : reads delta from
    TimerDisplay --|> UIComponent
    ToggleInput --|> InputComponent
    TimerDisplay *-- ToggleInput : toggled by
    ToggleInput --> TimerDisplay : toggle()
    TimerDisplay ..> RunTimer : reads times
    RoomManager ..> RunTimer : start/stop dungeon
    MainGameScreen *-- RunTimer : creates and updates
    MainGameScreen ..> GameSaveData : writes times on save
    MainGameScreen ..> RunTimer : restoreRun on load
    LoadGameScreen ..> GameSaveData : displays times
    ServiceLocator o-- RunTimer
    RoomManager ..> ServiceLocator : getRunTimer()
    TimerDisplay ..> ServiceLocator : getRunTimer()
Loading

Sequence diagram: timer lifecycle

sequenceDiagram
    participant MGS as MainGameScreen
    participant RT as RunTimer
    participant GT as GameTime
    participant RM as RoomManager
    participant TD as TimerDisplay

    alt new game
        MGS->>RT: startRun()
    else loaded save
        MGS->>RT: restoreRun(playTimeSeconds, dungeonTimesSeconds)
    end
    MGS->>RM: new RoomManager(...)
    RM->>RT: registerDungeon(id) for each room
    MGS->>RM: create()
    RM->>RT: startDungeon(startRoom.dungeonId)

    loop every frame
        MGS->>RM: update()
        MGS->>RT: update()
        RT->>GT: getDeltaTime()
        GT-->>RT: delta (clamped to 0.25s)
        TD->>RT: getTotalTime() / getDungeonSyncedTime()
        TD->>TD: refresh labels
    end

    Note over RM,RT: player changes room
    RM->>RT: stopDungeon()
    RM->>RT: startDungeon(newDungeonId)

    Note over MGS,RT: Save and exit
    MGS->>RT: getTotalTime()
    MGS->>RT: getDungeonTimes()
    MGS->>MGS: write to GameSaveData
Loading

State diagram: timer states

stateDiagram-v2
    [*] --> Idle
    Idle --> RunRunning : startRun() / restoreRun()
    RunRunning --> DungeonRunning : startDungeon(id)
    DungeonRunning --> DungeonRunning : startDungeon(other id), stops the old one first
    DungeonRunning --> RunRunning : stopDungeon()
    RunRunning --> Stopped : stopRun()
    DungeonRunning --> Stopped : stopRun()
    Stopped --> RunRunning : restoreRun()
Loading

Table of Contents

Home

Test Plan

Team 2

Features

Components
Player
Items and Inventory
Enemies
Interactable NPCs
Cutscenes and Dialogue
Music
Bosses
Rooms
Weapons
Abilities & Spells
Game Screens and Areas
Dungeons

Game Engine

Getting Started

Entities and Components

Service Locator

Loading Resources

Logging

Unit Testing

Debug Terminal

Input Handling

UI

Animations

Audio

AI

Physics

Terrain

Concurrency & Threading

Settings

Troubleshooting

MacOS Setup Guide

Clone this wiki locally