Cocos2D for the Web

Breakout Clone Tutorial : Part 3

NOTE: This tutorial is for v0.1
Full v0.2 source code available on github

Introduction

In this part we’ll be creating the ball that bounces around the level. A lot of the initial code is the same as the Bat so I won’t go into too much detail there.

Creating The Ball

Just like the Bat, the ball will live in its own module. Create a new file named Ball.js and at the top import the cocos and geometry modules.

var cocos = require('cocos2d');
var geom = require('geometry');

Also like the Bat, the ball will be a subclass of cocos.nodes.Nodes.

var Ball = cocos.nodes.Node.extend({
    init: function() {
        Ball.superclass.init.call(this);
    }
});

Add a sprite to the ball in the same way we did with the Bat. In the init method add:

var sprite = cocos.nodes.Sprite.create({
    file: '/resources/sprites.png',
    rect: new geom.Rect(64, 0, 16, 16)
});
 
sprite.set('anchorPoint', new geom.Point(0, 0));
this.addChild({child: sprite});
this.set('contentSize', sprite.get('contentSize'));

We’ll need to access the ball from the main module so at the end of Ball.js add:

exports.Ball = Ball;

Now back to main.js and import the Ball by adding this to the top of the file:

var Ball = require('Ball').Ball;

We need another property to assign the ball to. Underneath the bat property add:

ball: null,

And in the Breakout layer’s init method add the ball to the game.

var ball = Ball.create();
ball.set('position', new geom.Point(160, 250));
this.addChild({child: ball});
this.set('ball', ball);

If you try the application now you will see the ball frozen

Ball Movement

We need the ball to fly through the air. We’ll represent its speed using a vector which is really just an instance of geometry.Point with its X and Y values being the speed it’ll move on that axis.

Back to the Ball.js file and add a velocity property to the Ball class.

bc(language.javascript). velocity: null,

Then in the Ball’s init method add a default velocity. The X and Y values will be the number of pixels to move per second.

this.set('velocity', new geom.Point(60, 120));

Now that we know how fast the ball should be moving we’ll update the Ball so it actually does move. All nodes have a method named scheduleUpdate, which when called starts a time that calls an update method on the node for every single frame.

Lets start this timer by adding this line to the end of the init method.

this.scheduleUpdate();

And create the method that it calls, after @init@’s closing brace add a comma and add an update method which accepts parameter.

update: function(dt) {
}

The dt parameter stands for delta time. This is the number of seconds since the last time the method was called. You will use this to calculate how far the ball should move this frame. By using a delta time rather than simply moving a fixed number of pixels each frame, we’re able to make the game frame-rate independent. This means that the game will run at the same speed regardless of the frame-rate.

For the next part we’ll need a function from the util module. Import that at the top of Ball.js

var util = require('util');

Back to the newly created update method. Here we’ll be adjusting the balls position and velocity. We want to work with copies of its current speed and velocity. Luckily the util module has a copy method which does a deep copy of objects.

At the start of the update grab copies of the position and velocity.

var pos = util.copy(this.get('position')),
    vel = util.copy(this.get('velocity'));

Now to update the position of the ball based on its current velocity. Simply muliply the velocity by the delta time and add it to the position.

pos.x += dt * vel.x;
pos.y += dt * vel.y;

With that done, update the position of the ball.

this.set('position', pos);

Reload the application and the ball will move diagonally through the bat then disappear off the screen.

Your Bat.js file should look like this now:

var cocos = require('cocos2d');
var geom = require('geometry');
var util = require('util');
 
var Ball = cocos.nodes.Node.extend({
    velocity: null,
 
    init: function() {
        Ball.superclass.init.call(this);
 
        var sprite = cocos.nodes.Sprite.create({
                         file: '/resources/sprites.png',
                         rect: new geom.Rect(64, 0, 16, 16)
                     });
 
        sprite.set('anchorPoint', new geom.Point(0, 0));
        this.addChild({child: sprite});
        this.set('contentSize', sprite.get('contentSize'));
 
        this.set('velocity', new geom.Point(60, 120));
        this.scheduleUpdate();
    },
 
    update: function(dt) {
        var pos = util.copy(this.get('position')),
            vel = util.copy(this.get('velocity'));
 
        pos.x += dt * vel.x;
        pos.y += dt * vel.y;
 
        this.set('position', pos);
    }
});
 
exports.Ball = Ball;

Collision With Bat

Our next task is to make the ball bounce off the bat. We’ll be using a very basic rectangle collision detection technique here to keep the tutorial simple.

Create a new empty method on Ball named testBatCollision.

testBatCollision: function() {
}

And call it from very end of update.

this.testBatCollision();

To aid us in the collision detection we’ll be using a property available on all Nodes called boundingBox. This property is an instance of geometry.Rect. The origin is the top left corner of our Node and the size is the size of our Node. We’ll simply check if the boundingBox of the Bat and Ball overlap and if they do we’ll change the direction of the Ball.

testBatCollision: function() {
    var vel = util.copy(this.get('velocity')),
        ballBox = this.get('boundingBox'),
 
        // The parent of the ball is the Breakout Layer, which has a 'bat'
        // property pointing to the player's bat.
        batBox = this.get('parent').get('bat').get('boundingBox');
 
    // If moving down then check for collision with the bat
    if (vel.y > 0) {
        if (geom.rectOverlapsRect(ballBox, batBox)) {
            // Flip Y velocity
            vel.y *= -1;
        }
    }
 
    // Update position and velocity on the ball
    this.set('velocity', vel);
}

