Cocos2d-JS Tutorial – Chalk Board 2 – Erasing

Objective

In Continuation of our last Chalk Board drawing post, let’s see how to erase the board in this post.

ENGINE VERSION : COCOS2D-JS 3.8.1

Preview

Contents

  1. Prerequisite
  2. Download
  3. Chalk back to Position
  4. Draw / Erase Mode
  5. Touch Eraser Detection
  6. Erasing Magic
  7. 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
  • Read Previous post on Chalk Board Drawing.

See this link to get started with Cocos2d-x.

2. Download Code

Download project @ https://github.com/GethuGames/cocos2dx-chalk-board-draw/archive/erase1.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.

3. Chalk back to Position

When you are erasing, you dont want the chalk to be in the middle of the board and overlap with the Eraser. So let’s create a action that will move the chalk to the bottom of the move after drawing (on touches ended).

// inside `onTouchesEnded` of Board.js
slf.chalkSprite.runAction(cc.moveTo(0.2, cc.p(size.width * 0.7, slf.chalkSprite.getContentSize().height * 0.8)).easing(cc.easeIn(1.0)));

Add that code in `onTouchesEnded`, so as to drop the chalk to bottom after each draw.

4. Draw / Erase Mode

As you have seen in the video above, we should be able to drag the Eraser to clear the board. So to separate erasing and drawing in the common action of dragging, lets define a variable `mode`.

     mode : 0,
  1. When mode is 0, our Canvas will be in drawing mode and
  2. When it is 1, it will be in Erasing mode.
  3. When the touch begins on Eraser, we set the mode to 1 and drag the eraser instead of chalk.
  4. Touching anywhere else other than Eraser, will set the mode to 0 and start drawing.

5. Touch Eraser Detection

Now in `touchesBegan`, we need to identify If the touch is made on Eraser and set the mode accordingly.

// This code goes in `onTouchesBegan` function of Board.js
        slf.mode                =   0;

        dist                    =   Math.round(cc.pDistance(pos, slf.duster.getPosition()));
        if (dist < slf.duster.getContentSize().width) {
            slf.mode            =   1;
        }

Above is a very simple Hit Test Logic.

  • Set the mode to 0 (Draw) by default.
  • Then find the distance between touch point (pos) and eraser.
  • If the distance is less than the size of the eraser itself, then set the mode to `1`, since the touch is made on or very close to the Eraser.

Also introduce a `if` condition inside the `for` loop of `onTouchesMoved`:

        for (var i = 0; i < dist; i += 5) {
            var cPos            =   cc.pLerp(slf.prevPoint, pos, i/dist);
            if (slf.mode == 0) {
                slf.drawBrushAtPoint(cPos, cc.color(240, 240, 240, 230));
            } else if (slf.mode == 1) {
                // Erase Code to be updated
            }
        }

Now, drawing lines will be called only when the mode is `0`. Try running the code and see what happens when you touch on Eraser and drag across the board. You will see no lines.

All pieces are now set. Let’s see how to do the actual erasing Magic.

6. Erasing Magic

Now close the function `drawBrushAtPoint()` to `eraseAtPoint`. All code will be similar to drawing, except for just one line, which is,

    this.chalkBrush.setBlendFunc( cc.ZERO, cc.ONE_MINUS_SRC_ALPHA );

Setting the blend function of the sprite to ( cc.ZERO, cc.ONE_MINUS_SRC_ALPHA ) will erase rather than drawing. That’s it. Very very simple. Here is the full code of `eraseAtPoint`.

    eraseAtPoint                :   function(pt) {
        this.renderTex.begin();
        this.chalkBrush         =   cc.Sprite.create(res.chalkBrush_png);
        this.chalkBrush.setRotation(Math.random() * 180);
        this.chalkBrush.setPosition(pt);
        this.chalkBrush.setScale(10.0);
        this.chalkBrush.setOpacity(230);
        this.chalkBrush.setBlendFunc(  cc.ZERO, cc.ONE_MINUS_SRC_ALPHA );
        this.chalkBrush.visit();
        this.renderTex.end();

        this.duster.setPosition(pt);
    }

See this is much similar to Drawing code, except for setting the Blend function. With different blend functions, some super awesome visuals can be created.

