A Comeback post after around 2 years 🙂

Objective

In this post, we will see how to create a Cool Chalk board drawing functionality in Cocos2d-JS with RenderTexture. Later we will also see how to erase the Board with the Eraser.

Engine Version : Cocos2d-JS 3.8.1

Preview

Contents

  1. Prerequisite
  2. Resources
  3. Download Code
  4. Setup the Chalk Board
  5. Setup Touch functions
  6. Basic Drawing (Dotted)
  7. Smooth Drawing
  8. Complete Board.js Code

At any point of time, refer to the source files Board.jsmain.js and resource.js for clarity

1. Prerequisite

  • Should have Cocos2d-x installed in system.
  • Should have basic working knowledge of Cocos2d-JS

See this link to get started with Cocos2d-x.

2. Resources

Download and Copy these to the `res/` folder.

3. Download Code

Download project @ https://github.com/GethuGames/cocos2dx-chalk-board-draw/archive/draw1.0.zip Complete Project can be found in GITHUB.

Note: This is not directly executable. This contains the source and resources excluding the cocos2dx bundle. So, create a new project, download this github and replace the files in ur new project with this github files to be able to run the project.

4. Setup the Chalk Board

  • Create a Scene `Board`
  • In the constructor of `Board` scene, add the sprites for Chalk Board, Chalk Piece and Eraser as follows:
// This code goes in `ctor` function of Board.js
var size = cc.winSize;

this.boardSprite = cc.Sprite.create(res.Board_BG);
this.boardSprite.setPosition(cc.p(size.width / 2, size.height / 2));
this.boardSprite.setScale(640 / this.boardSprite.getContentSize().width,
size.height / this.boardSprite.getContentSize().height);
this.addChild(this.boardSprite);

this.chalkSprite = cc.Sprite.create(res.chalk_png);
this.chalkSprite.setPosition(cc.p(100, 200));
this.addChild(this.chalkSprite, 2);

this.duster = cc.Sprite.create(res.duster_png);
this.duster.setPosition(cc.p(this.duster.getContentSize().width * 0.5, this.duster.getContentSize().height * 0.8));
this.duster.setScale(0.5);
this.duster.setRotation(30);
this.addChild(this.duster, 2);
  • Then add the `RenderTexture` which will act the real chalk board to draw with the following code.
// This code goes in `ctor` function of Board.js
this.renderTex     =   cc.RenderTexture.create(size.width, size.height);
this.renderTex.setPosition(cc.p(size.width / 2, size.height / 2));
this.addChild(this.renderTex);

Then update the `main.js` to run the `Board` scene:

// This code goes in `onStart` function of main.js
cc.view.setDesignResolutionSize(640, 960, cc.ResolutionPolicy.FIXED_WIDTH);

cc.director.runScene(new Board());

This should display the Chalk Board.

Board Setup

5. Setup Touch Functions

Again in the constructor create Event Listeners to capture touch Began, Move and End.

// This code goes in `ctor` function of Board.js
if( 'touches' in cc.sys.capabilities ) { 
    console.log('Can Touch');
    this._touchListener = cc.EventListener.create({
        event: cc.EventListener.TOUCH_ALL_AT_ONCE,
        onTouchesBegan: this.onTouchesBegan,
        onTouchesMoved: this.onTouchesMoved,
        onTouchesEnded: this.onTouchesEnded
    });
    cc.eventManager.addListener(this._touchListener, this);
} else {
    console.log('No Touch Capabs');
}

Now add touch handlers to the `Board` scene

onTouchesBegan:function(touches, event) {
    var pos                 =   touches[0].getLocation();
    console.log('Began : ' + JSON.stringify(pos));

    return                      true;
},

onTouchesMoved:function(touches, event) {
    var pos                 =   touches[0].getLocation();
    console.log('Move : ' + JSON.stringify(pos));
},

onTouchesEnded              :   function(touches, event) {
    var pos                 =   touches[0].getLocation();
    console.log('End : ' + JSON.stringify(pos));
}

If this is set right, the app should throw logs in the console on touch and drag.

Chalk Board with Console log

6. Basic Drawing (Dotted)

Let’s create a new function that will handle the actual drawing.

drawBrushAtPoint            :   function(pt, color) {
    this.renderTex.begin(); //a
    this.chalkBrush         =   cc.Sprite.create(res.chalkBrush_png); //b
    this.chalkBrush.setRotation(Math.random() * 180); //c
    this.chalkBrush.setPosition(pt); //d
    this.chalkBrush.setColor(color); //e
    this.chalkBrush.visit(); //f
    this.renderTex.end(); //g

    this.chalkSprite.setPosition(cc.p(pt.x - this.chalkSprite.getContentSize().width * 0.3,
        pt.y - this.chalkSprite.getContentSize().height * 0.2)); //h

}

This is where the magic happens. Let’s go through this code line by line.
a : Prepares the Render Texture for Drawing.
b : Creates the brush texture sprite.
c, d, e : Set’s the rotation (random), position (as given) and color(as given) of the texture.
f : Draws the brush texture in the render texture.
g : Closes the Render Texture
h : Set’s the Chalk Sprite’s position to given position

We will call this function from touchesMoved passing the current touch Position and the color of the chalk.

onTouchesMoved:function(touches, event) {
    var slf                 =   event.getCurrentTarget();
    var pos                 =   touches[0].getLocation();
    slf.drawBrushAtPoint(cPos, cc.color(240, 240, 240, 230));
},

