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.
| Type 1 - When Brick is hit by the ball, it disappears |
| Type 2 - When Brick is hit by the ball, it is changed to Brick Type 1 |
| Type 3 - When Brick is hit by the ball, it is changed to Brick Type 2 |
| Type 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
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.
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
function hit()
{
if (this.disabled == false)
if (this.state < 1) {
this.disabled = true;
this.scene.brickCount--;
this.hide(this);
} 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
function show(obj)
{
var animator = new Animator({
transition: Animator.Transition.easeIn, duration: 500, });
animator.addSubject(function(state) { 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
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
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
function _eletricShock(frame = 0)
{
this.frame = frame;
frame++;
if (frame == 3)
frame = 0;
this._timer = new Timer(100, false); 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
function _createMantinels()
{
var (width, height) = (System.width, 1);
this._ground = this.addPolygonBody(null, #static, 0.0, 0.0, 1.0, width, height); this._ground.setPosition(System.width/2, System.height - (this._ground.height/2));
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);
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);
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
function _onBeginContactHandler(sender, contact)
{
if (!this._ball) return;
var current = contact;
while (current) {
var bodyA = current.getBodyA();
var bodyB = current.getBodyB();
if (bodyA == this._ground || bodyB == this._ground)
this._bodiesToDestory.push(this._ball);
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
function _onEndContactHandler(sender, contact)
{
if (!this._ball) return;
var current = contact;
while (current) {
var bodyA = current.getBodyA();
var bodyB = current.getBodyB();
var existing = this._bricks.filter(: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();
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
function process()
{
var timeStep = 1.0 / 40.0;
if (!this.paused)
this.step(timeStep, 4, 8);
if (this._bodiesToDestory.length != 0)
this._removeBricks();
if (this._bodiesToInactive.length != 0)
this._inactiveBricks();
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
function _removeBricks()
{
for(var body in this._bodiesToDestory) {
var existing = this._bricks.filter(:x { return x == body; });
if (existing.length != 0)
this._bricks.removeByValue(body);
if (this._ball == body) {
this._gameStarted = false;
this._createBall();
this._paddle.setPosition( this._paddleX, this._paddleY);
}
this.destroyBody(body);
}
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
function _createPlayButton(top)
{
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});
button.onClick = function(s)
{
game.game = GameScene.create(0.0, -0.5, { calledClass: GameScene } );
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
function _pulseAnimation(obj) {
obj.alpha = 1; var fade = Transition.to(obj, {
transition : Animator.Transition.easeIn,
duration : 400,
alpha : 255, }, false); var scale1 = Transition.to(obj, {
transition : Animator.Transition.easeInOut,
duration : 150,
scale : 0.8, }, false);
var scale2 = Transition.to(obj, {
transition : Animator.Transition.easeInOut,
duration : 200,
scale : 1.3, }, false);
var scale3 = Transition.to(obj, {
transition : Animator.Transition.easeInOut,
duration : 100,
scale : 1.0 }, false);
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.