cocos2d-x dynamic light tutorial

cocos2d-x dynamic light tutorial

What you are going to learn:

  • Create your normal maps + sprite sheets using SpriteIlluminator and TexturePacker.
  • Load animation frames from a sprite sheet
  • Load a normal map sprite sheet and add a light effect to your animation
Gamescene with 2d light effects using normal maps

What you are going to learn

  • Create your normal maps + sprite sheets using SpriteIlluminator and TexturePacker.
  • Load animation frames from a sprite sheet
  • Load a normal map sprite sheet and add a light 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 you simply

  • Drag + drop your sprites on the SpriteIlluminator window

  • Select all sprites

  • Apply Bevel and Emboss effects

  • Publish the normal map images to the sprite directory, using the _n suffix

Create the sprite sheets

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

  • Start TexturePacker and create a new cocos2d project

  • Drag + drop sprites and normal maps on the TexturePacker window

  • Enable the Pack with same layout option in the Normal Maps section

  • Publish the sprite sheets

Setup your Cococ2D-x project

To create a new Cocos2d-x project we can use the cocos command, with the option -l cpp we create a C++ "hello world" project.

~/Frameworks/cocos2d-x $ cocos new LightingDemo -l cpp
Running command: new
> Copy template into /Users/joachim/Frameworks/cocos2d-x/LightingDemo
> Copying cocos2d-x files...
> Rename project name from 'HelloCpp' to 'LightingDemo'
> Replace the project name from 'HelloCpp' to 'LightingDemo'
> Replace the project package name from 'org.cocos2dx.hellocpp' to 'org.cocos2dx.LightingDemo'
> Replace the mac bundle id from 'org.cocos2dx.hellocpp' to 'org.cocos2dx.LightingDemo'
> Replace the ios bundle id from 'org.cocos2dx.hellocpp' to 'org.cocos2dx.LightingDemo'
~/Frameworks/cocos2d-x $

Open the new project using Xcode or VisualStudio.

Load sprite sheet and add an animation

We can replace the init() method of HelloWorldScene.cpp by our own: First we have to load the sprite sheet data file (the one with the .plist extension) into the SpriteFrameCache . The texture image file is automatically loaded, its name is composed by replacing the .plist suffix with .png:

bool HelloWorld::init()
{
    if (!Layer::init())
        return false;

    auto spritecache = SpriteFrameCache::getInstance();
    spritecache->addSpriteFramesWithFile("spritesheet.plist");

Now we can fetch the individual sprite frames of our animation from the cache:

    Vector<SpriteFrame*> animFrames;
    char str[100];
    for(int i = 1; i <= 8; i++)
    {
        sprintf(str, "character/%02d.png", i);
        animFrames.pushBack(spritecache->getSpriteFrameByName(str));
    }

Finally we create a sprite, set the Animate action on it, and add the sprite to the scene:

    auto sprite = Sprite::createWithSpriteFrame(animFrames.front());

    Animation *animation = Animation::createWithSpriteFrames(animFrames, 1.0f/8);
    sprite->runAction(RepeatForever::create(Animate::create(animation)));
    sprite->setPosition(Director::getInstance()->getWinSize() / 2);

    addChild(sprite);

    return true;
}

If you start the demo application, the (still unlit) animation is played in the center of the screen.

Simple sprite sheet animation in cocos2d-x

Creating a point light effect using a fragment shader

The brightness of each sprite pixel depends on the angle between the light vector and the normal vector (stored in the normal map). This calculation is done by an OpenGL fragment shader:

  • PointLight.frag contains the shader code for a point light

  • LightEffect is a class which sets some uniforms (global OpenGL variables) on the shader

  • Effect is a base class for LightEffect

  • SpriteEffect is derived from Sprite , it provides a method to set an Effect on the sprite

You can clone all sources from GitHub.

Adding the light effect to the Sprite

First of all you have to modify the code from above, as we want to use an EffectSprite instead of a Sprite :

auto sprite = EffectSprite::createWithSpriteFrame(animFrames.front());

Now we create an instance of the LightEffect and configure some properties.

    _effect = LightEffect::create();
    _effect->retain();

    Vec3 lightPos(200, 200, 100);
    _effect->setLightPos(lightPos);
    _effect->setLightCutoffRadius(1000);
    _effect->setBrightness(2.0);

The LightEffect creates a point light. A point light is a light source that emits light from a single spot in all directions. Think of it as a candle, torch, light bulb.

The effect class lets you set several parameters for the effect:

  • setLightPos
    The position of the light in the scene. The light source can be placed in 3 dimensions. That means that you can place it between the player and the screen — to light the scene. The z-position can dramatically change the effect you get from the light effect.
  • setLightColor
    Sets the color of the light source
  • setBrightness
    Sets the brightness of the light: with a value of 1.0 and a white light color a rendered pixel will never get brighter than its original value in the texture. With brightness values >1 the pixels can get overexposed.
  • setLightCutoffRadius
    The radius at which the light source does not have any effect on the sprite.
  • setLightHalfRadius
    The radius at which the light's intensity decreases to 50%. The value range is [0 … 1], relative to the cut-off radius. A value of 0.5 will give you a soft light, a value of 1 a light with hard edges.
  • setAmbientLightColor
    Sets the color of the background light. This is a non-directional light which lights the sprite from all sides.
light cut off

The newly created LightEffect can be set on the EffectSprite . The sprite sheet with the _n suffix contains the normal maps for all sprites. It is important that this sheet is packed with the same layout as the sprites!

sprite->setEffect(_effect, "spritesheet_n.png");

After adding these few lines our application displays a lit animation, but we are not yet able to move the light position. We are going to change this in the next section.

Adding a light effect to the cocos2d-x animation

Adding a light bulb

Let's add a sprite symbolizing the light source:

    _lightSprite = Sprite::create("lightbulb.png");
    _lightSprite->setPosition(lightPos.x, lightPos.y);
    this->addChild(_lightSprite);

To allow the user to move around the light bulb we register some touch callbacks:

    auto listerner = EventListenerTouchAllAtOnce::create();
    listerner->onTouchesBegan = CC_CALLBACK_2(HelloWorld::handleTouches, this);
    listerner->onTouchesMoved = CC_CALLBACK_2(HelloWorld::handleTouches, this);
    listerner->onTouchesEnded = CC_CALLBACK_2(HelloWorld::handleTouches, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listerner, this);

The implementation of handleTouches() is quite simple, we update the position of the light bulb sprite, and tell the effect where our light is positioned now:

void HelloWorld::handleTouches(const std::vector<Touch *> &touches, cocos2d::Event *)
{
    for (auto &touch: touches)
    {
        Point pos = touch->getLocation();
        _lightSprite->setPosition(pos);
        _lightPos.set(pos.x, pos.y, _lightPos.z);

        _effect->setLightPos(_lightPos);
    }
}

In the demo project on GitHub we've also added some background images:

Simple sprite sheet animation in cocos2d-x