HTML5 Game Development – A Ball-shooting Machine with Physics Engine

Build interactive games with HTML, DOM, and the CreateJS Game library.

(For more resources related to this topic, see here.)

Mission briefing

In this article, we focus on the physics engine. We will build a basketball court where the player needs to shoot the ball in to the hoop. A player shoots the ball by keeping the mouse button pressed and releasing it. The direction is visualized by an arrow and the power is proportional to the duration of the mouse press and hold event.

There are obstacles present between the ball and the hoop. The player either avoids the obstacles or makes use of them to put the ball into the hoop. Finally, we use CreateJS to visualize the physics world into the canvas.

You may visit http://makzan.net/html5-games/ball-shooting-machine/ to play a dummy game in order to have a better understanding of what we will be building throughout this article.

The following screenshot shows a player shooting the ball towards the hoop, with a power indicator:

Why is it awesome?

When we build games without a physics engine, we create our own game loop and reposition each game object in every frame. For instance, if we move a character to the right, we manage the position and movement speed ourselves.

Imagine that we are coding a ball-throwing logic now. We need to keep track of several variables. We have to calculate the x and y velocity based on the time and force applied. We also need to take the gravity into account; not to mention the different angles and materials we need to consider while calculating the bounce between the two objects.

Now, let's think of a physical world. We just defined how objects interact and all the collisions that happen automatically. It is similar to a real-world game; we focus on defining the rule and the world will handle everything else. Take basketball as an example. We define the height of the hoop, size of the ball, and distance of the three-point line. Then, the players just need to throw the ball. We never worry about the flying parabola and the bouncing on the board. Our space takes care of them by using the physics laws.

This is exactly what happens in the simulated physics world; it allows us to apply the physics properties to game objects. The objects are affected by the gravity and we can apply forces to them, making them collide with each other.

With the help of the physics engine, we can focus on defining the game-play rules and the relationship between the objects. Without the need to worry about collision and movement, we can save time to explore different game plays. We then elaborate and develop the setup further, as we like, among the prototypes.

We define the position of the hoop and the ball. Then, we apply an impulse force to the ball in the x and y dimensions. The engine will handle all the things in between. Finally, we get an event trigger if the ball passes through the hoop.

It is worth noting that some blockbuster games are also made with a physics engine. This includes games such as Angry Birds, Cut the Rope, and Where's My Water.

Your Hotshot objectives

We will divide the article into the following eight tasks:

  • Creating the simulated physics world

  • Shooting a ball

  • Handling collision detection

  • Defining levels

  • Launching a bar with power

  • Adding a cross obstacle

  • Visualizing graphics

  • Choosing a level

Mission checklist

We create a project folder that contains the index.html file and the scripts and styles folders. Inside the scripts folder, we create three files: physics.js, view.js, and game.js.

The physics.js file is the most important file in this article. It contains all the logic related to the physics world including creating level objects, spawning dynamic balls, applying force to the objects, and handling collision.

The view.js file is a helper for the view logic including the scoreboard and the ball-shooting indicator.

The game.js file, as usual, is the entry point of the game. It also manages the levels and coordinates between the physics world and view.

Preparing the vendor files

We also need a vendors folder that holds the third-party libraries. This includes the CreateJS suite—EaselJS, MovieClip, TweenJS, PreloadJS—and Box2D.

Box2D is the physics engine that we are going to use in this article. We need to download the engine code from https://code.google.com/p/box2dweb/. It is a port version from ActionScript to JavaScript.

We need the Box2dWeb-2.1.a.3.min.js file or its nonminified version for debugging. We put this file in the vendors folder.

Box2D is an open source physics-simulation engine that was created by Erin Catto. It was originally written in C++. Later, it was ported to ActionScript because of the popularity of Flash games, and then it was ported to JavaScript. There are different versions of ports. The one we are using is called Box2DWeb, which was ported from ActionScript's version Box2D 2.1. Using an old version may cause issues. Also, it will be difficult to find help online because most developers have switched to 2.1.

Creating a simulated physics world

Our first task is to create a simulated physics world and put two objects inside it.

Prepare for lift off

In the index.html file, the core part is the game section. We have two canvas elements in this game. The debug-canvas element is for the Box2D engine and canvas is for the CreateJS library:

