Skip to content

Time System

gilgilgilgilgil edited this page Aug 29, 2023 · 51 revisions

The time system serves as an integral part of Gardens of the Galaxy, providing dynamic updates and information to other dependent classes. Since this is a farming game, the core goals revolve around time as you build and expand your farm over time while growing plants that take time to harvest. Hence, it is essential that an intuitive yet easy-to-understand implementation of the time system is required so that other classes can interact easily with the time system and that valuable information is provided to these dependent classes.

This implementation of the time system provides working in-game time, where each day in the game lasts 12 minutes in real time. A visual implementation of the in-game time is also present at the top left of the screen, which has an indicator (sun or moon) as well as the current hour of the day (in 12-hour time). The time will dynamically update on the UI, and the indicator will update to match that time as well. For example, as the day turns into night, the sun will slowly set and the moon will eventually rise. This ensures that the player will always be informed of the current time through text and images, providing a much more visually appealing experience for the player. The implementation also provides a pause and unpause function as well as a function to set the time to aid in debugging and testing to improve workflow.

Display

UI Design

The game time display is composed of three objects, which are a clock frame, a planet image, and a text label with the time.

Clock Frame

The clock frame appears at the back, and the planet and text are overlayed on top of it.

clock_frame

Planet Image

There are 24 planet images for the time, each representing a distinct hour of the day. These are cycled through dynamically depending on the current time, and change slightly from the previous one. The times below are for 12 am and 12 pm.

indicator_0 indicator_12

Time Display

The planet image and the time label correspond to the same game time, so only valid combinations get displayed. A complete image is shown in the top left corner of the main game screen.

image

Implementation

The GameTimeDisplay class serves as the UI creator for the game time display on the main game screen. It relies on an instance from the TimeController class to update the display, which provides a time that the display should be updated to. A Group is used to store the clock frame image and the planet image, which overlap perfectly when added to it. Depending on the number of digits of the time we want (i.e. 1 am vs 11 am), an offset is introduced to keep the text centred in the the bottom of the frame. This position is set relative to the coordinates of the clock image, to ensure that the text will be correctly located. This Group containing all of the images and text is then added to a Table, which is set for the top left corner and then added to the Stage.

UML Diagram

The UML diagram shows the interactions between GameTime, GameTimeDisplay and TimeController. They are all interconnected to each other through TimeController, since it controls the time and allows the other classes to access it.

image

Inspirations

The images were chosen to look similar to the rest of the game and the colour palette was complementary to the other aspects of the game. The simple design with the wooden clock frame is representative of the lowly beginning of a farming simulation.

Functionality

The functionality is centred in GameTime, GameTimeDisplay and TimeController within services

Implementation

The TimeController class serves as the control centre for all things that are dependent on the time. It is initialised in the constructor for GameTime and is stored within the current instance of GameTime, so when a new GameTime service is created at the start of a new game, a TimeController service will be created automatically. This ensures that each new game will have its own TimeController.

It follows an observer design pattern, where TimeController is the observable sending updates to the observers that are registered within the class. The class contains a private variable List<Entity> entities, which can be any form of entity such as a plant and can be registered or removed from the TimeController class. It also contains another observer GameTimeDisplay timeDisplay which is registered automatically and these observers will receive updates from TimeController; update() for entities and updateDisplay() for the UI display.

TimeController also contains functions to improve ease of use for dependent classes. Since GameTime returns the time in milliseconds, getTimeInSeconds() returns the time in seconds to make it easier to understand. Functions getTimeOfDay() returns the current time of the day in milliseconds and getHour() returns the current hour as an integer.

UpdateDisplay() Logic

UpdateDisplay() calculates the current hour of the game, then calls timeDisplay.update(hour) with the hour that was just caluculated. This lets GameTimeDisplay know to update the text to the current hour and update the day-night indicator to the image for the given hour. The display will not be updated if the game is paused and the current hour is calulated by:

int timeInDay = (int) timeSource.getActiveTime() % 720000 - this gets the current time of the day in milliseconds by using the modulus function. It divides it by 720000 as thats how many milliseconds are in 12 minutes which is the length of a day in the game. The remainder from the modulus operation is the current time of the day.

this.hour = (int) Math.floor(timeInDay / 30000) - this then gets the current hour of the day as an integer, by dividing by 30000 which is 30 seconds or 1 hour in game. This value is floored as we are only interested in the hour.

Pause() Logic

The pause() and unpause() function will allow for support for a pause menu, as well as assist in debugging and testing. Each time the game is paused, the timestamp is recorded this.pausedAt = timeSource.getTime() and the Boolean paused is set to true. When the game is unpaused the difference between the current time and the time that the game was paused at is calculated timeSource.addPauseOffset(**timeSource.getTimeSince(this.pausedAt)**). This duration is added to the variable pausedTime in GameTime, and then the total real time in game can be accessed by getActiveTime() which returns `TimeUtils.timeSinceMillis(startTime) - pausedTime'.

How To Use

Access The Time Controller

  • ServiceLocator.getTimeSource().getTimeController()

Register, Remove and Update Entities

  • ServiceLocator.getTimeSource().getTimeController().register('Entity')
  • ServiceLocator.getTimeSource().getTimeController().remove('Entity')
  • ServiceLocator.getTimeSource().getTimeController().update()

Get Time, ActiveTime

  • ServiceLocator.getTimeSource().getTime() - time since the start of the game including paused periods
  • ServiceLocator.getTimeSource().getActiveTime() - time since the start of the game excluding paused periods

Future Development

A setTime() function is currently is works to assist in debugging and testing

TimeController Testing Plan

Plan is to apply at least 3 test cases for relevant methods of TimeController:

  1. A time in the first 12 minute cycle (e.g. 300,000 ms).
  2. A time in the transition between 12-min cycles (720,000 ms or a multiple of this).
  3. A time after the transition to the new cycle (e.g. 800,000 ms).

As per Unit Testing :

  • All tests should should extend from GameExtension, using the @ExtendWith(GameExtension.class) annotation. This ensures that the correct mocking of underlying graphics is done, and that the service locator is cleared between tests.
  • When testing anything that relies on time, use ServiceLocator.getTimeSource() as your time source. Then in your tests you can provide a custom timesource with hardcoded values. You should never have tests that rely on real time passing.

All other dependencies will be made with mock objects using the Mockito library.

Clone this wiki locally