Note how we get access to the Bat via the Ball’s parent, which in this case is our Breakout layer in main.js. We also check if the ball is moving down the screen before checking for a collision. This prevents the Ball going crazy if it overlaps the Bat for more than one frame. Then we call a helper function from the geometry module to test if the bounding boxes overlap.

Reload the application and the ball will bounce of the bat then fly off the top of the screen.

Collision with edges

We have the ball bounding off the bat, but we also need it to bounce of the edges of the screen.

Create a new method on Ball called testEdgeCollision and call it from the end of update().

this.testBatCollision();
this.testEdgeCollision();

This is a fairly simple method. All it’s doing is checking if the ball has hit the left, right or top edge and flips the velocity in the opposite direction if it has.

var vel = util.copy(this.get('velocity')),
    ballBox = this.get('boundingBox'),
    // Get size of canvas
    winSize = cocos.Director.get('sharedDirector').get('winSize');
 
// Moving left and hit left edge
if (vel.x < 0 && geom.rectGetMinX(ballBox) < 0) {
    // Flip X velocity
    vel.x *= -1;
}
 
// Moving right and hit right edge
if (vel.x > 0 && geom.rectGetMaxX(ballBox) > winSize.width) {
    // Flip X velocity
    vel.x *= -1;
}
 
// Moving up and hit top edge
if (vel.y < 0 && geom.rectGetMinY(ballBox) < 0) {
    // Flip Y velocity
    vel.y *= -1;
}
 
this.set('velocity', vel);

The first new bit of code here is getting winSize from the Director. All this property represents is the size of the canvas in pixels. We need to know this to work out where the edges are.

The other bit of new code is the call to rectGetMinX, rectGetMaxX and rectGetMinY. What this group of functions does is get the X or Y pixel value of the edge of a rectangle. e.g. rectGetMaxX(rect) is effectivly the same as rect.origin.x + rect.size.width.

Reload the application and the ball will bounce of the left, right and top edges and you can start batting it around.

Posted

Comments

Alberto César Rodríguez

ago, Alberto César Rodríguez said

I just wanted to point out that in the file main.js this line:
<pre class="language-javascript">ball.set('position', new geom.Point(160, 250));</pre>
should be:
<pre class="language-javascript">ball.set('position', new geo.Point(160, 250));</pre>

Victor

ago, Victor said

You can change it in the Ball.js too.

It works because every line instances “geom”, but if you call it geo in the other js files I think that would be better to keep the same name.

Bernard

ago, Bernard said

I am using the “Create Project” shortcut to create this Breakout tutorial.
Whenever I run the code, I get an error in JS and have tracked it down to the main.js code generated by the Create Project wizard. This is the code it creates:

// Import the cocos2d module
var cocos = require(‘cocos2d’),
// Import the geometry module
geo = require(‘geometry’);
……

Basically there is a vsr before the cocos and no var before the geo.

If I remove the “var” before the :

var cocos = require(‘cocos2d’),

so that it is now:

cocos = require(‘cocos2d’),

the code runs without error. I am quite the newbie to this so can you please explain why this is happening?

How come this line works with the var:

var Bat = require(‘Bat’).Bat,

but using var here gives an error:
var cocos = require(‘cocos2d’),

and if it does not work with var, then could you please correct the wizard so that the correct code is generated?

Ryan

ago, Ryan said

Hi Bernard,

The trick here is the use of a comma rather than a semicolon. You can declare multiple variables with a single var by using a comma. e.g.

var a = 1, b = 2, c = 3;

This is exactly what’s happening but there are comments and new lines too. If you prefer you can declare each one separately by using a semicolon and it’ll work just the same:

// Import the cocos2d module
var cocos = require('cocos2d');
// Import the geometry module
var geo = require('geometry');

I hope that explains it for you.

Ryan

Felipe Pérez Granda

ago, Felipe Pérez Granda said

Hi

It’s run !!

I worked !

Yemi Bedu

ago, Yemi Bedu said

Hello,
I understand why you use the comma, but there is so much information with the comments and newline. It would be better to have the default be the way you referenced in your comment above. Plus when we add to the requires, we will be following this pattern and not doing the shortcut form most likely. Probably for your future releases that would be worth a try.
Other than that, great release and great tutorial. Thank you. Good day.

breghuna

ago, breghuna said

Hi Ryan, it worked. Great work! I am trying to load it through apache and it fails find the <myproject>.js file. How do I package it to put it in the apache directory?
Thanks in advance.

breghuna

ago, breghuna said

Well, I have just figured out how to set up the sample to work with apache. don’t need to answer my previous post

Rushme

ago, Rushme said

Hi in the source code of the game posted in github it uses a TMXTiledMap.create() to create map. will you please explain how it works.

Angelo

ago, Angelo said

Hello guys,
I am an artist and I am looking to hire an HTML5 canvas developer to create experimental games.
Please contact me if you are interested
Angelo Plessas
www.angeloplessas.com

Anjali

ago, Anjali said

Hi,
Is there any property as isTouchenabled in cocos-js too?
Can we create something to run on a tab or a mobile ?

Add Comment

If you want to add a comment, then simply fill in the form below. All fields are required.