For creating tanks, we have used images of tanks. First we should make a skeleton for tank, by creating an object constructor.
function tankinfo(){this.image = new Image();this.pos_index = 0;this.theta = 0;this.power = 50;this.turn = false;this.score = 0;this.numOfTurns = 0;this.nozzle = {x: 0, y: 0, alpha: 50*Math.PI/180};}var tankleft = new tankinfo();var tankright = new tankinfo();var tankHeight = 50, tankWidth = 80;
We have created two objects tankleft and tankright using tankinfo object constructor. The variables used in object constructor are:
- image - This will be the image of the tank
- pos_index - This will be the random index from 0 to canvas width on the terrain for placing tank
- theta - The inclination of the tank from horizontal level
- power - This the power set by user to attack
- turn - To specify if it is this user’s turn
- score - To keep track of the score of the player
- numOfTurns - Store the number of turns used by the player
- nozzle - An object with the position of nozzle and it’s inclination with respect to tank
Take a look at the image below explaining theta, alpha and various other properties :
(x,y) - The coordinates of the tank, in our case we store that particular index in our pos_index var so that we can access x and y using points array ( for eg points[pos_index].x)
(a,b) - The coordinates of our nozzle
tankWidth - 80
tankHeight - 50
(nx,ny) - nozzleX and nozzleY (Refer : creating nozzles)
Next we have to generate random positions for both tanks, give source for images of tanks and load them. Note that we have stored angles alpha and theta in radians.
function generateTankPoints(){tankleft.pos_index = Math.floor(Math.random()*canvas.scrollWidth/3)+10;tankright.pos_index = Math.floor((Math.random()*canvas.scrollWidth/3) + 2*canvas.scrollWidth/3)-85;}
Although we want the positions of both tanks to be random, we want them in a more sophisticated manner so that they do not get misplaced. We want to place the left tank in the first 1/3rd of the canvas and right tank in the last 1/3rd of the canvas leaving 1/3rd space in middle of both the tanks.
var loadedImages = 0;loadTanks();function loadTanks(){tankleft.image.onload = function(){ loadedImages++; goAhead(); }tankright.image.onload = function(){ loadedImages++; goAhead(); }tankleft.image.src = 'css/images/tank_left.png';tankright.image.src = 'css/images/tank_right.png';}
Next we should load the images first and only then execute the upcoming functions like drawing the tank. We keep track of the images loaded using the loadedImages variable. When it becomes 2 ( There are two images to load i.e. of left and right tank ) then only the goAhead function would execute as you will see it later in the goAhead function. The image.onload property is asynchronous so it will be switched to background and your code will keep running.
Now we draw the tanks at the coordinates that were just created. The logic for setting up the image of tank is as follows: We have the starting coordinates of the tank on the terrain, we choose a point on the terrain further away from above one and calculate the slope between these two points. That way we get the inclination of the terrain from the horizontal (theta) which we use to rotate the context and then draw the image.
function draw_tank(){var index, x1, y1, x2, y2, slope;//draw the left tankindex = tankleft.pos_index;x1 = points[index].x;y1 = canvas.scrollHeight - points[index].y;x2 = points[index+tankWidth].x;y2 = canvas.scrollHeight - points[index+tankWidth].y;slope = (y2-y1)/(x2-x1);ctx.save();tankleft.theta=Math.atan(slope);ctx.translate(x1, canvas.scrollHeight - y1);ctx.rotate(-tankleft.theta);ctx.drawImage(tankleft.image, 0, 0-53, tankWidth, tankHeight);ctx.restore();//draw the right tankindex = tankright.pos_index;x1 = points[index].x;y1 = canvas.scrollHeight - points[index].y;x2 = points[index+tankWidth].x;y2 = canvas.scrollHeight - points[index+tankWidth].y;slope = (y2-y1)/(x2-x1);ctx.save();tankright.theta=Math.atan(slope);ctx.translate(x1, canvas.scrollHeight - y1);ctx.rotate(-tankright.theta);ctx.drawImage(tankright.image, 0, 0-53, tankWidth, tankHeight);ctx.restore();}
Here (x1 , y1) is our initial coordinate point generated using loadTanks() function. There after we add tankWidth to that index and that coordinate is (x2 , y2) which we use for finding slope. Also observe how we calculate theta from the slope using Math.atan() method which returns angle in radians.
Few other context properties are explained below :
- ctx.save() - Saves the current state of context
- ctx.restore() - Restores the saved state of context
- ctx.translate() - Shifts the coordinate system to the given (x,y) coordinate (Remaps the (0,0) position on the canvas)
- ctx.rotate() - Rotate the coordinate system by given angle (in radians) Note :- If you pass a positive angle to ctx.rotate() the coordinate system will get rotated in clockwise direction.
- ctx.drawImage() - Draws an image on the canvas
- Syntax of ctx.drawImage() is: ctx.drawImage(img,sx,sy,swidth,sheight,dx,dy,dwidth,dheight);
We now call this function inside main.js to draw our tanks. Remember we talked about goAhead function, now we will create this function in main.js
generateTankPoints();function goAhead(){if(loadedImages == 2){draw_tank();}}
Add this code to your main.js file to draw the tanks. We have kept an if statement which checks if the loadedImages are 2 ( both left and right tank images have loaded ), only then will it call the function draw_tank()
After adding tanks your project will look like this,
Now we create nozzles for the tanks. The logic will be, we have the index of the image of the tank (i.e. bottom left point of tank) we translate there then rotate the context by theta radians. Thereafter we translate to the (nx,ny) point (refer the first image in creating tanks section). We will now rotate the context by alpha radians (alpha is the angle of nozzle with respect to the tank) and then draw a rectangle of width 40px and height 4px. This will be our nozzle. Take a look at the draw_nozzle() function in the code below,
var tankleft = new tankinfo();var tankright = new tankinfo();var tankHeight = 50, tankWidth = 80;var loadedImages = 0;loadTanks();function loadTanks(){tankleft.image.src = 'css/images/tank_left.png';tankright.image.src = 'css/images/tank_right.png';tankleft.image.onload = function(){ loadedImages++; goAhead(); }tankright.image.onload = function(){ loadedImages++; goAhead(); }}function tankinfo(){this.image = new Image();this.pos_index = 0;this.theta = 0;this.score = 0;this.numOfTurns = 0;this.power = 50;this.turn = false;this.nozzle = {x: 0, y: 0, alpha: 50*Math.PI/180};}function generateTankPoints(){tankleft.pos_index = Math.floor(Math.random()*canvas.scrollWidth/3)+10;tankright.pos_index = Math.floor((Math.random()*canvas.scrollWidth/3) + 2*canvas.scrollWidth/3)-85;}function draw_tank(){var index, x1, y1, x2, y2, slope;//draw the left tankindex = tankleft.pos_index;x1 = points[index].x;y1 = canvas.scrollHeight - points[index].y;x2 = points[index+tankWidth].x;y2 = canvas.scrollHeight - points[index+tankWidth].y;slope = (y2-y1)/(x2-x1);ctx.save();tankleft.theta=Math.atan(slope);ctx.translate(x1, canvas.scrollHeight - y1);ctx.rotate(-tankleft.theta);ctx.drawImage(tankleft.image, 0, 0-53, tankWidth, tankHeight);ctx.restore();//draw the right tankindex = tankright.pos_index;x1 = points[index].x;y1 = canvas.scrollHeight - points[index].y;x2 = points[index+tankWidth].x;y2 = canvas.scrollHeight - points[index+tankWidth].y;slope = (y2-y1)/(x2-x1);ctx.save();tankright.theta=Math.atan(slope);ctx.translate(x1, canvas.scrollHeight - y1);ctx.rotate(-tankright.theta);ctx.drawImage(tankright.image, 0, 0-53, tankWidth, tankHeight);ctx.restore();}function draw_nozzle(){var nozzleX, nozzleY;var theta, alpha, index;//draw nozzle for the left tankctx.save();index = tankleft.pos_index;theta = tankleft.theta;alpha = tankleft.nozzle.alpha;nozzleX = tankWidth-30;nozzleY = tankHeight-12;ctx.translate(points[index].x,points[index].y);ctx.rotate(-theta);ctx.translate(nozzleX, -nozzleY);ctx.rotate(-alpha);ctx.fillStyle = "#556B2F";ctx.fillRect(0,0,40,4);tankleft.nozzle.x = points[index].x + ((nozzleX + 40*Math.cos(alpha))*Math.cos(theta) - (nozzleY + 40*Math.sin(alpha))*Math.sin(theta));tankleft.nozzle.y = points[index].y - ((nozzleX + 40*Math.cos(alpha))*Math.sin(theta) + (nozzleY + 40*Math.sin(alpha))*Math.cos(theta));ctx.restore();//draw nozzle for the right tankctx.save();index = tankright.pos_index;theta = tankright.theta;alpha = tankright.nozzle.alpha;nozzleX = 30;nozzleY = (tankHeight-16);ctx.translate(points[index].x,points[index].y);ctx.rotate(-theta);ctx.translate(nozzleX, -nozzleY);ctx.rotate(alpha);ctx.fillStyle = "#556B2F";ctx.fillRect(0,0,-40,-4);nozzleY += 4;tankright.nozzle.x = points[index].x + ((nozzleX - 40*Math.cos(alpha))*Math.cos(-theta) + (nozzleY + 40*Math.sin(alpha))*Math.sin(-theta));tankright.nozzle.y = points[index].y - (-(nozzleX - 40*Math.cos(alpha))*Math.sin(-theta) + (nozzleY + 40*Math.sin(alpha))*Math.cos(-theta));ctx.restore();}
Now we are finished working with tanks.js file. So the above code is the final code of tanks.js
And your main.js file should look like this,
//We call the generate_terrain_pts function to generate points for terraingenerate_terrain_pts();//We call the draw_terrain function to draw the terraindraw_terrain();generateTankPoints();function goAhead(){if(loadedImages == 2){draw_tank();draw_nozzle();}}
Great, now run the project and it will look like this,