Erase Line

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

    mode                        :   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(size.width * 0.7, this.chalkSprite.getContentSize().height * 0.8));
        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();
        console.log('Began : ' + JSON.stringify(pos));
        
        slf.prevPoint           =   pos;
        slf.mode                =   0;

        /*
        var dist                =   Math.round(cc.pDistance(pos, slf.chalkSprite.getPosition()));
        if (dist < slf.chalkSprite.getContentSize().width) {
            slf.mode            =   0;
        }
        */

        dist                    =   Math.round(cc.pDistance(pos, slf.duster.getPosition()));
        if (dist < slf.duster.getContentSize().width) {
            slf.mode            =   1;
        }

        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);
            if (slf.mode == 0) {
                slf.drawBrushAtPoint(cPos, cc.color(240, 240, 240, 230));
            } else if (slf.mode == 1) {
                slf.eraseAtPoint(cPos);
            }
        }

        slf.prevPoint           =   pos;
    },

    onTouchesEnded              :   function(touches, event) {
        var slf                 =   event.getCurrentTarget();
        var pos                 =   touches[0].getLocation();
        var size                =   cc.winSize;

        console.log('End : ' + JSON.stringify(pos));

        if (slf.mode == 0) {
            slf.chalkSprite.runAction(cc.moveTo(0.2, cc.p(size.width * 0.7, slf.chalkSprite.getContentSize().height * 0.8)).easing(cc.easeIn(1.0)));
        } else if (slf.mode == 1) {
            slf.duster.runAction(cc.moveTo(0.2, cc.p(slf.duster.getContentSize().width * 0.5, slf.duster.getContentSize().height * 0.8)).easing(cc.easeIn(1.0)));
        }
                //this.duster.runAction(cc.moveTo(0.1, this.erasePoint).easing(cc.easeIn(1.0)));

    },

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

    },

    eraseAtPoint                :   function(pt) {

        this.renderTex.begin();
        this.chalkBrush         =   cc.Sprite.create(res.chalkBrush_png);
        this.chalkBrush.setRotation(Math.random() * 180);
        this.chalkBrush.setPosition(pt);
        this.chalkBrush.setScale(10.0);
        this.chalkBrush.setOpacity(230);
        this.chalkBrush.setBlendFunc(  cc.ZERO, cc.ONE_MINUS_SRC_ALPHA );
        this.chalkBrush.visit();
        this.renderTex.end();

        this.duster.setPosition(pt);

    }

});

Cocos2d-JS Tutorial – Chalk Board 1 – Drawing and Animations

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

}

});

Cocos2d-x Cross Platform Video Player – Part 2 Android

In the last post, we saw how to create a skeleton to handle multiple platforms with the `WrapperClass` and how to implement video player in iOS. Please go through the first post, to get a good understanding of this post.

Download

Download project @ https://github.com/saiy2k/cocos2dx-videoplayer-android/archive/master.zip

Complete Project can be found in GITHUB.

1. Project Creation

For setting up the project, refer this page. Create a project named `VideoPlayer` and come back here once you got the `Hello World` running in Android.

2. Copy Wrapper Classes from previous Tutorial

Copy the WrapperClass(.h, .cpp), AudioManager(.h, .cpp), Config(.h, .cpp), AppDelegate(.h, .cpp) and HelloWorldScene(.h, .cpp) files to the newly created Android project. If all configurations are done right and the old CPP files are copied in right way, running the project should show `Click to play Video` in the center of the screen. But one catch, it will show a Compilation error, since the `CCApplication` class wont be having `playVideo()` function, which is called from the `WrapperClass`. This line can be commented out temporarily to check if everything is configured right and if the label `Click to play Video` is being rendered.
Cocos2dx Cross Platform Video Player Screenshot

3. Passing control from C++ to Java

