Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / iOS

Breakout

5.00/5 (12 votes)
26 Jul 2012BSD10 min read 43K   1.7K  
This article shows how to create Breakout game for iOS, Android and Bada with only one code.
Image 1

Contents   

Introduction 

Breakout is a well-known arcade game and the first time it was introduced was on May 13, 1976 by Atary Inc. Up to this year the game has many various versions for many various platforms. Last year Atari introduced the Breakout Boost game available for Apple mobile devices. However, now we bring you instructions how to create this game for currently most often used platforms: iOS, Android or Bada. 

The game overview   

The main point of the game is to shoot down all bricks distributed in five lines on the top of the screen with a ball. The ball gets some velocity at the beginning and then it is bounced by a paddle which moves at the bottom of the screen. To make the game more interesting this implementation of the game has four levels where bricks last longer as the players proceeds in the game. When the ball hits the first level brick it disappears. When it hits the second level brick it changes to first level brick etc.

Image 2Type 1 - When Brick is hit by the ball, it disappears
Image 3Type 2 - When Brick is hit by the ball, it is changed to Brick Type 1
Image 4Type 3 - When Brick is hit by the ball, it is changed to Brick Type 2
Image 5Type 4 - When Brick is hit by the ball, it is changed to Brick Type 3

System requirements

As I mentioned earlier our game runs on all commonly used platforms (According to the statistics from the G1 2012). To create the game, running on three platforms using only one code we use Moscrif SDK. The hardware requirements of Moscrif SDK are really small. It runs even on devices with only 420MHz processor. This means that our game will run on all commonly used devices with iOS, Android or Bada.

User interface & Graphics

The game takes place in space, with suitable animations (electric effect) which improves the user experience. The game consists of two parts the game part and menu. Both are created as a separate Moscrif's Scene class.

The menu enables the all needed operations like start new game, continue previously started game and quit the game. The quit button is not available in version for iOS because of Apple’s app policy.

The game scene has only one button which returns to the menu scene. The remaining components of the game are the bricks, ball and paddle.

Image: graphic proposal 

Image 6

Physical engine 

The ball in the game behaves according to physical laws. It gets some velocity and then it bounces from the paddle. Also a little gravity is applied in the game. To simulate this physical behavior we used box2d physical engine which is supported by Moscrif SDK. This engine can be seen also in other platforms like Nintendo DS or Wii.

World

The most important features of box2d physics engine are world and bodies. The world creates background for all bodies. The world can have gravity, which can be specified on both axis (x, y). The world has always width which is equivalent of 10 metres in real world. All objects in the world are scaled to ensure this width. The scale property says how many pixels are in one meter. The box2d also uses its own coordinates which start in the left bottom corner and are counted in meters.

Image 7

Fortunately, the Moscrif framework usually uses normal pixel coordinates and conversions are made automatically

Bodies 

The bodies represent all real objects / things which interact in the world. There are three types of bodies which behave differently. 

  • #static: Static body does not move under simulation and behaves as infinite mass. Static bodies can be moved manually by setPosition method. A static body has zero velocity and does not collide with other static or kinematic bodies. 
  • #kinematic: Kinematic body moves under simulation according to its velocity. Kinematic bodies do not respond to forces. They can be moved manually by the user, but normally a kinematic body is moved by its velocity which needs to be set. A kinematic body behaves as infinite mass, but it does not collide with other static or kinematic bodies.
  • #dynamic: Dynamic body is fully simulated. It can be moved manually by setPosition, but normally they move according to forces. A dynamic body can collide with all body types and always has finite, non-zero mass. If you try to set the mass of a dynamic body to zero, it will automatically acquire a mass of one kilogram. 

As you can see not every body collides with every other body. The next table shows which bodies collide together. 

body type#static#dynamic#kinematic
#static
#dynamic
#kinematic


collide together
do not collide together

The bodies have many properties which affect their physical behaviour. The three properties which directly affect the body behaviour are bounce, density and friction.

