Repository navigation
Timer
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 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 |
-
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.
| 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. |
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.
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.
requestPause() sets a flag and requestResume() clears it. While paused, update() returns early and neither clock advances. Pausing is separate from GameTime.setTimeScale.
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.
-
formatTimeWithNoMilliSec(seconds)returnsMM:SS. -
formatTime(seconds)returnsMM:SS.hh.
Minutes do not roll over into hours, so 100 minutes displays as 100:00.
- It is a panel in the bottom-left corner of the screen, scaled to 0.9.
- It shows
RUN:with the total time, andDUNGEONwith the dungeon time. - Outside a dungeon the dungeon row shows
--:--. Inside one the caption becomesDUNGEON: <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 |
- The constructor registers every room's
dungeonId. -
create()starts timing the starting room's dungeon. -
switchToRoom(...)callsstopDungeon()and thenstartDungeon(...)when the dungeon id changes. -
completeDungeon()passesgetDungeonTime()to the achievement system as the clear time.
- On save,
playTimeSecondsis set togetTotalTime()anddungeonTimesSecondstogetDungeonTimes(). That method includes the dungeon currently being timed, so nostopDungeon()call is needed first. - On load,
MainGameScreencallsrestoreRun(playTimeSeconds, dungeonTimesSeconds). - The load screen shows play time as
MM:SSand the per-dungeon times as a|-separated list. See [Save System](Save-System).
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()
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
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()