We already have the touch handler in HelloWorldScene, which calls on `WrapperClass->playVideo()), when a touch is made.

Branching to Android specific code lies in `WrapperClass`, which calls `playVideo()` function of `CCApplication`. So lets add that function in `CCApplication`(.h, .cpp). The corresponding file will be located @ $COCOS2DX_HOME/cocos2dx/platform/android/CCApplication.h/cpp, where $COCOS2DX_HOME is the path where you installed coos2d-x files.

Add the following code to that class:

void CCApplication::playVideo(){
    JniMethodInfo minfo;
    if(JniHelper::getStaticMethodInfo(minfo,
                                      "in/gethugames/VideoPlayer",
                                      "playVideo",
                                      "(Ljava/lang/String;)V")) {
        jstring StringArg1 = minfo.env->NewStringUTF("sample.mov");
        minfo.env->CallStaticVoidMethod(minfo.classID, minfo.methodID, StringArg1);
        minfo.env->DeleteLocalRef(StringArg1);
        minfo.env->DeleteLocalRef(minfo.classID);
    }
}

The `getStaticMethodInfo` function of `JniHelper` defines the static JAVA function, which needs to be invoked. 2nd parameters defines a static Java function named playVideo, with first parameter stating the class path in.gethugames.VideoPlayer. And the static JAVA function is expected to take a string parameter, which is defined by the cryptic third parameter. The function `CallStaticVoidMethod` makes the actual function call to JAVA function.

For now, just add an empty function in the JAVA activity class `VideoPlayer` (.java)

public static void playVideo(final String msg) {
    System.out.println("Play Video in JAVA class in invoked");
}

Build and tun the project now and if everything is done right, when the `MenuItemLabel` is touched, the above line should get logged in LogCat. Wow, we passed the control from CPP to JAVA after much effort. Now is the time to play the video.

4. Invoking a separate Video Activity

I am not an android guy and there may be better solutions to play video than what I write here. Please comment, if any other easy / better methods are there.

Add the following code to the VideoPlayer class:

// in VideoPlayer class
public static VideoPlayer selfActivity = null;

// inside onCreate()
selfActivity = this;

// in playVideo() function
Intent i = new Intent();
i.setClass(selfActivity, Cocos2dxVideo.class);
selfActivity.startActivity(i);

`selfActivity is a object reference to the the VideoPlayer object. It is assigned in onCreate() and in `playVideo()`, an Intent is created to switch to another activity, named `Cocos2dxVideo`. This new activity will play a video and when touched in middle, will close itself and also notifies our CPP part of the project.

5. Cocos2dxVideo

Create a layout file `activity_cocos2dx_video.xml` in the res/layout folder with the following contents:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/relative"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="2dp"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:paddingTop="2dp"
tools:context=".Cocos2dxVideo" >

    <VideoView
    android:id="@+id/videoPlayer"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true" />

</RelativeLayout>

Above code defines a standard VideoView control that is used to play video in Android. Then add the following code in `Cocos2dxVideo` (.java) class, which gets a reference to the VideoView defined in the layout above, attach the video in `res/raw` and then plays it. Now register the Activity in `AndroidManifest.xml` as follows:

<activity android:name="in.gethugames.Cocos2dxVideo"
android:screenOrientation="landscape"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
</activity>

Two points to note here:

1. I didn’t passed the video path from the main activity (VideoPlayer) to here, but instead directly plays the video in `res/raw` folder. (will try and update post, when I get time)

2. The video shouldn’t be in the standard resources folder where sprites and other assets are copied. That folder is for cocos2d-x, but since we play the video from JAVA land, the video should be in `res/raw` folder and its suggested that only lowercase should be used in naming the video file.

        // in Cocos2dxVideo class
        private Cocos2dxVideo me;
        public native int onOver();

        // inside onCreate function of Cocos2dxVideo class
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cocos2dx_video);
        me = this;
        VideoView vv = (VideoView) findViewById(R.id.videoPlayer);
        vv.setMediaController(null);
        vv.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.samplevideo));
        vv.start();
        vv.requestFocus();

If the project is run, clicking on the menu item label should invoke the video. Happy? wait, its not over yet. The activity should be closed either after video playback, or if user touches the screen. Add the following event listeners to do the same:

        // invoked when the video playback is complete
        vv.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
            	me.onOver();
                me.finish();
            };
        });

        // invoked when the video view is touched
        vv.setOnTouchListener(new View.OnTouchListener()
        {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
            	me.onOver();
            	me.finish();
                return false;
            }
        });

Both the listeners does the some thing, calls the native function `onOver() and end the activity by calling, `finish()`. This resumes the previous activity, which the our `VideoPlayer`. When the video started playing, the in-game music is paused. So, we need to resume it, when the video is done. That’s the reason for calling the onOver().

6. JAVA to CPP – Video Completion notification

The function onOver() is marked with `native` keyword to denote its real implementation is in CPP and control should be transferred. JAVA to CPP is very simple. Just name a function in CPP in the following format to achieve it:

return-type Java_[CLASSPATH]_[FUNCTION_NAME](JNIEnv *env, jobject thiz) { }

So, add the following code anywhere within a extern “C” {} block, I added in jni/hellocpp/main.cpp file.

        void Java_in_gethugames_Cocos2dxVideo_onOver(JNIEnv *env, jobject thiz) {
	CCLog("CPP Function called");
	AudioManager::sharedManager()->playBG();
}

Hence the loop is complete and thus the game sound track gets played after the video is over.

Thats the end of the Tutorial !!!

Download

Download project @ https://github.com/saiy2k/cocos2dx-videoplayer-android/archive/master.zip

Complete Project can be found in GITHUB.