Fire a Bird
The main point of this game is to hit boxes by the bird. Basically, it is a demo game similar to the all-popular Angry Birds game. In the game, people are shooting the birds at pigs hidden within various structures (boxes in this demo). The bird is situated on the tree on the right side of the screen.
The direction and speed is controlled by pulling the bird by finger. The destroyable boxes are distributed randomly in two columns on the left side of the screen. The intention is to destroy all the pigs on the screen. As players advance, new abilities and special birds become available making the game more attracting and funnier.
Creating the game
Game physics
In order to make the game playable along with smooth animations, some physics engine has to be used that will demonstrate real behavior of the boxes. Moscrif is based on Box2d physical engine, which provides amazing performance and was used in various devices like Nintendo Wii or DS as well as on some major operation systems.
The physics scene
The whole game is situated into the physics scene. The physics scene contains core physics bodies, which represents boxes, bird and barriers in the playground. The Box2d engine supports three types of bodies to customize its different behavior. The barriers are static, so they do not move under simulation but still are able to collide with other dynamic bodies within the scene.
Example: creating mantinels around the playground
function _createMantinels()
{
// ground
var (width, height) = (System.width, 1);
this._ground = this.addPolygonBody(res.image.grass, #static, 0, 5, 0.5, width, height); // density, friction, bounce
this._ground.z = 2;
this._ground.setPosition(System.width/2, System.height - res.image.grass.height/2*this.grScale);
// left mantinel
(width, height) = (1, System.height);
var leftMantinel = this.addPolygonBody(null, #static, 0, 5, 0.5, width, height);
leftMantinel.setPosition(0, System.height/2);
// right mantinel
(width, height) = (1, System.height);
var rightMantinel = this.addPolygonBody(null, #static, 0, 5, 0.5, width, height);
rightMantinel.setPosition(System.width, System.height/2);
// top mantinel
(width, height) = (System.width, 1);
var topMantinel = this.addPolygonBody(null, #static, 0, 5, 0.5, width, height);
topMantinel.setPosition(System.width/2, 0);
}
The boxes and the bird are represented by dynamic bodies as they are fully simulated and move according to gravity and other forces.
Example: creating the boxes
function _createBoxes()
{
var offset = rand((res.image.box.width*this.grScale / 5).toInteger()) - res.image.box.width*this.grScale / 10;
// creating crates
var box = 0;
for (var i = 0; i < rand(3) + 5; i++) {
box = this.addPolygonBody(res.image.box, #dynamic, 0.5, 0.5, 0, res.image.box.width*this.grScale, res.image.box.height*this.grScale);
box.scale = this.grScale;
box.setPosition(System.width/6 + offset, System.height - res.image.grass.height / 2 * this.grScale - i*res.image.box.height*this.grScale - res.image.box.height*this.grScale/2);
this.boxes.push(box);
offset = rand((res.image.box.width*this.grScale / 5).toInteger()) - res.image.box.width*this.grScale / 10;
}
for (var i = 0; i < rand(3) + 4; i++) {
box = this.addPolygonBody(res.image.box, #dynamic, 0.5, 0.5, 0, res.image.box.width*this.grScale, res.image.box.height*this.grScale);
box.scale = this.grScale;
box.setPosition(System.width/3 + offset, System.height - res.image.grass.height / 2 * this.grScale - i*res.image.box.height*this.grScale - res.image.box.height*this.grScale/2);
this.boxes.push(box);
offset = rand((res.image.box.width*this.grScale / 5).toInteger()) - res.image.box.width*this.grScale / 10;
}
}
The bird is represented by dynamic body as well so it can move under the gravity force. At the start, the bird is placed on its initial position. The mouse joint is used to move the body towards a specified point. The starting point is set onto bird's initial position.
The bird is also set as a bullet. The bullet property allows CCD (continue collision physics onto the bird). "Continuous collision detection (also called CCD), is a box2d feature which ensures correct simulation of fast moving objects. Some older physical engines do not supports CCD, what means that they calculate positions and colisions of bodies for every time step, what is called discrete simulation. However in discrete simulation rigid body can move long distance in time step. (If the body has sufficiently high speed, it can be before time step few meters in front of the barrier and after time step it can be few meters behind the barrier.) It caused that the fast moving body can move throw another body without detection of collision between these two bodies. This effect is called tunneling. By default box2d uses CCD to prevent tunneling effect. CCD looks for all collisions between the position before and after time step. For every collision it calculates time of impact (TOI). On the next time step the body moves only to the next TOI and then wait for the rest of time step and does not move anymore during the time step. To ensure the best performace the CCD calculates contacts only between dynamic and static bodies (not between the dynamic bodies each other). However, onto dynamic bodies can be set CCD separately. If there is fast moving body, which hit other dynamic bodies, you can set the fast moving body’s property bullet to true to allows CCD onto it." source: Moscrif API
Example: creating the bird
function _createBird()
{
if (this._mouseJoint != null)
this.destroyJoint(this._mouseJoint); // create body
this.bird = this.addCircleBody(res.image.bird, #dynamic, 0.5, 0.5, 0.1, res.image.bird.width/2*this.grScale);
this.bird.scale = this.grScale;
this.bird.setLinearDamping(0.5);
this.bird.bullet = true;
// set position
this.bird.setPosition(this.ballStart.x, this.ballStart.y);
var mouseJointDef = {
maxForce : 10000,
frequencyHz : 100000,
dampingRatio : 0.0,
targetX : this.ballStart.x / this.scale,
targetY : (System.height-this.ballStart.y) / this.scale
};
this._mouseJoint = this.createMouseJoint(this._ground, this.bird, mouseJointDef, false);
this.angle = 0.0;
this.distance = 0.0;
}
When user taps the bird, it starts moving according to player’s tap. The distance from the initial position is restricted to simulate an effect similar to stretch.
Example: dragging the bird
function pointerDragged(x, y)
{
super.pointerDragged(x, y); if (!this.pressed)
return; // compute actual distance from start position
var distanceX = x - this.ballStart.x;
var distanceY = y - this.ballStart.y;
// compute angle
this.angle=Math.atan2(distanceY,distanceX);
// distance
this.distance = Math.sqrt(distanceX*distanceX + distanceY*distanceY);
if (this.distance > this.maxDistance)
this.distance = this.maxDistance; x=this.ballStart.x + this.distance*Math.cos(this.angle);
y=this.ballStart.y + this.distance*Math.sin(this.angle);
// recompute distance
var b2y = (System.height - y) / this.scale;
var b2x = x / this.scale;
// set target new target of mouse joint
this._mouseJoint.setTarget(b2x, b2y);
}
When user releases his tap from the screen the mouse joint is destroyed allowing free movement of the bird. Linear velocity impulse is applied on the bird according to the stretch of the bird.
Example: applying linear velocity
function pointerReleased(x, y)
{
super.pointerReleased(x, y); if (!this.pressed)
return; this.pressed = false;
this.destroyJoint(this._mouseJoint);
this._mouseJoint = null; // compute velocity vector
var velox=-this.distance*Math.cos(this.angle)*20/this.scale;
var veloy=this.distance*Math.sin(this.angle)*20/this.scale;
// set linear velocity to the ball
this.bird.setLinearVelocity(velox, veloy);
}
For the sake of this demo, the game can only distribute boxes and fire a bird. The physics scene has protected method process, which is called every 25 milliseconds. This method is excellent for checking the birds speed. If the speed is low enough the bird is destroyed and a new one is created.
However, the bird has zero speed when hitting the barriers as well. To solve this issue, a timer is used to check the birds speed in a short time intervals. If the bird is slow enough after this interval it is destroyed.
Example: checking the speed
function process()
{
super.process();
// make a time step in physics simulation
this.step();
// if ball lose his speed set flags and creates new one
if (this._mouseJoint)
return;
var (vx, vy);
if (this.bird) {
(vx, vy) = this.bird.getLinearVelocity();
var speed = Math.sqrt(vx*vx + vy*vy);
// check bird speed
if (!this.pressed && speed<0.2) {
var t = new Timer(1, 1);
// check bird speed also after short time interval
t.onTick = function()
{
var self = this super;
var (vx, vy);
if (self.bird && !self._mouseJoint) {
(vx, vy) = self.bird.getLinearVelocity();
var speed = Math.sqrt(vx*vx + vy*vy);
if (speed<0.2) {
// destroy bird
self.destroyBody(self.bird);
self.bird = null;
self._createBird();
}
}
}
t.start(1300);
}
}
}
Summary
As you can see, only few lines of code are needed to create playable demo of well-known Angry Birds game. It is extremely easy to create popular mobile game with millions of downloads all over the world. Once you implement some more levels and add some unique features, Fire the Bird game is ready to hit the app stores.
If you decide to add these, we will be more than happy to help with any issues you might come across, so do not hesitate to contact us.
|