Density is a property which affects the body mass.

Friction is the force resisting the relative motion elements sliding against each other.

Bounce property affects the size of bounce. The value 0.0 means that the body will not bounce from other bodies. The value 1.0 means that the body will bounce with the same speed as it fell; however, there is a possibility to increase the bounce rate and the ball will bounce off with more velocity.

Development process  

The game is situated in game scene. The game scene is extended from PhysicsScene which creates the box2d world. If an instance of PhysicsScene is created by create function the world is created automatically, otherwise it has to be created manually in init function. In Moscrif, all members of box2d world class is mapped to PhysicsScene class. It means that the Scene behaves like box2d world.

Bricks 

The bricks are represented by Brick class. It is extended from PhysicsSprite class which means that they behave like box2d body. The brick’s body type is set to be static. This means that it interacts with other dynamic bodies (ball), but do not move according to the gravity. The brick has four states (levels). When the ball hits the brick the level is decreased. The all physical properties are set in init function. The body has zero friction and density. The density of static body is always zero (no matter what it is set to). The bounce property affects the bounce from the brick and It is set only to 0.1 because the ball bounce is large enought (1.0 – full bounce).

When the bricks are hit, the brick is downgraded. If the level of the brick is less than 1, the brick disappears. 

Example hit function. Decrease state or disable the brick

C++
function hit()
{
    if (this.disabled == false)
        // disable brick if tis state (level) is less then one
        if (this.state < 1) {
            this.disabled = true;
            this.scene.brickCount--;
            this.hide(this);
        // decrease the brick's state (level)
        } else {
            this.state -= 1;
        }
}

The bricks are shown and hidden with a smooth animation. It only changes the alpha level. The animation is created by an Animator class. The animator class implements all mathematical background of the animations. Using this class the animation does not have to have linear behaviour. According to the required behaviour the animator object calls call-back function specified with addSubject function. The call back function has only one parameter the state, which specifies the current position in the animation.

Moscrif offers many various behaviours, used in transition property. In the show function, is created animator which runs 500ms and uses transition identified by easyIn. It starts up slowly and then quickly speeds up at the end of the animation.

Example: show brick with smooth animation

C++
// animate brick wehn it is shown
function show(obj)
{
    // create animator
    var animator = new Animator({
        transition: Animator.Transition.easeIn,     // start up slowly and then quickly speed up at the end of the animation
        duration: 500,                              // length of animation in miliseconds
    });
    animator.addSubject(function(state) {           // state starts from 1.0 to 0.0
        obj._paint.alpha = 255-(state*255).toInteger();
    });
   animator.reverse();
}

However, the easyIn is not only one type of transition. Moscrif offers many other types. Their behaviour is shown in next three graphs.

Image: Animator transition types

Image 8
Image 9
Image 10

Disk 

The disk is used as a paddle which bounces the ball back to the top to hit the bricks. It is also created as a separate class extended from PhysicsSprite. It is type static, because it also does not move according to the worlds gravity and other forces. It is moved manually by setPosition function when user drags his or her finger on the screen. To make the game more interesting the disk is connected with the animation of electric sparks. It has only three frames, but the fps is only set to 10 because the electric spark usually takes a short time. Real sparks are not any smooth animations.

Image: Animation frames

Image 11

The frames are changed in regular time intervals using timer class. The frames are changed in _electricShock function which is recurrently called on timer’s tick event. Between two frames with the effect is gap 100ms. There is a longer time delay between the first frame and the last frame. 

Example: create electric animation

C++
// create electric animation
function _eletricShock(frame = 0)
{
    // change current frame
    this.frame = frame;
    frame++;
 
    // if animation is on the last frame move to the first
    if (frame == 3)
        frame = 0;
 
    this._timer = new Timer(100, false); // interval make no sense here
    if (frame != 1)
        this._timer.start(100);
    else
        this._timer.start(res.values.electricShockMin + rand(res.values.electricShockGap));
    this._timer.onTick = function(sender) {this super._eletricShock(frame)};
}

