Time System (Sprint 2 Onwards) - UQcsse3200/2023-studio-1 GitHub Wiki
Overview
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 through a fade-out fade-in transition. 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.
Functionality
The aim of this revamped time version is to give entities the ability to perform actions based on the in-game time. Additionally, coupling should be reduced between our in-game time system and the time tools provided in the base game engine. To reduce unnecessary processing, entities behaviours that are time-based (eg: plant growing) should only be triggered on time events (eg: time advances by one hour). This way entities are not required to know anything about the time other than it just advances.
UML Diagram
Compared to the V1 time system, there are a lot fewer interactions between the GameTime, GameTimeDisplay and TimeService.
Implementation
In the base game-engine a GameTime class is provided. This is a service that controls the game time and can calculate various time metrics (eg: time since last frame was drawn, time since game started). To control the in-game time the TimeService class was created which controls and keeps track of the in-game time. Since the GameTime class was provided in the base engine, the descision was made to create another class that uses GameTime rather than adding functionality to the GameTime class.
The TimeService class has various methods to track time and notify entities of time changes. To track the time the update() method is called during the main game loop. Depending on whether the game is paused or not, it will calculate the time passed since the last frame and will add to the in-game time.
public void update() {
// this time will be in ms
long timePassed = ServiceLocator.getTimeSource().getTimeSince(lastGameTime);
lastGameTime = ServiceLocator.getTimeSource().getTime();
if (paused) {
return;
}
timeBuffer += timePassed;
// If time elapsed isn't one hour in the game, do nothing
if (timeBuffer < MS_IN_HOUR) {
return;
}
hour += 1;
timeBuffer -= MS_IN_HOUR;
// If hour is between 0 and 23, day hasn't elapsed, do nothing
if (hour < 24) {
events.trigger("hourUpdate");
return;
}
hour -= 24;
events.trigger("hourUpdate");
day += 1;
events.trigger("dayUpdate");
}
In this update() method, it will determine whether an hour/day has passed, and if so will notify registered entities that this has occurred. See more in the usage section.
Usage
Modifying the game speed
In order to make the game speed increase or decrease, you simply change the MS_IN_MINUTE static variable. By default this is set to 500, and hence 500ms will pass in real-time when 1 minute passes in the game. To increase the game speed you simply decrease this value, and to slow the game down you increase it. Additionally, you are able to set the in-game day and hour through various debug commands (see here)
Pausing in-game time
In order to pause/resume the in-game time, the setPaused(boolean state) method should be used. Depending on the value of the state variable, the game will be paused/resumed which will affect the logic in the update() method.
Pausing the game stops the physics enginer and entities from being updated in Render() in the MainGameScreen class.
if (!ServiceLocator.getTimeService().isPaused()) {
physicsEngine.update();
ServiceLocator.getEntityService().update();
}
Pausing the game will also prevent animations from continuing. By setting the time scale in GameTime to 0, no time will be passing in game will prevent animations from updating (as they depend on delta time which will be 0 if time scale is 0).
public void setPaused(boolean state) {
paused = state;
ServiceLocator.getTimeSource().setTimeScale(state ? 0 : 1);
}
Registering for time-based updates
With this time system, you can add event handlers for both hourly and daily updates. This way, every in-game hour/day, the registered event handler will be called for in-game events. To register for an hourly/daily updates, you must use both the addListener() method which is provided in the base game-engine on the event handler in the TimeService class.
As an example, to register the updateDisplay() method every in-game hour is as follows:
ServiceLocator.getTimeService().getEvents().addListener("hourUpdate", this::updateDisplay);
To run this method every in-game day you would use the following:
ServiceLocator.getTimeService().getEvents().addListener("dayUpdate", this::updateDisplay);
Lose Timer
The update() function will be checking to see if day reaches a certain date. When it does, it will trigger the "loseScreen" event to end the game.
if (day >= LOSE_DAY) {
ServiceLocator.getGameArea().getPlayer().getEvents().trigger("loseScreen");
}