Hi, welcome! Let me show you how we can add many animated objects to the game framework.
You can imagine them as being the enemies the player should fight or whatever.
For the sake of this example, we are using black balls, but you can imagine small images or small monsters or whatever.
Using here a constructor function is interesting because we can design a sort of class.
I mean a bit like Java classes or C# classes, if we make a comparison with other objected oriented languages.
So we just say that the ball has a x position and a y position, an angle, a speed (the ‘v’ here if for he speed), and a radius.
We also said that each ball will be able to move and to be drawn on the screen.
It is a way to encapsulate in one single function the properties and the methods for manipulating
the balls.
And the advantage is that now we can create
many balls using the new operator and passing different parameters.
Let's have a look at a function that will build a certain amount of balls with different
parameters.
We called it createBalls: it takes as parameters
a number of balls and will, in a loop, create new balls.
The new ball here will create a ball with a random x position, a random y position, a random angle between 0 and 2*PI, and a random speed and a given size.
So I can change the size here, I can use another size so the reduce is a fixe parameter here.
Every ball is added to an array, so we have
got a variable called ballArray that contains all the balls.
At the initialization, when the page is loaded we call this createBalls function that will
fill the ball array.
The mainLoop is called 60 times per seconds and goes along the whole
ballArray and for each ball in the array, we will call ‘move’ that will change the x and
y position depending on the angle and the speed of the ball and we will draw the balls.
In the middle we test if the ball is colliding with a side and we change the angle.
I can just call createBalls with a large number: here I created 100 balls, 1000 balls, and I can change some of their properties.
This small example, that is 50 lines of code long, we can just take the function and add them inside the game framework.
So here, what we did is that we just added the ballArray variable inside the game framework, we called createBalls in the start function of the game framework, start function, I am inside here, so I create 160 balls or 1 ball so I can do whatever I like.
And we call the draw ball and move ball from inside the animation loop.
If you look at the animation loop, we clear the canvas, we draw the monster and we update the monster position to take into account the keys.
If I press some keys the monster moves.
So we call updateBalls that will move all the balls and draw all the balls.
So here, what we have is that we got the last example with the moving monster plus a set of enemies that are animated.
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.
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:
// Constructor function for balls
function Ball(x, y, vx, vy, diameter) {
// property of each ball: a x and y position, speeds, radius
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.radius = diameter/2;
// methods
this.draw = function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2*Math.PI);
ctx.fill();
};
this.move = function() {
// add horizontal increment to the x pos
// add vertical increment to the y pos
this.x += this.vx;
this.y += this.vy;
};
}
Using a constructor function makes it easy to build new balls as follows:
var b1 = new Ball(10, 10, 2, 2, 5); // x, y, vx, vy, radius
var b1 = new Ball(100, 130, 4, 5, 5);
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:
b1.draw();
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:
var canvas, ctx, width, height;
// array of balls to animate
var ballArray = [];
function init() {
canvas = document.querySelector("#myCanvas");
ctx = canvas.getContext('2d');
width = canvas.width;
height = canvas.height;
// try to change this number
createBalls(16);
requestAnimationFrame(mainLoop);
}
function createBalls(numberOfBalls) {
for(var i=0; i < numberOfBalls; i++) {
// Create a ball with random position and speed.
// You can change the radius
var ball = new Ball(width*Math.random(),
height*Math.random(),
(10*Math.random())-5,
(10*Math.random())-5,
30);
// add the ball to the array
ballArray[i] = ball;
}
}
function mainLoop() {
// clear the canvas
ctx.clearRect(0, 0, width, height);
// for each ball in the array
for(var i=0; i < ballArray.length; i++) {
var ball = ballArray[i];
// 1) move the ball
ball.move();
// 2) test if the ball collides with a wall
testCollisionWithWalls(ball);
// 3) draw the ball
ball.draw();
}
// ask for a new frame of animation at 60f/s
window.requestAnimationFrame(mainLoop);
}
function testCollisionWithWalls(ball) {
// left
if (ball.x < ball.radius) { // x and y of the ball are at the center of the circle
ball.x = ball.radius; // if collision, we replace the ball at a position
ball.vx *= -1; // where it's exactly in contact with the left border
} // and we reverse the horizontal speed
// right
if (ball.x > width - (ball.radius)) {
ball.x = width - (ball.radius);
ball.vx *= -1;
}
// up
if (ball.y < ball.radius) {
ball.y = ball.radius;
ball.vy *= -1;
}
// down
if (ball.y > height - (ball.radius)) {
ball.y = height - (ball.radius);
ball.vy *= -1;
}
}
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).
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:
var canvas, ctx, width, height;
// Array of balls to animate
var ballArray = [];
function init() {
...
}
function createBalls(numberOfBalls) {
for(var i=0; i < numberOfBalls; i++) {
// Create a ball with random position and speed.
// You can change the radius
var ball = new Ball(width*Math.random(),
height*Math.random(),
(2*Math.PI)*Math.random(), // angle
(10*Math.random())-5, // speed
30);
// We add it in an array
ballArray[i] = ball;
}
}
function mainLoop() {
...
}
function testCollisionWithWalls(ball) {
// left
if (ball.x < ball.radius) {
ball.x = ball.radius;
ball.angle = -ball.angle + Math.PI;
}
// right
if (ball.x > width - (ball.radius)) {
ball.x = width - (ball.radius);
ball.angle = -ball.angle + Math.PI;
}
// up
if (ball.y < ball.radius) {
ball.y = ball.radius;
ball.angle = -ball.angle;
}
// down
if (ball.y > height - (ball.radius)) {
ball.y = height - (ball.radius);
ball.angle =-ball.angle;
}
}
// constructor function for balls
function Ball(x, y, angle, v, diameter) {
this.x = x;
this.y = y;
this.angle = angle;
this.v = v;
this.radius = diameter/2;
this.draw = function() {
...
};
this.move = function() {
// add horizontal increment to the x pos
// add vertical increment to the y pos
this.x += this.v * Math.cos(this.angle);
this.y += this.v * Math.sin(this.angle);
};
}
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.