About the game
Our goal is to create a single player game playing against artificial intelligence. When the game starts, the playground shows up right away. Menu will be created as a dialog window with only three buttons: New game, Continue and Quit (Quit button will not be available on iOS).
Graphic design
Let’s start out development process by preparing the graphics. Our game is really simple so the graphics do not need to be too complicated either. They consist of the menu, playground backgrounds, buttons, paddles and the puck.
Image: graphic design

The current mobile market offers devices with many various resolutions. To ensure the best appearance on every device the graphics are created separately for all commonly used resolutions.
Physics engine
The puck in the game behaves according to physical laws. It bounces from the paddles and barriers, and it mowes with a small damping. 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. The chapters about box2d world and bodies are similar as in my some other articles. If you have read it, you can skip to Let’s start – start up file & resources chapter.
World
The world creates background for all bodies, joints or contacts. The world has always width which is equivalent of 10 metres in real world. All objects and things 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: box2d coordinates

Fortunately, Moscrif’s 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 PhysicsBody.setPosition. 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.
Let’s start – start up file & resources
Start up file
We are going to create our game in Moscrif IDE where we need to start a new project based on the game’s framework. By default, the start up file is main.ms. This file contains an instance of Game framework class, which is a base class for all game projects in Moscrif. The Moscrif‘s game framework also offers PhysicsScene class which creates physics world and combines all other physical and non-physical objects. PhysicsScene class is also used for creating a GameScene class, which puts playground into existence and its instance is created in onStart event of Game class which is called when the game starts.
Example: create a new instance of GameScene
game.onStart = function(sender)
{
if (res.supportedResolution) {
// push game scene to to application
this.gameScene = new GameScene();
this.push(this.gameScene);
// restart game (start new)
this.gameScene.reset();
}
this._paint = new Paint();
this._paint.textSize = System.height / 30;
var (w, h) = this._paint.measureText("Unsupported resolution");
this._textWidth = w;
}
In main.ms are also managed users‘ events like pointer or key pressed.
Example: manage user events
// quit game when user clicks on the back or home hardware button
game.onKeyPressed = function(sender, keyCode)
{
if (keyCode == #back || keyCode == #home)
game.quit();
}
game.onPointerPressed = function()
{
if (!res.supportedResolution)
game.quit();
} // quit game when user clicks on the back or home hardware button
game.onKeyPressed = function(sender, keyCode)
{
if (keyCode == #back || keyCode == #home)
game.quit();
}
game.onPointerPressed = function()
{
if (!res.supportedResolution)
game.quit();
}
Resources
As I wrote earlier, the graphics are made separately for more resolutions. However, all images are managed by Resource class which means that we do not need to worry about device resolutions in the next development process. An instance of the resource class is created as a second global variable in main.ms which also ensures that all images will be loaded only once and to save device’s memory.
Example: load resources in class constructor
function this() {
this._supportedResolution = true;
this._images = {
background : this._loadImage("backGame", "jpg");
playerHuman : this._loadImage("player2");
playerAI : this._loadImage("player1");
puck : this._loadImage("puck");
menuBg : this._loadImage("menuBack");
menuButton : this._loadImage("menuBtn");
menuButtonPressed : this._loadImage("menuBtnPress");
menuPart : this._loadImage("menuPart");
};
...
}
The _loadImage function loads images according to device resolution.
Example: load images according to device resolution
function _loadImage(filename, format = "png")
{
var file = "app://" + System.width + "_" + System.height + "/" + filename + "." + format;
var bitmap;
if (System.isFile(file)) {
bitmap = Bitmap.fromFile(file);
if (bitmap != null)
return bitmap;
}
// Kindle Fire 600x1002
if (System.width == 600) {
file = "app://" + System.width + "_1024" + "/" + filename + "." + format;
bitmap = Bitmap.fromFile(file);
if (bitmap != null)
return bitmap;
}
// Galaxy tab 800x1232 752x1280
if (System.width == 800) {
file = "app://" + System.width + "_1280" + "/" + filename + "." + format;
bitmap = Bitmap.fromFile(file);
if (bitmap != null)
return bitmap;
}
// SE xperia 480x854
if (System.width == 480) {
file = "app://" + System.width + "_800" + "/" + filename + "." + format;
bitmap = Bitmap.fromFile(file);
if (bitmap != null)
return bitmap;
}
this._supportedResolution = false;
return null;
}
Game scene
Now, let’s create the most important part of our game -> game scene. The game scene is the playground: table, barriers, goals, puck and paddles. The game scene is created by GameScene class extended from PhysicsScene, which creates the physics world. When framework classes are constructed, they call init (also beforeInit and afterInit) method. In our game, we are going to create the physics world for the scene, set events onBeginContact and onEndContact which are called when two bodies collide in the scene, and also create all other objects in init function. Other game elements like the puck, barriers, goals or paddles are created by separate functions like PhysicsSprite objects.
The game scene also draws table image and score. The image is resized to the full screen for cases when the Resource class cannot find images for current device resolution.
Example: draw background image and score
function draw(canvas)
{
canvas.drawBitmapRect(res.images.background, 0, 0, res.images.background.width, res.images.background.height, 0, 0, System.width, System.height);
// save current canvas setings
canvas.save();
// rotate canvas to 270° CW
canvas.rotate(270);
// draw score
canvas.drawText(this.playerAI.score.toString(), System.height / - 2 - 2 * this._scoreW, System.width / 16 + this._scoreH, res.paints.scoreBlue);
canvas.drawText(this.playerHuman.score.toString(), System.height / - 2 + this._scoreW, System.width / 16 + this._scoreH, res.paints.scoreGreen);
// restore canvas settings (revert rotation)
canvas.restore()
super.draw(canvas);
}
Puck
Puck is created as an instance of PhysicsSprite class. Its type is set to dynamic which means that it collides with other static or dynamic bodies. The puck‘s bounce property is one, which means that it bounces from other bodies with some force as it falls. To achieve the realistic physical behavior we need to apply linear damping to the body. In real air hockey, the puck moves on the air cushion so the damping between the puck and table is really small and the puck moves fast. It’s image is as well as all other images in the game loaded from the resources.
Example: create puck
function _createPuck()
{
const density = 1.0, friction = 0.2, bounce = 1.0;
// create physics body of the puck
var puck = this.addCircleBody(res.images.puck, #dynamic, density, friction, bounce, res.images.puck.width / 2/*radius*/);
// place puck to center of the table
puck.setPosition(System.width / 2, System.height / 2);
puck.fixedRotation = true;
puck.bullet = true;
puck.setLinearDamping(0.3);
return puck;
}
Barriers
Barriers prevent the puck and mallets from leaving the table. They are around the whole table except the goals. Their type is set to static, which means that they do not move under the simulation but collide with other dynamic bodies (puck and paddles). Together with barriers around the playground we are going to create also four invisible squares in the table corners. Without these squares the puck gets stuck sometimes near the left or right barrier and moves along up and down. However, when the puck hits one of these squares in the corners, it bounces away from the barrier.
Image: barriers and „inivisible“ squares around the playground

Example: create barriers
function _createBarriers()
{
const density = 0.0, friction = 0.0, bounce = 0.0;
const width = System.width / 4, height = System.width / 32;
var topWallA = this.addPolygonBody(null, #static, density, friction, bounce, width, height);
topWallA.setPosition(System.width / 8, 1);
var topWallB = this.addPolygonBody(null, #static, density, friction, bounce, width, height);
topWallB.setPosition(System.width - System.width / 8, 1);
var bottomWallA = this.addPolygonBody(null, #static, density, friction, bounce, width, height);
bottomWallA.setPosition(System.width/8, System.height);
var bottomWallB = this.addPolygonBody(null, #static, density, friction, bounce, width, height);
bottomWallB.setPosition(System.width - System.width / 8, System.height);
...
// corners
var leftTop = this.addPolygonBody(null, #static, density, friction, bounce, this.puckRadius, this.puckRadius);
leftTop.setPosition(this.puckRadius / 2, System.width / 60);
var rightTop = this.addPolygonBody(null, #static, density, friction, bounce, this.puckRadius, this.puckRadius);
rightTop.setPosition(System.width - this.puckRadius / 2, System.width / 60);
...
}
Goals
The goals are situated in the center of the top and bottom barrier. The goals are also bordered by three barriers on their left, right and top (or bottom) side, but these barriers are out of the screen to allow the puck leave the playground. When the puck collides with some barriers inside the goals, the puck is removed and the score updated.
Image: goals

Example: create goals
// creates goals
function _createGoals()
{
const density = 0.0, friction = 0.0, bounce = 0.0;
var goalA = this.addPolygonBody(null, #static, density, friction, bounce, this.goalsWidth, System.width / 32);
goalA.beginContact = function(contact) { this super._checkGoal(contact, #playerAI); }
goalA.setPosition(System.width / 2, -2 * this.puckRadius + System.width / 32);
var goalALeft = this.addPolygonBody(null, #static, density, friction, bounce, System.width / 32, 2 * this.puckRadius);
goalALeft.beginContact = function(contact) { this super._checkGoal(contact, #playerAI); }
goalALeft.setPosition(System.width / 2 - this.goalsWidth / 2 - this.puckRadius, -1 * this.puckRadius);
var goalARight = this.addPolygonBody(null, #static, density, friction, bounce, System.width / 32, 2 * this.puckRadius);
goalARight.beginContact = function(contact) { this super._checkGoal(contact, #playerAI); }
goalARight.setPosition(System.width / 2 + this.goalsWidth / 2 + this.puckRadius, -1 * this.puckRadius);
...}
Paddles
Both paddles are created by the same function _createPaddle function, which creates circular body with different image for human and for AI paddle . The density of the paddle is bigger than the puck’s density, which causes that with bigger size comes bigger mass comparing to the puck. When colliding two bodies with different mass, the movement of the body with larger mass is affected less.
Example: create paddles
// creates paddle (for AI or human player)
function _createPaddle(paddleType)
{
assert paddleType == #playerAI || paddleType == #playerHuman;
const density = 1.1, friction = 0.3, bounce = 0.0;
var paddle = (paddleType == #playerAI)
? this.addCircleBody(res.images.playerAI, #dynamic, density, friction, bounce, res.images.playerAI.width / 2)
: this.addCircleBody(res.images.playerHuman, #dynamic, density, friction, bounce, res.images.playerHuman.width / 2);
paddle.fixedRotation = true;
paddle.setLinearDamping(5.0);
return paddle;
}
Contacts
When two bodies collide in the scene two events are called: onBeginContact, when collision starts and onEndContact when collision ends. We map both events onto class member functions: _beginConcat and _endContact in game scene’s init function
Example: map both event functions
function init()
{
// create physics world
this._world = new b2World(0.0, 0.0, true, true);
// world callback
this.onBeginContact = function(sender, contact) { this super._beginContact(contact); }
this.onEndContact = function(sender, contact) { this super._endContact(contact); }
...
}
There are many bodies that can come into contact with one another (human paddle & puck, AI paddle & puck, human paddle & AI paddle, puck & bariers etc.). Checking particular bodies which collide throws any if or switch condition which may be too complicated. To simplify the contact management we add to all bodies, which should raise an event when they collide, call back function to beginContact or endContact variable. Moscrif’s JavaScript engine allows to create both local and member function anywhere in the code. It means that the variables do not have to be defined in the class, but it can be simply added to any separate objects similar as in the next example:
Example:
// create AI player
this.paddleAI = this._createPaddle(#playerAI);
this.paddleAI.endContact = function(body)
{
this super.playerAI.hit();
}
Then when some contact appears we only call object’s beginContact or endContact method for both bodies, which manages all other needed operations.
Example: begin contact
// listener for begin of collicion
function _beginContact(contact)
{
var bodyA = contact.getBodyA();
var bodyB = contact.getBodyB();
if(bodyA.beginContact)
bodyA.beginContact(bodyB, contact);
if(bodyB.beginContact)
bodyB.beginContact(bodyA, contact);
}
Human player
At this point, everything is prepared but paddles do not move. In order to do that, two classes are created: playerHuman and playerAI. PlayerHuman class moves paddle controlled by the player. To move the paddle we use mouse joint. Mouse joint allows manipulating with the physical bodies to wanted position. They can be also moved by setPosition method, but when body is moved by this method, it does not interact with other physical objects.
When user taps on the screen, the scene calls human player’s handlePressed method. In handlePressed method mouse joint is created to manipulate the paddle controlled by the player. If player taps on the opponent’s halve of playground the paddle moves along the central line.
Example: create mouse joint
// called by Table when touch down occured
function handlePressed(x, y)
{
// check player's side
if (y < System.height / 2)
y = System.height / 2;
// just simple helper
const table = this.table;
// mouse joint definition
var mouseJointDef = {
maxForce : 10000,
frequencyHz : 1000,
dampingRatio : 0.0,
targetX : table.x2box2d(x), // specified in box2d coords
targetY : table.y2box2d(y) // specified in box2d coords
};
// move paddle to touched place
this.paddle.setTransform(x, y);
// create mouse joint
if (this.joint)
this.table.destroyJoint(this.joint);
this.joint = table.createMouseJoint(table.ground, this.paddle, mouseJointDef, true);
}
When user moves his finger on the screen the scene calls handleDragged method from human player class. In this method, setTarget method of mouseJoint is called. This method moves the paddle to the current finger position.
Example: move the paddle to the current finger position
// called by Table when touch drag occured
function handleDragged(x, y)
{
// limit player's side
if (y < System.height / 2 + this.puckRadius)
y = System.height / 2 + this.puckRadius;
// affect mouse joint
if (this.joint != null)
this.joint.setTarget(this.table.x2box2d(x), this.table.y2box2d(y));
}
AI player
Opponent’s game is controlled by playerAI class. AI player also moves by mouse joint similar as a human player but coordinates to setTarget method is calculated by our algorithms. The AI player does only two actions -> defense or attack. To make player more realistic the class implements following features:
- The gap between two attacks is at least 700 milliseconds
- The AI player does not respond directly after the line but only after a small gap after line
- The AI player does not hit the puck if it is in the table corner.
- The AI player moves to defend position also if puck is on opponents half of the playground
- The AI player’s paddle moves with realistic speed
The gap between two opponents attacks is minimally 700 milliseconds. Every 25 milliseconds method handleProcess is called which checks if it takes at least 700 milliseconds after the last attack. If yes the opponent attacks again, otherwise it goes back to defense.
Example: check if last attack was before at least 700 miliseconds
// called by Table object (onProcess)
function handleProcess()
{
// get position of my paddle
var (x, y) = this.paddle.getPosition();
// get position of puck
var (px, py) = this.table.puck.getPosition();
// delay & defense after contact
if (System.tick - this.hitTime < 700) {
this._defense(x, y, px, py);
return;
}
// otherwise make a desiciton
this._makeDecision(x, y, px, py);
}
When the time from the last attack is at least 700 milliseconds the player may attack, but does not have to. To decide if attack or not function _makeDecision. If the puck is not too close to table’s corner and it is on the player’s half, the player attacks onto the puck.
Example: decide if the player should attack
function _makeDecision(x, y, px, py)
{
// attack when puck is in our corner
var puckInCorner = px < System.width / 5 || px > 4*System.width / 5;
if (puckInCorner && py < 2 * this.puckRadius ) {
return ;
}
// move to puck's position and hit puck to the second half of table
if (py < ( 9 * System.height / 20))
return this._moveTo(x, y, px, py - this.puckRadius / 4);
return this._defense(x, y, px, py);
}
The defense function moves the paddle horizontally in the front of the goal. However, the paddle does not move from the left barrier to the right. It moves only to the middle of the playground according to the current puck position.
Example: defense position
// simple defence method
function _defense(x, y, px, py)
{
if (py < y && Math.abs(System.width / 2 - px) > System.width / 5)
return this._moveTo(x, y, px, py - this.puckRadius);
this._moveTo(x, y, System.width / 4 + System.width / 2 * (px / (1.0 * System.width)) , System.height / 6);
return true;
}
As you can see in all previous methods, function _moveTo is used to move the paddle. This function moves the paddle gradually according to required speed. This function ensures that AI player’s speed is similar to human speed, because using directly setTarget method may cause that the AI player is too fast.
Example: move AI player's paddle
// calculates movement for AI paddle
function _moveTo(ox, oy, px, py)
{
// be random
var speed = Integer.min(640, System.width) / (40.0 + rand(20));
// calculate deltas
const dx = px - ox;
const dy = py - oy;
// calculate distance between puck and paddle position (we use Pythagorean theorem)
const distance = Math.sqrt(dx * dx + dy * dy);
// if total distance is greater than the distance, of which we can move in one step calculate new x and y coordinates somewhere between current puck and paddle position.
if (distance > speed) {
// x = current padle x position + equally part of speed on x axis
px = ox + speed / distance * dx;
py = oy + speed / distance * dy;
}
// move paddle to the new position
this.joint.setTarget(this.table.x2box2d(px), this.table.y2box2d(py));
return true;
}
The whole logic of AI player game is showed in next diagram:
Image: AI player logic

|