Skip to content

Lighting system

Lucas Hicks edited this page Oct 1, 2023 · 10 revisions

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.

LightService

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.).

Usage

The following methods are available through the LightService:

  • renderLight() - method used in the Renderer to render all of the lights in the game
  • getRayHandler() - method used to get the RayHandler instance. This is used during the creation of lights as they need to be registered to a RayHandler instance. Additionally, this method can be used to access the RayHandler instance and use its methods.

AuraLightComponent

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.

Constructor Flexibility

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

Usage

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.

ConeLightComponent

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.

Constructor Flexibility

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

Usage

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.

Player Lighting

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).

Rendering of the lights

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:

  1. Anything renderable with a sprite is rendered by the RenderService. This includes the player, animals, items, the map, etc.
  2. The lights are then rendered by the LightService with the renderLight() method.
  3. 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.

UML Diagram

Below is a UML overview of the core classes in the system (ignoring specific implementations).

lightingSystemUML

Clone this wiki locally