Animating multiple objects (enemies, obstacles, etc.)

Animating multiple objects

Introduction

In this section we will see how we can animate and control not only the player but also other objects on the screen.

Let’s study a simple example: animating a few balls and detecting collisions with the surrounding walls. For the sake of simplicity, we will not use time-based animation in the first examples.

animating multiple balls which bounce off horizontal and vertical walls

Online example at JSBin:

Some circular balls that bounce agains vertical and horizontal borders of the canvas

In this example, we define a constructor function for creating balls. This is a way to design JavaScript “pseudo classes” as found in other object-oriented languages like Java, C# etc. It’s useful when you plan to create many objects of the same class. Using this we could animate hundreds of balls on the screen.

Each ball has an x and y position, and in this example, instead of working with angles, we defined two “speeds” – horizontal and vertical speeds – in the form of the increments we will add to the x and y positions at each frame of animation. We also added a variable for adjusting the size of the balls: the radius.

Here is the constructor function for building balls:

  1. // Constructor function for balls
  2. function Ball(x, y, vx, vy, diameter) {
  3.   // property of each ball: a x and y position, speeds, radius
  4.   this.x = x;
  5.   this.y = y;
  6.   this.vx = vx;
  7.   this.vy = vy;
  8.   this.radius = diameter/2;
  9.   // methods
  10.   this.draw = function() {
  11.     ctx.beginPath();
  12.     ctx.arc(this.x, this.y, this.radius, 0, 2*Math.PI);
  13.     ctx.fill();
  14.   };
  15.   this.move = function() {
  16.     // add horizontal increment to the x pos
  17.     // add vertical increment to the y pos
  18.     this.x += this.vx;
  19.     this.y += this.vy;
  20.  };
  21. }

Using a constructor function makes it easy to build new balls as follows:

  1. var b1 = new Ball(10, 10, 2, 2, 5); // x, y, vx, vy, radius
  2. var b1 = new Ball(100, 130, 4, 5, 5); 
  3. etc

We defined two methods in the constructor function for moving the ball and for drawing the ball as a black filled circle. Here is the syntax for moving and drawing a ball:

  1. b1.draw();
  2. b1.move();

We will call these methods from inside the mainLoop, and as you’ll see, we will create many balls. This object-oriented design makes it easier to handle large quantities.

Here is the rest of the code from this example:

  1. var canvas, ctx, width, height;
  2. // array of balls to animate
  3. var ballArray = [];
  4. function init() {
  5.   canvas = document.querySelector(“#myCanvas”);
  6.   ctx = canvas.getContext(‘2d’);
  7.   width = canvas.width;
  8.   height = canvas.height;
  9.   // try to change this number
  10.   createBalls(16);
  11.   requestAnimationFrame(mainLoop);
  12. }
  13. function createBalls(numberOfBalls) {
  14.   for(var i=0; i < numberOfBalls; i++) {
  15.     // Create a ball with random position and speed.
  16.     // You can change the radius
  17.     var ball = new Ball(width*Math.random(),
  18.                         height*Math.random(),
  19.                         (10*Math.random())-5,
  20.                         (10*Math.random())-5,
  21.                         30);
  22.     // add the ball to the array
  23.     ballArray[i] = ball;
  24.    }
  25. }
  26. function mainLoop() {
  27.   // clear the canvas
  28.   ctx.clearRect(0, 0, width, height);
  29.   // for each ball in the array
  30.   for(var i=0; i < ballArray.length; i++) {
  31.     var ball = ballArray[i]; 
  32.     // 1) move the ball
  33.     ball.move();
  34.     // 2) test if the ball collides with a wall
  35.     testCollisionWithWalls(ball);
  36.     // 3) draw the ball
  37.     ball.draw();
  38.   }
  39.   // ask for a new frame of animation at 60f/s
  40.   window.requestAnimationFrame(mainLoop);
  41. }
  42. function testCollisionWithWalls(ball) {
  43.   // left
  44.   if (ball.x < ball.radius) { // x and y of the ball are at the center of the circle
  45.     ball.x = ball.radius;     // if collision, we replace the ball at a position
  46.     ball.vx *= 1;            // where it’s exactly in contact with the left border
  47.   }                           // and we reverse the horizontal speed
  48.   // right
  49.   if (ball.x > width  (ball.radius)) {
  50.     ball.x = width  (ball.radius);
  51.     ball.vx *= 1;
  52.   }
  53.   // up
  54.   if (ball.y < ball.radius) {
  55.     ball.y = ball.radius;
  56.     ball.vy *= 1;
  57.   }
  58.   // down
  59.   if (ball.y > height  (ball.radius)) {
  60.     ball.y = height  (ball.radius);
  61.     ball.vy *= 1;
  62.   }
  63. }

Notice that:

    • All the balls are stored in an array (line 4),
    • We wrote a createBalls(nb) function that creates a given number of balls (and stores them in the array) with random values for position and speed (lines 18-32)
    • In the mainLoop, we iterate on the array of balls and for each ball we: 1) move it, 2) test if it collides with the boundaries of the canvas (in the function testCollisionWithWalls), and 3) we draw the balls (lines 38-50). The order of these steps is not critical and may be changed.
    • The function that tests collisions is straightforward (lines 55-76).  We did not use “if… else if” since a ball may sometimes touch two walls at once (in the corners). In that rare case, we need to invert both the horizontal and vertical speeds. When a ball collides with a wall, we need to replace it in a position where it is no longer against the wall (otherwise it will collide again during the next animation loop execution).

