-
Notifications
You must be signed in to change notification settings - Fork 7
Lighting system
To enhance the game and make the game time feel more meaningful, a day/night cycle has been implemented. This complements the existing game time display by adding brightness and darkness to the game during day and night respectively. To implement this system, a 2D lighting framework for LibGdx has been used called Box2dLights. This lighting framework uses Box2d for all of its raycasting which is helpful as this is the 2D physics engine that is used in this game engine. This framework provides a vast number of lighting features; however, to keep the lighting system within the game style and to reduce CPU burden, very little of the dynamic lighting features are used.
In order to manage everything related to lights updating and rendering, box2dlights uses a RayHandler
class. This RayHandler
class uses the World
from the PhysicsEngine
in order to handle all of the collision geometry for the lighting. In order to readily access this RayHandler
in order to create lights across the game, the LightService
has been created, which essentially acts as a wrapper class for the RayHandler
following the design patterns similar to the other services seen in the ServiceLocator
. Additionally, this LightService
is accessible through the ServiceLocator
as lighting may not be restricted to the main game area as it could be used on other screens (eg: main game screen, loading screens, etc.).
The following methods are available through the LightService
:
-
renderLight()
- method used in theRenderer
to render all of the lights in the game -
getRayHandler()
- method used to get theRayHandler
instance. This is used during the creation of lights as they need to be registered to aRayHandler
instance. Additionally, this method can be used to access theRayHandler
instance and use its methods.
In order to ensure that all entities in the game can benefit both stylistically and functionally from lighting, the AuraLightComponent
component was created. This class is a component-like wrapper for the PointLight
class in box2dlights, allowing entities in the ECS benefit from lighting. To reduce complexity, CPU burden and match the lighting style with the game theme, static lighting has been enabled, soft lighting has been enabled and lights have been set as x-ray lights meaning that they pass through geometry.
In order to maximise flexibility with this lighting components, enabling lights to have different colours and distance (spread), the following constructor methods are offered.
AuraLightComponent()
AuraLightComponent(float distance)
AuraLightComponent(Color color)
AuraLightComponent(float distance, Color color)
If not passed into the constructor, the default values are used:
- Distance: 8
- Colour: Light grey
If other entities want to take advantage of this lighting system, they simply need to have the AuraLightComponent
added to them when they are created. If different colours and light distances are needed, one of the constructors can be chosen. Additionally, with the use of the toggleLight
event, the lights of different entities can be turned on/off with certain conditions. For example, if a plant were to have a glow-in-the-dark feature, a plant could just listen for the hourUpdate
event from the TimeService
, then check the current time, then if appropriate, fire the toggleLight
event to the EventHandler
of the entity.
To create lights cone-like lights that shine in a particular direction, the ConeLightComponent
has been created. Similar to the AuraLightComponent
, the ConeLightComponent
is also a component-like wrapper for the ConeLight
in Box2dLights.
In order to maximise flexibility with this lighting components, enabling lights to have different colours, distance (spread), cone degree and direction degree, the following constructor methods are offered.
ConeLightComponent()
ConeLightComponent(float distance)
ConeLightComponent(Color color)
ConeLightComponent(float distance, Color color)
ConeLightComponent(float distance, Color color, float directionDegree)
ConeLightComponent(float distance, Color color, float directionDegree, float coneDegree)
If not passed into the constructor, the default values are used:
- Distance: 8
- Colour: Light grey
- Direction Degree: 0 degrees (pointing right, measured anticlockwise)
- Cone Degree: 30 degrees
Similar to the AuraLightComponent
, the ConeLightComponent
just needs to be added to the entity in order for them to have light. To toggle this light, fire the toggleLight
event to the entity's event handler. When updating every frame, this cone light will follow the player and be positioned at the player's centre position. The direction of the light does not change every frame; however, it can be changed using the setDirection() method.
In order for the player to survive the darkness of the night, they are equipped with a torch, which is just a AuraLightComponent
under the hood. Using the toggleLight
event which is handled by the AuraLightComponent
through its toggleLight()
method, the player is able to turn on/off the light using the t
key (see PlayerActions
class).
As the lights are all registered with the RayHandler
instance, they are all kept track of and easily drawn using the updateAndRender()
method of the RayHandler
. In order for the lights to be drawn in the correct order and not be drawn over important ui components, the render()
method in the Renderer
class was modified. This method is shown below:
public void render() {
Matrix4 projMatrix = camera.getProjectionMatrix();
batch.setProjectionMatrix(projMatrix);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
renderService.render(batch);
batch.end();
if (ServiceLocator.getLightService() != null) {
ServiceLocator.getLightService().renderLight();
}
debugRenderer.render(projMatrix);
stage.act();
stage.draw();
}
The order of rendering here is quite simple, as is as follows:
- Anything renderable with a sprite is rendered by the
RenderService
. This includes the player, animals, items, the map, etc. - The lights are then rendered by the
LightService
with therenderLight()
method. - The game UI is then rendered as it is part of the stage.
When creating new visual components, you need to determine whether or not they will be drawn before or after the lights are drawn and hence decide whether they should be part of the game stage or not.
Below is a UML overview of the core classes in the system (ignoring specific implementations).