Jason Thomas

I like to make stuff

May 04, 2016 @ 18:20

How to animate projectiles with set range in a top-down JavaScript shooting game

I'm making a JavaScript game about surviving zombies. It's a game where you forrage for food and weapons and kill zombies. (Wait is kill the right word? Zombies are dead already.)

The game has one survivor and x amount of zombies that break down walls to get to the survivor. The game engine demo shows how the zombies behave in-game. Also, see Github repo.

The survivor object in my game will have ranged weapons. Weapons all have their own set range and fire when the user clicks the canvas.

So that's an interesting problem! How do you click on the canvas and get a projectile to go a set distance in the click direction? It should not matter if you click near or far from the human object - the projectile will travel the same distance (unless it hits a zombie).

Here is a working example of what I want to talk about. Notice that the projectiles reach a distance in a circle around the player object.

Trigonometry for n00bs (like me)

At school I never, ever, thought I'd use trigonometry so I didn't pay any attention. I had to relearn it to get this function working. Here is what I figured out.

Let's say you clicked on the canvas, which has an event listener.

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");

//Tells you where you've clicked
function getCursorPosition(canvas, event) {
    var rect = canvas.getBoundingClientRect();
    var x = event.clientX - rect.left;
    var y = event.clientY - rect.top;
    return [+x, +y];
}

//Listens for player click
canvas.addEventListener('click', function(ev) {
    ev.preventDefault();
    var click_pos = getCursorPosition(canvas, ev);
    player.fire_projectile(click_pos)
    //return false;
});

My survivor character has a function to fire a projectile from the current weapon, on user click. The player also has x and y coords, which become the starting point for each projectile

var player = {
    current_pos: [300,300], //x, y in context of canvas
    radius: 10,
    projectile_colour: 'black',
    projectile_tail_colour: 'grey',
    projectiles: [],
    fire_projectile: function(click_pos){
        var new_projectile = new create_projectile(this.current_pos, click_pos)
        this.projectiles.push(new_projectile)
    }
}

You can draw a straight line between your player and the click. In my case, the click is longer than the maximum distance that a projectile can travel from the player's weapon.

Click and form line with player

Since we know the x and y coords of the player and the click location, we can make a right angle triangle using those (AB and BC).

Make right angle triangle

The length of these sides is unimportant, but the angle (A) is important.

I use the following object constructor to make my projectiles.

var create_projectile = function(starting_pos, click_pos){
    this.starting_pos = starting_pos, //Your unit location, where the bullet starts
    this.click_pos = click_pos,
    this.opp_1 = this.click_pos[0] - this.starting_pos[0] //The opposite side from your player, to where you have clicked
    this.adj_1 = this.click_pos[1] - this.starting_pos[1] //The adjacent side from your player, to where you have clicked
    this.angle = 0,  //Between your unit, the hypotenuse (the bullet's path) and the adjacent side.
    this.target_pos = [], //Bullet's target destination
    this.range = 250, //Maximum distance
    this.steps = 12, //Number of times the bullet will appear
    this.steps_taken = 0, //How many times has this projectile appear?
    this.init = function(){  
        this.angle = Math.atan2(this.opp_1,this.adj_1) //Tan is a ratio between the opposite and adjacent. Note, javascript works with radians. to find degrees, times radians by ```* 180/Math.Pi
        this.target_pos[0] = Math.sin(this.angle) * this.range; //sin is a ratio between hypotenuse and opposite
        this.target_pos[1] = Math.cos(this.angle) * this.range; //cos is a ratio between hypotenuse and adjacent
    },
    this.init()
}

All of the calculations will happen inside the init() function. If my opposite side is BC and the adjacent side is AB on my triangle drawing, then we can use Math.atan2(BC,AB) to find the angle.

Know we have the angle, we can find a new right angle triangle (ADE).

Make smaller right angle triangle

For this new triangle, we already know the length of the hypotenuse (AD) because that's the projectile's set range. We want to find the opposite ED (in this case the bullet's landing y position) and adjacent AE (the landing x position).

That's all the trigonometry you need

What's cool about this is you only need to do this once for every projectile. Once we have the AE side and ED sides of the second triangle saved in the projectile object, we can use a ratio to find the bullet's position during animation.

Let's say a bullet will appear 12 times before it reaches its range and gets deleted. Let's also say the bullet is appearing for the fourth time. We can say the bullet is 1/3 from its destination.

You can define the bullet's x and y coordinates with the same ratio.

1 of 3 ratio

And as the bullet gets closer to its destination, the ratio gets closer to 6/12, and eventually 12/12

1 of 2 ratio

To see more code on this, go to the Github repo

Questions - sure, I'm on Twitter.

log in