similar example but with the ball direction as an angle, and a single velocity variable

Try this example at JSBin: it behaves in the same way as the previous example.

Note that we just changed the way we designed the balls and computed the angles after they rebound from the walls. The changes are highlighted in bold:

  1. var canvas, ctx, width, height;
  2. // Array of balls to animate
  3. var ballArray = [];
  4. function init() {
  5.   …
  6. }
  7. function createBalls(numberOfBalls) {
  8.   for(var i=0; i < numberOfBalls; i++) {
  9.     // Create a ball with random position and speed.
  10.     // You can change the radius
  11.     var ball = new Ball(width*Math.random(),
  12.                         height*Math.random(),
  13.               (2*Math.PI)*Math.random(), // angle
  14.               (10*Math.random())-5,      // speed
  15.                         30);
  16.    // We add it in an array
  17.    ballArray[i] = ball;
  18.   }
  19. }
  20. function mainLoop() {
  21.   …
  22. }
  23. function testCollisionWithWalls(ball) {
  24.   // left
  25.   if (ball.x < ball.radius) {
  26.      ball.x = ball.radius;
  27.      ball.angle = ball.angle + Math.PI;
  28.   }
  29.   // right
  30.   if (ball.x > width  (ball.radius)) {
  31.     ball.x = width  (ball.radius);
  32.     ball.angle = ball.angle + Math.PI;
  33.   }
  34.   // up
  35.   if (ball.y < ball.radius) {
  36.     ball.y = ball.radius;
  37.     ball.angle = ball.angle;
  38.   }
  39.   // down
  40.   if (ball.y > height  (ball.radius)) {
  41.     ball.y = height  (ball.radius);
  42.     ball.angle =-ball.angle;
  43.   }
  44. }
  45. // constructor function for balls
  46. function Ball(x, y, angle, v, diameter) {
  47.   this.x = x;
  48.   this.y = y;
  49.   this.angle = angle;
  50.   this.v = v;
  51.   this.radius = diameter/2;
  52.   this.draw = function() {
  53.    …
  54.   };
  55.   this.move = function() {
  56.    // add horizontal increment to the x pos
  57.    // add vertical increment to the y pos
  58.    this.x += this.v * Math.cos(this.angle);
  59.    this.y += this.v * Math.sin(this.angle);
  60.   };
  61. }

Using angles or horizontal and vertical increments is equivalent. However, one method might be preferable to the other: for example, to control an object that follows the mouse, or that tracks another object in order to attack it, angles would be more practical input to the computations required.

Leave a comment