Skip to content

Add a day/night cycle. #109

@dmccoystephenson

Description

@dmccoystephenson

Summary

Add a day/night cycle that periodically dims and brightens the game world by applying a brightness overlay to the rendered game area, affecting all visible entities uniformly.

Background

Roam's renderer draws entities tile-by-tile in Room.drawLocation() (src/world/room.py) and Room.drawWithOffset(), blitting scaled pygame surfaces directly to graphik.gameDisplay. The simplest way to apply a global brightness effect without touching individual entity textures is to blit a semi-transparent black overlay onto the game area rect (Graphik.getGameAreaRect()) after all tiles are drawn each frame, with opacity driven by the current tick and a configurable cycle length. Note: if PR #344 is merged before this, any new print() calls should use getLogger from src/gameLogging/logger.py instead.

Requirements

Config

  • Add dayNightCycleEnabled boolean (default true) and dayNightCycleLengthTicks integer (default 43200 — 30 minutes at 30 tps) to config.yml and load both in Config (src/config/config.py)
  • Add a Day/Night Cycle toggle button to ConfigScreen (src/screen/configScreen.py), consistent with existing toggles

Cycle Logic

  • Create src/world/dayNightCycle.py with a DayNightCycle class registered as a @component in the DI container
  • The class should expose a getOverlayOpacity(tick) method that maps the current tick to an opacity value in the range 0 (full brightness, midday) to 200 (near-dark, midnight) using a sine curve so transitions are smooth rather than linear
  • Midday should occur at tick % cycleLengthTicks == 0 and midnight at the halfway point

Rendering

  • In WorldScreen.draw() (src/screen/worldScreen.py), after all rooms are drawn and before HUD elements, blit a pygame.Surface filled with (0, 0, 0) at the opacity returned by getOverlayOpacity onto the game area rect (graphik.getGameAreaRect())
  • Use pygame.SRCALPHA or Surface.set_alpha() for the overlay — do not create a new surface every frame; cache it as a WorldScreen attribute and only recreate it when the game area rect size changes
  • The overlay must be clipped to the game area rect so letterbox bars outside the square game area are not affected
  • The cycle must be skipped entirely (no overlay drawn) when config.dayNightCycleEnabled is False

Debug Info

  • When config.debug is True, include the current cycle phase (e.g. day, dusk, night, dawn) and overlay opacity alongside the existing debug text drawn in WorldScreen

Save/Load

  • The cycle derives its state entirely from TickCounter.getTick(), which is already persisted in tick.json — no additional save/load logic is needed

Acceptance Criteria

  • The game world smoothly transitions between full brightness (day) and near-darkness (night) on a configurable tick-based cycle
  • The overlay is clipped to the game area rect and does not affect letterbox bars
  • Toggling dayNightCycleEnabled in the settings screen immediately stops or resumes the overlay
  • dayNightCycleLengthTicks in config.yml controls the cycle duration and is respected without a restart
  • Debug mode displays the current cycle phase and overlay opacity
  • The overlay surface is not reallocated every frame
  • All existing tests pass; new tests cover DayNightCycle.getOverlayOpacity() at tick values corresponding to midday, midnight, dusk, and dawn

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions