Skip to content

Day and Night Cycle Service

Michelle edited this page Oct 16, 2022 · 21 revisions

Page Navigation

Jump to a section or return to Day and Night Cycle Summary here!

Summary

DayNightCycleService was created to be the foundation of the day and night cycle. The service is called when the game is first started started by the player and runs in the background in its own thread for the duration of the game -- keeping track of how many milliseconds have elapsed in the current day, what day number it is and what phase (DAWN, DAY, DUSK, NIGHT) of the cycle it currently is. The service also handles triggering of the events that inform other features of the current day phase so they are able to dynamically update their status based on the current day state (e.g. trigger the enemies to spawn at night).

Technical

DayNightCycleService

The main part of implementing the cycle, DayNightCycleService controls the running of the day/night service.

Start

public CompletableFuture<Object> start() {
    if (this.isStarted && !this.isPaused) {
        throw new IllegalStateException("The timer has already been started");
    }

    if (this.isPaused) {
        // Resuming a timer
        logger.info("Day/night cycle resumed");
        this.isPaused = false;
        return null; // Avoid running another async job
    }

    this.isStarted = true;
    this.setPartOfDayTo(DayNightCycleStatus.DAWN);

    return JobSystem.launch(() -> {
        try {
            this.run();
        } catch (InterruptedException e) {
            logger.error(e.getMessage());
        }

        return null;
    });
}

Pause

public void pause() {
    this.isPaused = true;
    this.timePaused = this.currentDayMillis;
    logger.info("Day/night cycle paused");
}

Run

The run method handles updating the current day, the milliseconds passed, and triggering the events for the different phases of the day/night cycle. The method sleeps for 100 milliseconds after each loop to reduce the memory load on the system running it.

When the game is not paused, the current day time is updated and then compared to the different thresholds for DAWN, DAY, DUSK and NIGHT. If the current time falls into a new threshold and is currently in the correct phase of the day, the day cycle will be progressed to the next phase and the EVENT_PART_OF_DAY_PASSED event is triggered.

this.currentDayMillis = this.timer.getTime() - (this.currentDayNumber * (config.nightLength +
        config.duskLength + config.dayLength + config.dawnLength)) - this.totalDurationPaused  + this.loadedTimeOffset;

For more information on the loadedTimeOffset, see Save Game

After checking all thresholds, if the day would tick over from NIGHT to DAWN, the method will check if all days have been run through. If they have then the service will stop before informing listeners that the a day has passed through the EVENT_DAY_PASSED event.

if (this.currentDayNumber == config.maxDays - 1) {
    // End the game
    this.stop();

    Gdx.app.postRunnable(() -> {
        events.trigger(EVENT_DAY_PASSED, this.currentDayNumber + 1);
    });

    return;
}

If the game is paused then the run method will keep track of how many milliseconds it has been since the service was paused.

durationPaused = this.timer.getTimeSince(this.timePaused);

DayNightCycleConfig

The DayNightCycleConfig class holds the default values for the different day/night phase lengths and the max number of game days. This can be populated by a config file with the desired durations and game days, or manually set whenever a new instance of the service is created.

public class DayNightCycleConfig {
    public long dawnLength = 1;
    public long dayLength = 1;
    public long duskLength = 1;
    public long nightLength = 1;
    public int maxDays = 1;
}

DayNightCycleStatus

DayNightCycleStatus holds an enum for the different day statuses to reduce errors when implementing the day/night cycle in the game from misspellings.

public enum DayNightCycleStatus {
    NONE,
    DAWN,
    DAY,
    DUSK,
    NIGHT;
}

UML Class Diagram

The below UML diagram outlines the structure of the service and its dependencies, with the classes above highlighted in green.

UML class diagram for the Day and Night Cycle Service

This type of representation makes it easier to see how DayNightCycleService works within the context of the game.

Testing

The functionality of DayNightCycleService was tested using JUnit and Mockito in DayNightServiceTest. These tests check that days are updating as they should, the timer is pausing and resuming correctly, and that events are being sent correctly from by the event handler.

Day Cycling Tests

These tests were written to check that the service was correctly cycling through the different stages of the day, and that the timer was correctly stopping after the maximum number of days had passed.

shouldAdvanceToDayWhenAtDawn()
shouldCompleteFullCyclePassingThroughAllPartsOfDay()
shouldCompleteFullDayRestartingAtDawn()
shouldStopWhenDaysAreCompleted()
shouldGoThroughAllNumberOfDays()

These tests are important for checking the core functionality of the day/night cycle service. If they were failing then it would mean there is a flaw in the implementation of the service and it likely will not function as intended at all.

Pause and Resume Tests

Pausing and resuming the timer is also an important part of the service. The timer for the service is GameTime object that is passed through the declaration method, and does not have any pause functionality by itself.

shouldStopDayTimeWhenPaused()
shouldResumeDayTimeWhenResumed()
shouldResumeDayTimeAfterBeingPausedAndResumedMultipleTimes()

If these tests were not passing, it would mean that either the service was not stopping running when paused or that the amount of time the timer had been paused for was not being accounted for correctly.

Event Trigger Tests

The final batch of tests for the DayNightCycleService were intended to check the event trigger methods.

whenSendingFirstDayPassedEventDayShouldBeginAtOne()
shouldGoThroughCorrectSequenceOfDays()
shouldCycleThroughAllWedgesOfDay()
shouldCycleThroughAllWedgesOfNight()
shouldGoThroughAllEightWedges()
shouldHaveDelaysBetweenAllWedges()

These events allow other services to be informed of the current state of the game - including what day number it is and what the current phase of the day/night cycle the game is currently in. This is important for the game as the spawning of enemy entities is tied to the night phase of the cycle, so if the service was not able to correctly send the event triggers it would negatively affect the development of the rest of the game.

Challenges

The main challenges faced in development of the day/night service in handling pausing and resuming the timer. The method used is likely not the most optimal as it requires the service to keep track of multiple different millisecond values; however, by subtracting the total duration paused when calculating the current milliseconds that have passed for the current day it is possible to keep the timer consistent.


Back to Day and Night Cycle Summary

Table of Contents

Home

How to Play

Introduction

Game Features

Main Character

Enemies
The Final Boss

Landscape Objects

Shop
Inventory
Achievements
Camera

Crystal

Infrastructure

Audio

User Interfaces Across All Pages
Juicy UI
User Interfaces Buildings
Guidebook
[Resource Management](Resource-Management)
Map
Day and Night Cycle
Unified Grid System (UGS)
Polishing

Game Engine

Getting Started

Entities and Components

Service Locator

Loading Resources

Logging

Unit Testing

Debug Terminal

Input Handling

UI

Animations

Audio

AI

Physics

Game Screens and Areas

Terrain

Concurrency & Threading

Settings

Troubleshooting

MacOS Setup Guide

Clone this wiki locally