Now, you should be able to draw in the canvas by dragging over it.

Preview:

Dotted Draw

But the chalk line is not continuous and when drag faster, it dots the board rather than drawing lines. It’s because the touchesMoved is invoked between distant points when you move faster. Like when you drag really faster, you are at (50, 50) in one cycle and in (100, 150) in the next. Thus drawing 2 dots, one at (50, 50) and other at (100, 150).

No worries, we can fix this with some techniques.

7. Smooth Drawing

To get the drawing smoother, we will interpolate points between two distant `touch move` points and draw there. To do this, update `touchesMoved` function with the following:

onTouchesMoved:function(touches, event) {
    var slf                 =   event.getCurrentTarget();
    var pos                 =   touches[0].getLocation();
    console.log('Move : ' + JSON.stringify(pos));

    var dist                =   Math.round(cc.pDistance(pos, slf.prevPoint)); //A

    for (var i = 0; i < dist; i += 5) { //B
        var cPos            =   cc.pLerp(slf.prevPoint, pos, i/dist);
        slf.drawBrushAtPoint(cPos, cc.color(240, 240, 240, 230));
    }

    slf.prevPoint           =   pos;
},
  • At the end of each `touchesMove` function, we store the point as `prevPoint`.
  • Then on next call of `touchesMove`, we get the distance between current Move position and previous move position (Line A)
  • We create a for loop that will advance 5 pixels from Zero to the distance between 2 points.
  • Inside the loop, we interpolate the corresponding point between the previous point and current points and draw the chalk brush there.
  • This way we will fill the gap between the dots and get a smooth Drawing..

Smooth Draw

Hope this Tutorial is useful 🙂

Next Tuturial will be on erasing the board. Subscribe to get Notified.

Happy Coding (y)

Complete Board.js Code

var Board = cc.Scene.extend({

    boardSprite                 :   null,

    renderTex                   :   null,

    chalkSprite                 :   null,

    chalkBrush                  :   null,

    prevPoint                   :   cc.p(0, 0),

    ctor                        :   function () {

        this._super();

        var slf                 =   this;
        var size                =   cc.winSize;
        
        if( 'touches' in cc.sys.capabilities ) { 
            console.log('Can Touch');
            this._touchListener = cc.EventListener.create({
                event: cc.EventListener.TOUCH_ALL_AT_ONCE,
                onTouchesBegan: this.onTouchesBegan,
                onTouchesMoved: this.onTouchesMoved,
                onTouchesEnded: this.onTouchesEnded
            });

            cc.eventManager.addListener(this._touchListener, this);
        } else {
            console.log('No Touch Capabs');
        }

        this.boardSprite        =   cc.Sprite.create(res.Board_BG);
        this.boardSprite.setPosition(cc.p(size.width / 2, size.height / 2));
        this.boardSprite.setScale(640 / this.boardSprite.getContentSize().width,
                size.height / this.boardSprite.getContentSize().height);
        this.addChild(this.boardSprite);

        this.renderTex     =   cc.RenderTexture.create(size.width, size.height);
        this.renderTex.setPosition(cc.p(size.width / 2, size.height / 2));
        this.addChild(this.renderTex);

        this.chalkSprite        =   cc.Sprite.create(res.chalk_png);
        this.chalkSprite.setPosition(cc.p(100, 200));
        this.addChild(this.chalkSprite, 2);

        this.duster             =   cc.Sprite.create(res.duster_png);
        this.duster.setPosition(cc.p(this.duster.getContentSize().width * 0.5, this.duster.getContentSize().height * 0.8));
        this.duster.setScale(0.5);
        this.duster.setRotation(30);
        this.addChild(this.duster, 2);
    }, 

    onTouchesBegan:function(touches, event) {
        var slf                 =   event.getCurrentTarget();
        var pos                 =   touches[0].getLocation();
        
        slf.prevPoint           =   pos;

        return                      true;
    },

    onTouchesMoved:function(touches, event) {
        var slf                 =   event.getCurrentTarget();
        var pos                 =   touches[0].getLocation();
        //console.log('Move : ' + JSON.stringify(pos));
        //console.log('PrevPoint : ' + JSON.stringify(slf.prevPoint));

        var dist                =   Math.round(cc.pDistance(pos, slf.prevPoint));
        //console.log(dist);

        for (var i = 0; i < dist; i += 5) {
            var cPos            =   cc.pLerp(slf.prevPoint, pos, i/dist);
            slf.drawBrushAtPoint(cPos, cc.color(240, 240, 240, 230));
        }

        slf.prevPoint           =   pos;
    },

    onTouchesEnded              :   function(touches, event) {
        var pos                 =   touches[0].getLocation();
        console.log('End : ' + JSON.stringify(pos));

    },

drawBrushAtPoint            :   function(pt, color) {

    this.renderTex.begin();
    this.chalkBrush         =   cc.Sprite.create(res.chalkBrush_png);
    this.chalkBrush.setRotation(Math.random() * 180);
    this.chalkBrush.setPosition(pt);
    this.chalkBrush.setColor(color);
    this.chalkBrush.setScale(1.5);
    this.chalkBrush.visit();
    this.renderTex.end();

    this.chalkSprite.setPosition(cc.p(pt.x - this.chalkSprite.getContentSize().width * 0.3,
        pt.y - this.chalkSprite.getContentSize().height * 0.2));

}

});