<section id="game" class="row"> <canvas id="debug-canvas" width="480" height="360"></canvas> <canvas id="canvas" width="480" height="360"></canvas> </section>

We prepare a dedicated file for all the physics-related logic. We prepare the physics.js file with the following code:

;(function(game, cjs, b2d){ // code here later }).call(this, game, createjs, Box2D);

Engage thrusters

The following steps create the physics world as the foundation of the game:

  1. The Box2D classes are put in different modules. We will need to reference some common classes as we go along. We use the following code to create an alias for these Box2D classes:

    // alias var b2Vec2 = Box2D.Common.Math.b2Vec2 , b2AABB = Box2D.Collision.b2AABB , b2BodyDef = Box2D.Dynamics.b2BodyDef , b2Body = Box2D.Dynamics.b2Body , b2FixtureDef = Box2D.Dynamics.b2FixtureDef , b2Fixture = Box2D.Dynamics.b2Fixture , b2World = Box2D.Dynamics.b2World , b2MassData = Box2D.Collision.Shapes.b2MassData , b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape , b2CircleShape = Box2D.Collision.Shapes.b2CircleShape , b2DebugDraw = Box2D.Dynamics.b2DebugDraw , b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef , b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef ;

  2. We prepare a variable that states how many pixels define 1 meter in the physics world. We also define a Boolean to determine if we need to draw the debug draw:

    var pxPerMeter = 30; // 30 pixels = 1 meter. Box3D uses meters and we use pixels. var shouldDrawDebug = false;

  3. All the physics methods will be put into the game.physics object. We create this literal object before we code our logics:

    var physics = game.physics = {};

  4. The first method in the physics object creates the world:

    physics.createWorld = function() { var gravity = new b2Vec2(0, 9.8); this.world = new b2World(gravity, /*allow sleep= */ true); // create two temoporary bodies var bodyDef = new b2BodyDef; var fixDef = new b2FixtureDef; bodyDef.type = b2Body.b2_staticBody; bodyDef.position.x = 100/pxPerMeter; bodyDef.position.y = 100/pxPerMeter; fixDef.shape = new b2PolygonShape(); fixDef.shape.SetAsBox(20/pxPerMeter, 20/pxPerMeter); this.world.CreateBody(bodyDef).CreateFixture(fixDef); bodyDef.type = b2Body.b2_dynamicBody; bodyDef.position.x = 200/pxPerMeter; bodyDef.position.y = 100/pxPerMeter; this.world.CreateBody(bodyDef).CreateFixture(fixDef); // end of temporary code }

  5. The update method is the game loop's tick event for the physics engine. It calculates the world step and refreshes debug draw. The world step upgrades the physics world. We'll discuss it later:

    physics.update = function() { this.world.Step(1/60, 10, 10); if (shouldDrawDebug) { this.world.DrawDebugData(); } this.world.ClearForces(); };

  6. Before we can refresh the debug draw, we need to set it up. We pass a canvas reference to the Box2D debug draw instance and configure the drawing settings:

    physics.showDebugDraw = function() { shouldDrawDebug = true; //set up debug draw var debugDraw = new b2DebugDraw(); debugDraw.SetSprite(document.getElementById("debug-canvas").getContext("2d")); debugDraw.SetDrawScale(pxPerMeter); debugDraw.SetFillAlpha(0.3); debugDraw.SetLineThickness(1.0); debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit); this.world.SetDebugDraw(debugDraw); };

  7. Let's move to the game.js file. We define the game-starting logic that sets up the EaselJS stage and Ticker. It creates the world and sets up the debug draw. The tick method calls the physics.update method:

    ;(function(game, cjs){ game.start = function() { cjs.EventDispatcher.initialize(game);
    // allow the game object to listen and dispatch custom events. game.canvas = document.getElementById('canvas'); game.stage = new cjs.Stage(game.canvas); cjs.Ticker.setFPS(60); cjs.Ticker.addEventListener('tick', game.stage);
    // add game.stage to ticker make the stage.update call automatically. cjs.Ticker.addEventListener('tick', game.tick); // gameloop game.physics.createWorld(); game.physics.showDebugDraw(); }; game.tick = function(){ if (cjs.Ticker.getPaused()) { return; } // run when not paused game.physics.update(); }; game.start(); }).call(this, game, createjs);

After these steps, we should have a result as shown in the following screenshot. It is a physics world with two bodies. One body stays in position and the other one falls to the bottom.

Objective complete – mini debriefing

We have defined our first physical world with one static object and one dynamic object that falls to the bottom.

A static object is an object that is not affected by gravity and any other forces. On the other hand, a dynamic object is affected by all the forces.

Defining gravity

In reality, we have gravity on every planet. It's the same in the Box2D world. We need to define gravity for the world. This is a ball-shooting game, so we will follow the rules of gravity on Earth. We use 0 for the x-axis and 9.8 for the y-axis.

It is worth noting that we do not need to use the 9.8 value. For instance, we can set a smaller gravity value to simulate other planets in space—maybe even the moon; or, we can set the gravity to zero to create a top-down view of the ice hockey game, where we apply force to the puck and benefit from the collision.

Debug draw

The physics engine focuses purely on the mathematical calculation. It doesn't care about how the world will be presented finally, but it does provide a visual method in order to make the debugging easier.

This debug draw is very useful before we use our graphics to represent the world.

We won't use the debug draw in production. Actually, we can decide how we want to visualize this physics world. We have learned two ways to visualize the game. The first way is by using the DOM objects and the second one is by using the canvas drawing method. We will visualize the world with our graphics in later tasks.

Understanding body definition and fixture definition

In order to define objects in the physics world, we need two definitions: a body definition and fixture definition.

The body is in charge of the physical properties, such as its position in the world, taking and applying force, moving speed, and the angular speed when rotating.

We use fixtures to handle the shape of the object. The fixture definition also defines the properties on how the object interacts with others while colliding, such as friction and restitution.

Defining shapes

Shapes are defined in a fixture. The two most common shapes in Box2D are rectangle and circle. We define a rectangle with the SetAsBox function by providing half of its width and height. Also, the circle shape is defined by the radius.

It is worth noting that the position of the body is at the center of the shape. It is different from EaselJS in that the default origin point is set at the top-left corner.

Pixels per meter

When we define the dimension and location of the body, we use meter as a unit. That's because Box2D uses metric for calculation to make the physics behavior realistic.

But we usually calculate in pixels on the screen. So, we need to convert between pixels on the screen and meters in the physics world. That's why we need the pxPerMeter variable here. The value of this variable might change from project to project.

The update method

In the game tick, we update the physics world.

The first thing we need to do is take the world to the next step. Box2D calculates objects based on steps. It is the same as we see in the physical world when a second is passed. If a ball is falling, at any fixed time, the ball is static with the property of the falling velocity. In the next millisecond, or nanosecond, the ball falls to a new position. This is exactly how steps work in the Box2D world. In every single step, the objects are static with their physics properties. When we go a step further, Box2D takes the properties into consideration and applies them to the objects.

This step takes three arguments. The first argument is the time passed since the last step. Normally, it follows the frame-per-second parameter that we set for the game. The second and the third arguments are the iteration of velocity and position. This is the maximum iterations Box2D tries when resolving a collision. Usually, we set them to a low value.

The reason we clear the force is because the force will be applied indefinitely if we do not clear it. That means the object keeps receiving the force on each frame until we clear it. Normally, clearing forces on every frame will make the objects more manageable.

Classified intel

We often need to represent a 2D vector in the physics world. Box2D uses b2vec for this purpose. Similar to the b2vec function, we use quite a lot of Box2D functions and classes. They are modularized into namespaces. We need to alias the most common classes to make our code shorter.

Shooting the ball

In this task, we create a hoop and allow the player to throw the ball by clicking the mouse button. The ball may or may not pass through the hoop based on the throwing angle and power.

Prepare for lift off

We remove the two bodies that were created in the first task. Those two bodies were just an experiment and we don't need them anymore.

Engage thrusters

In the following steps, we will create the core part of this article—shooting the ball:

  1. We will create a hoop and spawn a ball in the physics world. We create a function for these two tasks:

    physics.createLevel = function() { this.createHoop(); // the first ball this.spawnBall(); };

  2. We are going to spawn many balls. We define the following method for this task. In this task, we hardcode the position, ball size, and fixture properties. The ball is spawned as a static object until the player throws the ball out:

    physics.spawnBall = function() { var positionX = 300; var positionY = 200; var radius = 13; var bodyDef = new b2BodyDef; var fixDef = new b2FixtureDef; fixDef.density = 0.6; fixDef.friction = 0.8; fixDef.restitution = 0.1; bodyDef.type = b2Body.b2_staticBody; bodyDef.position.x = positionX/pxPerMeter; bodyDef.position.y = positionY/pxPerMeter; fixDef.shape = new b2CircleShape(radius/pxPerMeter); this.ball = this.world.CreateBody(bodyDef); this.ball.CreateFixture(fixDef); };

  3. We will need to get the ball position to calculate the throwing angle. We define the following method to get the ball position and convert it into screen coordinates:

    physics.ballPosition = function(){ var pos = this.ball.GetPosition(); return { x: pos.x * pxPerMeter, y: pos.y * pxPerMeter }; };

  4. By using the cursor and ball position, we can calculate the angle. This is the Math function that returns the angle, which will be explained later:

    physics.launchAngle = function(stageX, stageY) { var ballPos = this.ballPosition(); var diffX = stageX - ballPos.x; var diffY = stageY - ballPos.y; // Quadrant var degreeAddition = 0; // Quadrant I if (diffX < 0 && diffY > 0) { degreeAddition = Math.PI; // Quadrant II } else if (diffX < 0 && diffY < 0) { degreeAddition = Math.PI; // Quadrant III } else if (diffX > 0 && diffY < 0) { degreeAddition = Math.PI * 2; // Quadrant IV } var theta = Math.atan(diffY / diffX) + degreeAddition; return theta; };

  5. We have prepared the Math methods and can finally throw the ball with the following method:

    physics.shootBall = function(stageX, stageY, ticksDiff) { this.ball.SetType(b2Body.b2_dynamicBody); var theta = this.launchAngle(stageX, stageY); var r = Math.log(ticksDiff) * 50; // power var resultX = r * Math.cos(theta); var resultY = r * Math.sin(theta); this.ball.ApplyTorque(30); // rotate it // shoot the ball this.ball.ApplyImpulse(new b2Vec2(resultX/pxPerMeter, resultY/pxPerMeter),
    this.ball.GetWorldCenter()); this.ball = undefined; };

  6. We need a target for the throwing action. So, we create the hoop with the following code. A hoop is constructed using a board and two squares:

    physics.createHoop = function() { var hoopX = 50; var hoopY = 100; var bodyDef = new b2BodyDef; var fixDef = new b2FixtureDef; // default fixture fixDef.density = 1.0; fixDef.friction = 0.5; fixDef.restitution = 0.2; // hoop bodyDef.type = b2Body.b2_staticBody; bodyDef.position.x = hoopX/pxPerMeter; bodyDef.position.y = hoopY/pxPerMeter; bodyDef.angle = 0; fixDef.shape = new b2PolygonShape(); fixDef.shape.SetAsBox(5/pxPerMeter, 5/pxPerMeter); var body = this.world.CreateBody(bodyDef); body.CreateFixture(fixDef); bodyDef.type = b2Body.b2_staticBody; bodyDef.position.x = (hoopX+45)/pxPerMeter; bodyDef.position.y = hoopY/pxPerMeter; bodyDef.angle = 0; fixDef.shape = new b2PolygonShape(); fixDef.shape.SetAsBox(5/pxPerMeter, 5/pxPerMeter); body = this.world.CreateBody(bodyDef); body.CreateFixture(fixDef); // hoop board dimension: 10x80 (5x40 in half value) bodyDef.type = b2Body.b2_staticBody; bodyDef.position.x = (hoopX-5)/pxPerMeter; bodyDef.position.y = (hoopY-40)/pxPerMeter; bodyDef.angle = 0; fixDef.shape = new b2PolygonShape(); fixDef.shape.SetAsBox(5/pxPerMeter, 40/pxPerMeter); fixDef.restitution = 0.05; var board = this.world.CreateBody(bodyDef); board.CreateFixture(fixDef); };

  7. Now, we can initialize the world by calling the createLevel method. At the same time, we should remove the creation of two test objects that we added in the last task:

    game.physics.createLevel();

  8. In the game.js file, we handle the mousedown and mouseup events to get the position of the cursor and the duration for which the mouse button was kept pressed. The cursor's position determines the angle and the duration determines the power:

    isPlaying = true; game.tickWhenDown = 0; game.tickWhenUp = 0; game.stage.on('stagemousedown', function(e){ if (!isPlaying) { return; } game.tickWhenDown = cjs.Ticker.getTicks(); }); game.stage.on('stagemouseup', function(e){ if (!isPlaying) { return; } game.tickWhenUp = cjs.Ticker.getTicks(); ticksDiff = game.tickWhenUp - game.tickWhenDown; game.physics.shootBall(e.stageX, e.stageY, ticksDiff); setTimeout(game.spawnBall, 500); });

  9. Finally, we spawn another ball after the last ball is thrown:

    game.spawnBall = function() { game.physics.spawnBall(); };

After performing these steps, we should get the result as shown in the following screenshot. We can click anywhere on the screen; once the mouse button is released, the ball is thrown towards the position of the cursor. When the angle and power is right, the ball is thrown into the hoop.

Objective complete – mini debriefing

Thanks to the physics engine, we only have to define the position of the objects, throwing angle, and power to create the entire ball-throwing logic. The engine automatically calculates the path of the throw and the bounce of the ball.

Shooting the ball

We change the state of the ball from static to dynamic before applying some force to it. Forces only affect dynamic bodies.

Once we know any two points in the screen, we can calculate the rotation angle. This is done using the geometry formula. The following figure shows the relationship between the edges of the triangle and angles between them:

The calculated angle is correct only if it is in the first quadrant. We determine the quadrant of the mouse cursor by referencing the ball's position as the original point. The following figure shows the four quadrants:

According to math, we need to add additional degrees to the calculated result if the cursor is not in the first quadrant. The degrees to be added, depending on the quadrant, are shown as follows:

  • Quadrant 1: Add 0

  • Quadrant 2 and 3: Add 180 (PI)

  • Quadrant 4: Add 360 (PI * 2)

After we get the angle, we need the value of the power. The power is based on the time duration of the mouse button being pressed.

The less amount of time the mouse button is being pressed, the less power is applied to the ball. The longer the mouse button is pressed, the more power is applied. You may find that the power will be hard to control if we use a linear scale. It is very difficult to find the right timing. Either the power is too little to be noticeable or too much that the ball flies directly out of the screen. The duration is too sensitive.

We use the logarithm to solve this problem. The logarithm makes the shooting power much smoother. Imagine when we click on the mouse. It is not very different if we keep it pressed for 500 milliseconds or 1 second. But the value indeed has doubled. That's why the timing is difficult. The power of force is determined in the millisecond scale. Now with the logarithm, the value is determined by the exponential of the duration. 100 milliseconds may mean that the value is 1 and 1 second may mean that the value is 2. It is still more powerful when kept pressed for longer. But the value difference is not that sensitive anymore. The following figure shows how the logarithm decreases the sensitivity:

After we have the angle and power, we decompose the vector into x-axis and y-axis vectors using a geometry formula. This is the impulse force that we apply to the ball.

Applying the force

There are two ways to apply force in Box2D: ApplyForce and ApplyImpulse.

In Box2D, force is often consistently applied to a body for a while. For example, we speed up a car by applying force. We use impulse, for instance, to apply a one-time force. Throwing a ball is more like an impulse than a constant force.

Explaining the construction of the physics world

The hoop is constructed using three static bodies—a board and two squares, as illustrated in the following figure:

Classified intel

In the real world, when basketball players throw the ball, they spin the ball as shown in the following figure:

We make the ball spin by applying torque to it while the ball is thrown.

Summary

In this article, we learned to create a simulated physics world and put two objects inside it; and a hoop, and also allowed the player to throw the ball by clicking the mouse button

Resources for Article:


Further resources on this subject:


Books to Consider

comments powered by Disqus
X

An Introduction to 3D Printing

Explore the future of manufacturing and design  - read our guide to 3d printing for free