Level System Overview - UQcsse3200/2024-studio-3 GitHub Wiki
The Level System is what defines the number of customers spawning depending on the level, and how 'difficult' each level should be - with 'difficulty' being defined as the number of interactions required to successfully get through the day, meaning levels with a 'higher difficulty' require more interactions in order for the player to get complete the day. It also stores the player's gold, so that between levels it is not reset.
The back-end of the Level System is comprised of two parts, a LevelService
and a LevelComponent
. The LevelService
acts as a way of globally handling and tracking events related to levels - mainly for triggering them. The LevelComponent
acts as sort of spawn controller which is added to an Entity
in order to control when and what customers are spawned on screen.
Sequence Diagram showcasing how customers are spawned. This diagram is very simplified, and does not showcase certain stages such as registering events and initialising classes.
UML Diagram showing the relations between all classes involved in the Level System (NOTE: Does not contain any implemented interfaces)
The Level System as a whole consists of two separate classes, LevelService
and LevelComponent
. The LevelService
acts as a globally accessible service which can be registered to the ServiceLocator
and allows for the control - triggering a levels spawning pattern and other related events - and tracking of various level related variables - e.g. what the current level is. This is done by having LevelService
store an EventHandler
as well as several private variables and methods. The two most important events that the EventHandler
has listeners for is the startLevel
and startSpawning
events. The startLevel
event calls the levelControl()
method in the registered LevelService
object. When levelControl()
is called, it calculates what the spawn cap of the level should be, and then triggers the startSpawning
event, which is parsed the spawn cap variable. The startSpawning
event is related heavily to the LevelComponent
class.
The LevelComponent
when added to an Entity
makes it (the Entity
) act as a spawn controller, controlling when customers spawn on screen and which customer spawns on screen. When the startSpawning
event is triggered, the method initSpawning()
is called. When called, this method ensures that all private variables in the LevelComponent
object are set to default values and then calls a method called toggleNowSpawning()
. When this method is called, a check present in the update()
method now passes, which then allows for customers to be spawned. When customer spawning is allowed, a check is done to see if it has been three seconds since the last customer spawned, if another customer can be spawned (the spawn cap has not been reached yet) and if another customer can join the line up. If all of these conditions are true, then another customer is spawned. When the spawn cap is reached, then toggleNowSpawning()
is called again, preventing anymore customer spawning from occurring.
In order to make a spawn controller, it was decided the best course of action would be to create a class that extends Component
, as the update()
method could be overwritten, which, when an instance of LevelComponent
is added to an Entity
, allows for easy asynchronous checks due to the EntityService
. To be completely honest, I also had no other ideas of what possibly I could do and I had no idea about scheduling events. Oh well. Additionally, using a Component
allowed for a factory design pattern to be used to instantiate a "spawning controller Entity
."
Creating a new type of service for use with the ServiceLocator
was done because it is a very robust and preexisting method of global access, with several other services already being implemented for similar purposes.
Below is the constructor for the LevelService, which initialise some private variables, and most importantly registers some listeners.
The levelControl()
method called by the startLevel
event currently takes one parameter. All it does for now is limit the number of customers that spawn.
This is in the MainGameScreen
class, at the bottom of the constructor. The first two statements become more relevant in later photos.
This is from the LevelComponent
class. The event startSpawning
, if you remember, is triggered in LevelService
by levelControl()
This is what the event startSpawning
calls. This method basically just ensures that all private variables are reset, but most importantly, enabled spawning through the toggleNowSpawning()
method.
Speaking of, here is the toggleNowSpawning()
method
Now the reason why I use a Component
in the first place is because the Entity
class has a very nice method which is update()
for asynchronous code which can be called constantly (I didn't know about scheduledEvents
). When the conditions are correct, a few methods are called.
Most importantly, spawnCustomer()
, which calls a random event from an Entity
in ForestGameArea
to spawn a customer.
This is the method for creating the Entity
object that has the EventHandler
which listens for all the customer spawning events.
These are the various methods called by the multiple spawning events.
And this is what the spawnCustomer()
method looks like, again, in the ForestGameArea
class
In order to access the LevelService
then ServiceLocator.getLevelService();
must be used, which returns the LevelService
.
Example:
LevelService levelService = ServiceLocator.getLevelService();
In order to access the EventHandler
, use .getEvents();
, which returns the EventHandler
stored in LevelService
. When accessed, the EventHandler
has the standard methods, most importantly, trigger()
and addListener()
.
Example:
EventHandler eventHandler = levelService.getEvents();
eventHandler.addListener("exampleEvent", this::exampleMethod);
eventHandler.trigger("exampleEvent");
As previously outlined, in order to go to a new level, the event startLevel
must be triggered. To trigger this event, an integer between 0 and 10 (inclusive) must be parsed. This event requires the existence of an Entity
with the LevelComponent
added to it, otherwise, no event will be triggered.
Example:
Entity spawnController = LevelFactory.createSpawnControllerEntity();
eventHandler.trigger("startLevel", 1);
In order to use gold, there are two very simple set and get methods that can be called at any time. These are .getCurrGold()
and .setCurrGold(int)
:
Example:
int gold = ServiceLocator.getLevelService.getCurrGold();
ServiceLocator.getLevelService.setCurrGold(50);
There are, 7 prelude levels in total. It is an enum in GDXGame.java
Level 0 -> is only used in Tutorials as it has not increment. Level 1,2,3,4,5,Done -> Are playable levels in the MainGame.
Several tests were written under LevelComponentTest and LevelSystemTest in order to automatically test various methods and logics. Visual tests also played a substantial role for verifying logic, mainly for spawning, incrementing levels and gold persistence. In order to verify spawning, levels have a predetermined number of customers they should spawn, therefore, in order to test this, run the game, enter the game and count how many customers spawn on screen. Totals can be seen under Implementation Summary.
To verify levels were incrementing correctly, then enter the game, wait for all customers to spawn and quit back to the main menu. Re-enter the game. The same amount of customers should spawn, signifying the level did not increase. In order to go to a new level, the player must press P
in order to display the end of day display. When exiting this, the level should increment, and this can be verified by cross referencing how many customers spawned vs how many should spawn according to the level.
With gold preservation, this can be tested by generating gold either through the service window or waiting for a docket to expire, and when going to the next level, the gold will remain the same.