How to use dynamic 2D lighting in Cocos Creator

soida(左泽众)
Last updated:
How to use dynamic 2D lighting in Cocos Creator

What you will learn

  • Create normal maps and sprite sheets with SpriteIlluminator and TexturePacker.
  • Load animation frames from a sprite sheet.
  • Load a normal-map sprite sheet and add a Light2D effect to your animation.

Create your normal maps

The easiest way to create normal maps for your sprites is to use SpriteIlluminator. For a quick start:

  1. Drag and drop your sprites into the SpriteIlluminator window.
  2. Select all sprites.
  3. Apply the effects you like, e.g. Bevel, Emboss, or Structure.
  4. Publish the normal map images to the sprite directory using the _n suffix.

Create the sprite sheets

With TexturePacker you can pack sprites and normal maps on two different sprite sheets, using the same layout:

  1. Start TexturePacker and create a new Cocos2d project.
  2. Drag and drop sprites and normal maps into the TexturePacker window.
  3. Enable the "Pack with same layout" option in the Normal Maps section.
  4. Publish the sprite sheets.

Install Cocos Creator and the Light2D plugin

  1. Go to the official Cocos Creator website and click the Download Cocos Dashboard button. Your browser will start the download automatically.

  2. After installing Cocos Dashboard, you may need to register an account and log in.

    Cocos Dashboard download button on the Cocos website
  3. Install the editor. In the left sidebar, click Installs, then click Install Editor in the upper-right corner. In the version list, choose 3.8.6. The editor downloads and installs automatically; you will receive a system notification when it is done.

  4. Install the Light2D plugin. In the left sidebar, click Store, search for Light2D, and complete the installation and import steps.

    Installing the Light2D plugin from the Cocos Creator Store

Create the game scene

  1. In the editor, create a new empty scene named scene_game. Delete the default MainLight and MainCamera, and create a new UI ComponentsCanvas.

  2. Copy the LightSystemKeep node from the sample scene Scene_Light2D into your scene_game scene.

  3. Drag the prepared texture assets into the Canvas node. You will see the distant sky/mountains and the foreground ground/rocks/trees.

    Scene with sky, mountains, and foreground layers in Canvas

Add light sources

  1. Select these nodes and add the Light2DReceiver component to them. The scene becomes darker because they are now affected by the global ambient light.

  2. Under the Canvas node, create a new empty node named Light-Point and add the Light2DSource component to it. You will see a circular area in the center of the scene illuminated by the default point light. Adjust the parameters to see the effects.

    Point light illuminating the center of the scene
  3. Set the ReceiverLayer of the foreground nodes to LAYER1, and set the AffectLayerList of the light source to only LAYER1. This prevents the distant sky background from being illuminated.

  4. For the foreground nodes, enable EnableNormalMap and assign the corresponding normal map texture. Move the light source node to observe the effect.

    Foreground nodes with normal maps enabled for Light2D

Create the character and animation

  1. Drag a frame of the character asset into the scene as a new node named Role, and add the Animation component to it.

  2. Create the corresponding frame animation AnimationClip. When done, click the Run button at the top of the editor to see the character walking in place.

    Character AnimationClip playing in the editor

Light the character

  1. Add the Light2DReceiver component to Role, and set its ReceiverLayer to LAYER1.

  2. Enable EnableNormalMap for Role and assign the corresponding normal map texture.

    Character with a normal map receiving Light2D illumination

Make the character move

Add a movement script component, Role.ts, to the Role character so it moves to the right for a certain distance, then turns around and moves back, looping forever.

Role.ts
import { _decorator, Component, tween, Vec3 } from 'cc';
const { ccclass } = _decorator;

@ccclass('role')
export class role extends Component {
  start() {
    tween(this.node)
      .by(16, { position: new Vec3(3400, 0, 0) })
      .call(() => {
        this.node.setScale(this.node.scale.x * -1, this.node.scale.y);
      })
      .by(16, { position: new Vec3(-3400, 0, 0) })
      .call(() => {
        this.node.setScale(this.node.scale.x * -1, this.node.scale.y);
      })
      .union()
      .repeatForever()
      .start();
  }
}

Add a follow script component, FollowForever.ts, to the Camera node, and set Target Node to the Role node.

FollowForever.ts
import { _decorator, Component, Node, Vec3 } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('FollowForever')
export class FollowForever extends Component {
  @property(Node)
  public targetNode: Node = null;

  @property({})
  public xFollow = true;

  @property({})
  public yFollow = true;

  private _calcPos: Vec3 = new Vec3();

  protected onEnable(): void {}

  protected update(dt: number): void {
    if (this.targetNode) {
      const targetPos = this.targetNode.worldPosition;
      const currentPos = this.node.worldPosition;
      Vec3.moveTowards(this._calcPos, currentPos, targetPos, 100);
      this.node.setWorldPosition(
        this.xFollow ? this._calcPos.x : currentPos.x,
        this.yFollow ? this._calcPos.y : currentPos.y,
        currentPos.z
      );
    }
  }
}

Add a rotation script component, RotateForever.ts, to the Light-Point node, and set Target Node to the Role node.

RotateForever.ts
import { _decorator, Component, Node, Vec3 } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('RotateForever')
export class RotateForever extends Component {
  @property(Node)
  public targetNode: Node = null;

  @property
  public degSpeed: number = 90;

  private radius = 0;
  private angle = 0;

  protected onEnable(): void {
    if (this.targetNode) {
      const targetPos = this.targetNode.worldPosition;
      const currentPos = this.node.worldPosition;
      const delta = Vec3.subtract(new Vec3(), targetPos, currentPos);
      this.radius = delta.length();
      this.angle = Math.atan2(delta.y, delta.x) * (180 / Math.PI) + 180;
    }
  }

  protected update(dt: number): void {
    if (this.targetNode) {
      this.angle = (this.angle + dt * this.degSpeed) % 360;

      const rad = this.angle * (Math.PI / 180);
      const x = this.radius * Math.cos(rad);
      const y = this.radius * Math.sin(rad);

      const targetPos = this.targetNode.worldPosition;
      this.node.setWorldPosition(targetPos.x + x, targetPos.y + y, 0);
    } else {
      this.node.angle = (this.node.angle + dt * this.degSpeed) % 360;
    }
  }
}
  • Adjust the light source parameters: set InnerRadius to 4, OuterRadius to 900, LightIntensity to 2, and Falloff to 1 so the light covers a larger area and the attenuation is more natural.

  • Add more background and foreground nodes to expand the scene.

    Expanded scene with more background and foreground elements

Run and preview

Click Run to see the vivid lighting scene.