GameScene

All game elements are combined in GameScene, included the ball, bricks and disk borders. They are created in GameScene. The borders prevents the ball to leave the screen.

Example: create borders around screen 

C++
function _createMantinels()
{
    // Ground
    var (width, height) = (System.width, 1);
    this._ground = this.addPolygonBody(null, #static, 0.0, 0.0, 1.0, width, height); // density, friction, bounce
    this._ground.setPosition(System.width/2, System.height - (this._ground.height/2));
    
    // Left mantinel
    var (widthML, heightML) = (1, System.height);
    this._leftMantinel = this.addPolygonBody(null, #static, 0.0, 0.0, 1.0, widthML, heightML);
    this._leftMantinel.setPosition(this._leftMantinel.width/2, System.height/2);
    
    // Righ mantinel
    var (widthMR, heightMR) = (1, System.height);
    this._rightMantinel = this.addPolygonBody(null, #static, 0.0, 0.0, 1.0, widthMR, heightMR);
    this._rightMantinel.setPosition(System.width - (this._rightMantinel.width/2), System.height/2);
    
    // Top mantinel
    var (widthMT, heightMT) = (System.width, 1);
    this._topMantinel = this.addPolygonBody(null, #static, 0.0, 0.0, 1.0, widthMT, heightMT);
    this._topMantinel.setPosition(System.width/2, (this._topMantinel.height/2));
}

When two bodies collide together, two events occur. First event, is onBeginContact when contact starts and the second is onEndContact when contact ends. These events are mapped to _onBeginContactHandler and _onEndContactHandler functions.

In _onBeginContactHandler function checks to see if something hit the ground. If yes, it can be only the ball so destroy it. Because of onBeginContact event is called max once per time step also if more contacts appear it is needed to check all contacts in one function. The second object of both contact events is b2Contact object which caries information about all contacts. The body which interact together can be found by getBodyA and getBodyB functions. The b2Contact object is a list of all contacts. To move to the next contact the getNext function is used. The best way how to check all contacts is to do it in cycle.

Example: onBeginContact event handler

C++
function _onBeginContactHandler(sender, contact)
{
    // if the ball do not exists the contact is irrelevant - do nothing
    if (!this._ball) return;
    // get the first contact
    var current = contact;
    while (current) {
        // get the bodies in the contact
        var bodyA = current.getBodyA();
        var bodyB = current.getBodyB();
        // check if something hit the ground (it can be only ball)
        if (bodyA == this._ground || bodyB == this._ground)
            // destroy the ball
            this._bodiesToDestory.push(this._ball);
        // get the next contact (they can be more contacts during the one step)
        current = current.getNext();
    }
}

The _onEndContactHandler works similar but it checks if something hits the brick. If yes, it decreases the brick grade and plays a sound. 

Example: onEndContact event handler 

C++
function _onEndContactHandler(sender, contact)
{
    // if the ball do not exists the contact is irrelevant - do nothing
    if (!this._ball) return;
    var current = contact;
    // get the first contact
    while (current) {
        // get the bodies in the contact
        var bodyA = current.getBodyA();
        var bodyB = current.getBodyB();
        var existing = this._bricks.filter(:x { return x == bodyA; }); // lamba function, the same as ".filter(function(x) { return x == bodyA; })"
        if (existing.length != 0) {
            bodyA.hit();
            if (this._enableSounds) this._wavPaddle.play();
            return;
        }
        existing = this._bricks.filter(:x { return x == bodyB; });
        if (existing.length != 0) {
            bodyB.hit();
            if (this._enableSounds) this._wavPaddle.play();
            return;
        }
        if (this._enableSounds && (bodyA == this._paddle || bodyB == this._paddle))
            this._wavBall.play();
        // get next contact
        current = current.getNext();
    }
}

The bodies are not removed directly in call backs function. In box2d it is not allowed to remove or inactive the bodies in box2d call backs functions. Although this feature is mentioned also directly in box2d official documentation this problem is also frequently discussed on forums and other unofficial tutorials. To ensure a safe way of removing the bodies, they are only pushed to array. In process function, (which runs about every 25ms) are searched the array and removed all bodies from the world.

Example: call destroy body function from process event if it is needed to remove some bodies

C++
function process()
{
    var timeStep = 1.0 / 40.0;
    // recalculate physics world. All objects are moved about timeStep
    if (!this.paused)
        this.step(timeStep, 4, 8);
 
    // remove bricks from the world
    if (this._bodiesToDestory.length != 0)
        this._removeBricks();
 
    // inactive bricks in the world
    if (this._bodiesToInactive.length != 0)
        this._inactiveBricks();
 
    // if user finished the level move to the next level
    if (this.brickCount == 0 && this.paused == false){
        this._nextLevel();
        this.paused = true;
    }
}

Searching the array and removing the bricks are made in separate function

Example: remove brick from scene

C++
function _removeBricks()
{
    // Remove touched bricks
    for(var body in this._bodiesToDestory) {
        var existing = this._bricks.filter(:x { return x == body; });
        if (existing.length != 0)
            this._bricks.removeByValue(body);
            // GAME OVER
             if (this._ball == body) {
                this._gameStarted = false;
                this._createBall();
                this._paddle.setPosition( this._paddleX, this._paddleY);
            }
            this.destroyBody(body);
    }
    // zero the array
    this._bodiesToDestory = [];
}

Menu 

Game also has easy menu, which is created as separate scene. It only contains three buttons: play continue and quit. The quit button is not on the iOS version, because Apple’s application policy does not allow it. All buttons are created by separate function.

Example: create play button

C++
function _createPlayButton(top)
{
    // create new instanco of GameButton with images from the resources
    var button = new ImageButton({image: res.img.playButton, x:System.width/2, y: top, frameWidth: res.img.playButton.width, frameHeight: res.img.playButton.height / 2});
    // set onClick event - start new game
    button.onClick = function(s)
    {
        // create new game scene
        game.game = GameScene.create(0.0, -0.5, { calledClass: GameScene } );
        // initialize new game
        game.game.removeAllBricks();
        game.game.brickCount = 0;
        game.game.paused = true;
        game.game.visible = true;
        game.game.level = 0;
        game.game.start();
        game.push(game.game, new SlideToBottom());
    }
    return button;
}

When application starts the buttons are shown with amazing push animation. To achieve a required effect is needed to combine more animators. It is needed to change alpha level and also scale. To combine more animator AnimatorChain is used.

Example: create pulse animation

C++
function _pulseAnimation(obj)           // fade in then pulse
{
    obj.alpha = 1;                      // start form invisible state
    var fade = Transition.to(obj, {
        transition  : Animator.Transition.easeIn,
        duration    : 400,
        alpha       : 255,               // transparency - custom attribute
    }, false);                           // false = don't play autimatically
    var scale1 = Transition.to(obj, {
        transition  : Animator.Transition.easeInOut,
        duration    : 150,
        scale       : 0.8,              // smothly resize the object
    }, false);
    var scale2 = Transition.to(obj, {
        transition  : Animator.Transition.easeInOut,
        duration    : 200,
        scale       : 1.3,              // smothly resize the object
    }, false);
    var scale3 = Transition.to(obj, {
        transition  : Animator.Transition.easeInOut,
        duration    : 100,
        scale       : 1.0              // smothly resize the object
    }, false);
    // play all animations gradually
    new AnimatorChain([fade, scale1, scale2, scale3]).play();
}

Summary 

Now it is the time to port the game to mobile platforms. This article describes how to create it in Moscrif SDK. Using this Moscirf SDK saves a lot of time because it needs only one code for all platforms. This game is suitable for a large number of device because it supports iOS, Android and Bada smartphones and also tablets.

More resources 

More samples, demos and information about Moscrif can be found on www.moscrif.com.

License

This article, along with any associated source code and files, is licensed under The BSD License