wall collision detection with html5 canvas [closed] - javascript
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I am making a game and there is a wall that I don't want the player to pass. I am using html5 canvas and have a player object to hold the x and y values. The wall is at x: 650 and y: 0. Since the player is 20x20 pixels when its x coordinate is 630, it touches the wall.
if(player.x > 630 && player.y <= 500) {
player.x = 630;
}
What is wrong with this code? I appreciate any help!
Answer
The code you have give is OK, there is nothing wrong with it. So I suspect the problem is elsewhere in the code, most likely in the movement code. If you are moving the player after the wall test and then display it, the player may start to creep into the wall, but without the rest of the code it is hard to know what is wrong with your code.
I have included more details on the correct way to do collision tests as there are two answers showing only a partial solution. It is there as a general guide to collision testing and may not be directly applicable to the question.
Inter frame movement
The correct way to reflect an object from a surface.
You must take into account that the ball is moving between frames and that the collision may have happened at any time during the previous frame. The ball's distance from the wall after the collision is dependent on when during the previous frame it hit the wall. This is important if the ball moves slowly or quickly.
var dx = 10; // delta x velocity of object in pixels
var wx = 10; // width of object in pixels
var px = 90; // position of object in pixels
var wallX = 105; // position of wall
px += dx; // move the ball. Its position is now 100.
// its right side is at px + wx = 110.
// test if it has it the wall
if(px+wx > wallX){
dx = -dx; // reflect delta x
// The object is 5 pixel into the wall.
// The object has hit the wall some time during the last frame
// We need to adjust the position as the ball may have been
// traveling away from the wall for some time during the last frame.
var dist = (px+wx)-wallX; // get the distance into the wall
px -= dist*2; // the object hit the wall at position 95 and has been
// traveling away since then so it is easy to just
// subtract 2 times the distance the ball entered the wall
// the above two lines can be done in one
// px -= ((px+wx)-wallX)*2;
}
Why it matters
Below is a simulation of a ball bouncing inside the canvas.
To illustrate that the ball is moving between frames it has been motion blurred to show its motion between frames. Please note this is not the perfect solution as the bounce is assumed to occur while the ball is in linear motion while infact it is in freefall and under constant acceleration. But it still conserves energy.
In the correct test the height the ball bounces back to, stays around the same over time. No energy is lost or gained.
Right click to turn off the inter frame adjustment and you will notice that the ball begins to decrease its height each frame. This is because at each collision the ball loses a little energy because it motion during the previous frame is not taken into account when positioning it after the collision test. It will settle down to a constant rate when the collision occurres at precisely the frame time. When that will be is very hard to determine in advance.
Left click to slow the simulation frame rate, left click again to return to normal.
The code below is not really part of the answer, it is there to demonstrate the effect of not correctly adjusting the position during collision test on the overall accuracy of the simulation.
// helper functions. NOT part of the answer
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
var mouseButton = 0;
canvas.addEventListener('mousedown',function(event){mouseButton = event.which;});
canvas.addEventListener('mouseup' ,function(){mouseButton = 0;});
canvas.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);
var currentSurface = ctx;
var createImage = function (w, h) {// create an canvas image of size w,h and attach context 2d
var image = document.createElement("canvas");
image.width = w;
image.height = h !== undefined?h:w;
currentSurface = image.ctx = image.getContext("2d");
return image;
}
var setColour = function (fillC, strokeC, lineW) {
currentSurface.fillStyle = fillC !== undefined ? fillC : currentSurface.fillStyle;
currentSurface.strokeStyle = strokeC !== undefined ? strokeC : currentSurface.strokeStyle;
currentSurface.lineWidth = lineW !== undefined ? lineW : currentSurface.lineWidth;
}
var circle = function(x,y,r,how){
currentSurface.beginPath();
currentSurface.arc(x,y,r,0,Math.PI*2);
how = how.toLowerCase().replace(/[os]/g,"l"); // how to draw
switch(how){
case "f": // fill
currentSurface.fill();
break;
case "l":
currentSurface.stroke();
break;
case "lf":
currentSurface.stroke();
currentSurface.fill();
break;
case "fl":
currentSurface.fill();
currentSurface.stroke();
break;
}
}
function createGradImage(size,col1,col2){
var image = createImage(size);
var g = currentSurface.createLinearGradient(0,0,0,currentSurface.canvas.height);
g.addColorStop(0,col1);
g.addColorStop(1,col2);
currentSurface.fillStyle = g;
currentSurface.fillRect(0,0,currentSurface.canvas.width,currentSurface.canvas.height);
return image;
}
function createColouredBall (ballR,col) {
var ball = createImage(ballR*2);
var unit = ballR/100;
setColour("black");
circle(ballR,ballR,ballR,"f");
setColour("hsl("+col+",100%,30%)");
circle(ballR-unit*3,ballR-unit*3,ballR-unit*7,"f");
setColour("hsl("+col+",100%,50%)");
circle(ballR-unit*10,ballR-unit*10,ballR-unit*16,"f");
setColour("White");
circle(ballR-unit*50,ballR-unit*50,unit*16,"f");
return ball;
}
//===================================
// _
// /_\ _ _ ____ __ _____ _ _
// / _ \| ' \(_-< V V / -_) '_|
// /_/ \_\_||_/__/\_/\_/\___|_|
//
// ==================================
// Answer code
// lazy coder variables
var w = canvas.width;
var h = canvas.height;
// ball is simulated 5cm
var pixSize = 0.24; // in millimeters for simulation
// Gravity is 9.8 ms^2 so convert to pixels per frame squared
// Assuming constant 60 frames per second. ()
var gravity = 9800*pixSize/60;
gravity *= 0.101; // because Earth's gravity is stupidly large let's move to Pluto
// ball 5cm
var ballR = (25/pixSize)/2; // radius is 2.5cm for 5cm diamiter ball
var ballX = w/2; // get center of canvas
var ballY = ballR+3; // start at the top
var ballDX = (Math.random()-0.5)*15; // start with random x speed
ballDX += ballDX < 0 ? -5 : 5; // make sure it's not too slow
var ballDY = 0; // star with no downward speed;
var ballLastX = ballX;
var ballLastY = ballY;
//create an image of the Ball
var ball = createColouredBall(ballR,Math.floor(Math.random()*360)); // create an image of ball
// create a background. Image is small as it does not have much detail in it
var background = createGradImage(16,"#5af","#08C");
// time to run for
var runFor = 10*60; // ten secons yimes 60 frames per second
// draws the ball motion blured. This introduces extra complexity
var drawMotionBlur = function(image,px,py,dx,dy,steps){
var i,sx,sy;
sx = dx / steps;
sy = dy / steps;
px -= dx; // move back to start position
py -= dy;
ctx.globalAlpha = 1/(steps*0.8); // set alpha to slightly higher for each step
for(i = 0; i < steps; i+= 1){
ctx.drawImage(image,px+i*sx,py+i*sy);
}
ctx.globalAlpha = 1; // reset alpha
}
// style for text
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.textAlign = "center";
ctx.lineJoin = "round"; // stop some letters getting ears.
ctx.lineWidth = 3;
ctx.textBaseline = "bottom";
var textCenterX = w/2;
var maxHeight = Infinity;
var lastMaxHeight = ballY;
var slowMotion = false; // slow motion flag
var frameTravel = true; // use frame travel in collision test
var update = function(){
var blurSteps = 10; // motion blur ball render steps
const bSteps = 10;
if(mouseButton === 1){
slowMotion = ! slowMotion;
mouseButton = 0;
}
if(mouseButton === 3){
frameTravel = ! frameTravel;
ballX = w/2; // get center of canvas
ballY = ballR+3; // start at the top
ballDY = 0; // start at 0 y speed
mouseButton = 0;
}
// clear the canvas with background canvas image
ctx.drawImage(background,0,0,w,h);
ballDY += gravity; // accelrate due to grav
// add deltas to ball position
ballX += ballDX;
ballY += ballDY;
// test for collison on left and right walls. Need to
// ajust for motion blur
if (ballX < ballR) {
ballDX = -ballDX; // refect delta x
if (frameTravel) { // if using frame travel time
// blur the outward traveling ball only for the time it has been traveling away
blurSteps = Math.ceil(10 * ((ballX - ballR) / -ballDX));
// get position it should have traveled since
ballX -= (ballX - ballR) * 2;
}else{
ballX = ballR; // move ball to touching wall
blurSteps = 1; // there is no outward motion
}
} else
if (ballX > w - ballR) {
ballDX = -ballDX;
if (frameTravel) { // if using frame travel time
// blur the outward traveling ball only for the time it has been traveling away
blurSteps = Math.ceil(10 * ((ballX - (w - ballR)) / -ballDX));
ballX -= (ballX - (w - ballR)) * 2;
}else{
ballX = w - ballR; // move ball to touching wall
blurSteps = 1; // there is no outward motion
}
}
if (ballY > h - ballR) {
ballDY = -ballDY;
// to show max height
lastMaxHeight = maxHeight;
maxHeight = Infinity;
if (frameTravel) { // if using frame travel time
// blur the outward traveling ball only for the time it has been traveling away
blurSteps = Math.ceil(10 * ((ballY - (h - ballR)) / -ballDY));
ballY -= (ballY - (h - ballR)) * 2;
}else{
ballY = h - ballR; // move ball to touching wall
blurSteps = 1; // there is no outward motion
}
}
// draw the ball motion blured
drawMotionBlur(
ball, // image to draw
ballX - ballR, // offset radius
ballY - ballR,
ballDX * (blurSteps / bSteps), // speed and adjust for bounced
ballDY * (blurSteps / bSteps),
blurSteps // number of blurs
);
// show max height. Yes it is min but everything is upside down.
maxHeight = Math.min(maxHeight,ballY);
lastMaxHeight = Math.min(ballY,lastMaxHeight);
// show max height
ctx.font = "12px arial black";
ctx.beginPath();
ctx.moveTo(0,lastMaxHeight - ballR);
ctx.lineTo(w,lastMaxHeight - ballR);
ctx.stroke();
ctx.fillText("Max height.",40,lastMaxHeight - ballR + 6);
var str = ""; // display status string
if(slowMotion){ // show left click help
str += "10fps."
ctx.fillText("click for 60fps.",textCenterX,43);
}else{
str += "60fps."
ctx.fillText("click for 10fps.",textCenterX,43);
}
if(frameTravel){ // show mode and right click help
str += " Mid frame collision.";
ctx.fillText("Right click for Simple collision",textCenterX,55);
}else{
str += " Simple collision.";
ctx.fillText("Right click for mid frame collision",textCenterX,55);
}
// display help text
ctx.font = "18px arial black";
ctx.strokeText(str,textCenterX,30);
ctx.fillText(str,textCenterX,28);
if(slowMotion){
setTimeout(update,100); // show in slow motion
}else{
requestAnimationFrame(update); // request next frame (1/60) seconds from now
}
// all done
}
update(); // to start the ball rolling
.canC { width:500px; height:500px;}
<canvas class="canC" id="canV" width=500 height=500></canvas>
For a wall running along the X axis at the bottom (Y = 0) of a 650 x 650 field, we would want:
if (player.y <= 20) {
player.y = 20;
}
For a wall running along the Y axis at the left side (X = 0) of a 650 x 650 field, we would want:
if (player.x <= 20) {
player.x = 20;
}
For a wall running along the Y axis at the right side (X = 650) of a 650 x 650 field, we would want:
if (player.x >= 630) {
player.x = 630;
}
For a wall running along the X axis at the top (Y = 650) of a 650 x 650 field, we would want:
if (player.y >= 630) {
player.y = 630;
}
This code is similar to the code I use, if we attach horizontal (h) and vertical (v) velocity attributes to the player object we can multiply them by negative one to get them to bounce off of the wall if the player is going to go beyond the bounds. Or if you want it to stop, set them equal to zero at the wall.
//player.x+player.h gives us the future position of the player
if (player.x+player.h>630||player.x+player.h<0)
{
player.h*=-1;//bounce
//stop player.h=0;
}
if (player.y+player.v>500||player.y+player.v<0)
{
player.v*=-1;
//stop player.v=0;
}
//new player coordinates
player.x+=player.h;
player.y+=player.v;
Hope this helps.
Related
How to detect the side on which collision occured
This is my first post so I'm trying to make my problem as clear as possible. I'm making a game and I want to improve my collision detection. This is because I want to check what side is being hit and stop the player from moving past it without using something general like if(collision(player, enemy)) player.x = enemy.x - player.w(width) because if the player were to collide with the top it wouldn't keep the player on top. In the code it checks if any one of the statements is true and then returns it but it doesn't tell me which statement was the one that was equal to true so I can stop the player from moving accordingly, if that makes sense. If you have a more efficient collision detection for me to use it would be greatly appreciated. I've already tried to make a position variable to be equal to whatever side gets collided into and then stop the player from moving past it but it only works for the left side and won't let my player jump over the enemy or block. function collision(object1, object2) { return !( object1.x > object2.x + object2.w || object1.x + object1.w < object2.x || object1.y > object2.y + object2.h || object1.y + object1.h < object2.y ) } //Only works for the left side if(collision(player, enemy)) player.x = enemy.x - player.w I expect it to be able to tell me what side is being collided into and then either stop the player from moving past/into it and for the player to be able to be on top of the block/enemy without just being pushed to the left.
You'll want to calculate the distance between the x's and y's and also use the minimum distance that they could be colliding along each axis to find the depth along both axes. Then you can pick the smaller depth and move along that one. Here's an example: if(collision(player, enemy)){ // Most of this stuff would probably be good to keep stored inside the player // along side their x and y position. That way it doesn't have to be recalculated // every collision check var playerHalfW = player.w/2 var playerHalfH = player.h/2 var enemyHalfW = enemy.w/2 var enemyHalfH = enemy.h/2 var playerCenterX = player.x + player.w/2 var playerCenterY = player.y + player.h/2 var enemyCenterX = enemy.x + enemy.w/2 var enemyCenterY = enemy.y + enemy.h/2 // Calculate the distance between centers var diffX = playerCenterX - enemyCenterX var diffY = playerCenterY - enemyCenterY // Calculate the minimum distance to separate along X and Y var minXDist = playerHalfW + enemyHalfW var minYDist = playerHalfH + enemyHalfH // Calculate the depth of collision for both the X and Y axis var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY // Now that you have the depth, you can pick the smaller depth and move // along that axis. if(depthX != 0 && depthY != 0){ if(Math.abs(depthX) < Math.abs(depthY)){ // Collision along the X axis. React accordingly if(depthX > 0){ // Left side collision } else{ // Right side collision } } else{ // Collision along the Y axis. if(depthY > 0){ // Top side collision } else{ // Bottom side collision } } } } Working example Here's a working example that you can play around with. Use the arrow keys to move the player around. player = { x: 9, y: 50, w: 100, h: 100 } enemy = { x: 100, y: 100, w: 100, h: 100 } output = document.getElementById("collisionType"); canvas = document.getElementById("canvas"); ctx = canvas.getContext("2d") function collision(object1, object2) { return !( object1.x > object2.x + object2.w || object1.x + object1.w < object2.x || object1.y > object2.y + object2.h || object1.y + object1.h < object2.y ) } function draw() { ctx.clearRect(0, 0, 400, 400) ctx.lineWidth = "5" ctx.beginPath(); ctx.strokeStyle = "red"; ctx.rect(player.x, player.y, player.w, player.h); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = "blue"; ctx.rect(enemy.x, enemy.y, enemy.w, enemy.h); ctx.stroke(); } function handleCollision() { if (collision(player, enemy)) { var playerHalfW = player.w / 2 var playerHalfH = player.h / 2 var enemyHalfW = enemy.w / 2 var enemyHalfH = enemy.h / 2 var playerCenterX = player.x + player.w / 2 var playerCenterY = player.y + player.h / 2 var enemyCenterX = enemy.x + enemy.w / 2 var enemyCenterY = enemy.y + enemy.h / 2 // Calculate the distance between centers var diffX = playerCenterX - enemyCenterX var diffY = playerCenterY - enemyCenterY // Calculate the minimum distance to separate along X and Y var minXDist = playerHalfW + enemyHalfW var minYDist = playerHalfH + enemyHalfH // Calculate the depth of collision for both the X and Y axis var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY // Now that you have the depth, you can pick the smaller depth and move // along that axis. if (depthX != 0 && depthY != 0) { if (Math.abs(depthX) < Math.abs(depthY)) { // Collision along the X axis. React accordingly if (depthX > 0) { output.innerHTML = "left side collision" } else { output.innerHTML = "right side collision" } } else { // Collision along the Y axis. if (depthY > 0) { output.innerHTML = "top side collision" } else { output.innerHTML = "bottom side collision" } } } } else { output.innerHTML = "No collision" } } keyStates = [] function handleKeys() { if (keyStates[39]) { player.x += 2 //Move right } else if (keyStates[37]) { player.x -= 2 //Move left } if (keyStates[38]) { player.y -= 2 //Move up } if (keyStates[40]) { player.y += 2 //Move down } } function main() { handleKeys(); draw(); handleCollision(); window.requestAnimationFrame(main); } window.onkeydown = function(e) { keyStates[e.keyCode] = true } window.onkeyup = function(e) { keyStates[e.keyCode] = false } main(); <h2 id="collisionType"></h2> <canvas id="canvas" width='300' height='300'></canvas> Reacting to the collision Now that you know the side the collision happened on, it should be fairly trivial to decide how to react. It would be very similar to what you are currently doing for the left side just flip some signs around and change the axis. Other Considerations You may want to take into account your player's velocity (if it has one) otherwise the detection may fail. If the player's velocity is too high, it might 'tunnel' through the enemy and no collision will be detected. The player's movement can also look jittery if the velocity is not stopped upon collision Can your objects rotate or have more than 4 sides? If so, you'll probably want to use another method as described below. Here's a good answer to another post that talks in depth about collision engines Other Methods As for other collision detection methods, there's quite a few but one that comes to mind is Separating Axis Theorem which is a little more complex than what you have but will work with more complex convex shapes and rotation. It also tells you the direction and distance needed to move to resolve the collision. Here's a site that has interactive examples and goes in-depth on the subject. It doesn't appear to give a full implementation but those can be found other places.
How to add a logic statement to an object entering a moving area
I am trying to make a game. The object of the game is to move the square across the screen without hitting a raindrop falling from the roof. How do i make it so that if one of the raindrops enters the square, the square returns to the beginning of the canvas or x =0. Here is the code: var canvas = document.getElementById('game'); var ctx = canvas.getContext('2d'); var WIDTH = 1000; var HEIGHT = 700; var x = 0; var y = HEIGHT-20; var xPos = [0]; var yPos = [0]; var speed = [1]; var rainDrops = 50; var rectWidth = 20; var rectHeight = 20; for (var i = 0; i < rainDrops; i++) { xPos.push(Math.random()* WIDTH); yPos.push(0); speed.push(Math.random() * 5); } function rainFall () { window.requestAnimationFrame(rainFall); ctx.clearRect(0, 0, WIDTH, HEIGHT); for (var i = 0; i <rainDrops; i++) { //Rain ctx.beginPath(); ctx.arc(xPos[i], yPos[i], 3, 0, 2 * Math.PI); ctx.fillStyle = 'blue'; ctx.fill(); //Rain movement yPos[i]+=speed[i]; //Box ctx.fillStyle = 'red'; ctx.fillRect(x, y, rectWidth, rectWidth); if (yPos[i] > HEIGHT) { yPos[i]= 0; yPos[i]+=speed[0]; } //This is the part where I need the Help!!!!!!!!! if (){ x = 0; } } }; //Move Object function move (e) { if (e.keyCode === 37) { ctx.clearRect (0, 0, WIDTH, HEIGHT); x -=10; } if (e.keyCode === 39) { ctx.clearRect (0, 0, WIDTH, HEIGHT); x+=10; } canvas.width=canvas.width } //Lockl Screen window.addEventListener("keydown", function(e) { // Lock arrow keys if( [37,39,].indexOf(e.keyCode) > -1) { e.preventDefault(); } }, false); rainFall(); document.onkeydown = move; window.addEventListener("load", doFirst, false);
Conditional statements I am never too sure how to answer these types of questions. As you are a beginner I don't want to overwhelm you with code and techniques, but at the same time I don't want to give an answer that perpetuates some bad techniques. The short answer So first the simple answer from your code where you wrote //This is the part where I need the Help!!!!!!!!! // the test checks the center of the drop // if drop is greater than > top of player (y) and (&&) // drop x greater than > left side of player x and (&&) drop is less than < // right side of player (x + rectWidth) then drop has hit player if (yPos[i] > y && xPos[i] > x && xPos[i] < x + rectWidth ){ x = 0; // move player back } BTW you are drawing the player rectangle for each rain drop. You should move that draw function outside the loop. The long answer Hopefully I have not made it too confusing and have added plenty of comments about why I did this and that. To help keep everything organised I separate out the various elements into their own objects. There is the player, rain, and keyboard handler. This is all coordinated via the mainLoop the is called once a frame (by requestAnimationFrame) and calls the various functions to do all that is needed. The player object holds all the data to do with the player, and functions to draw and update the player (update moves the player) The rain object holds all the rain in an array called rain.drops it has functions to draw and update the rain. It also has some functions to randomize a drop, and add new drops. To test if the rain has hit the player I do it in the rain.update function (where I move the rain) I don`t know what you wanted to happen when the rain hits the player so I just reset the rain drop and added 1 to the hit counter. I first check if the bottom of the rain drop drop.y + drop.radius is greater than the top of the player if(drop.y + drop.radius >= player.y){ This makes it so we dont waste time checking rain that is above the player. The I test for the rain in the x direction. The easiest is the test the negative (if the rain is not hitting the player) as the logic is a little simplier. If the right side of the drop is to the left of the left side of the player, or (use || for or) the left side of the drop is to the right of the right side of the player than the drop can not be hitting the player. As we want the reverse condition we wrap it in a brackets a put a not in front if(! ( ... ) ) The test is a little long so I break it into 2 lines for readability. // drop is a single rain drop player is the player if (drop.y + drop.radius >= player.y) { if ( ! (drop.x + drop.radius < player.x || drop.x - drop.radius > player.x + player.width) ) { // code for player hit by rain in here } } The rain.update function also checks if the rain has hit the bottom of the canvas and resets it if so. Demo I copied your code from in the question and modified it. addEventListener("load",function(){ // you had onload at the bottom after all the code that gets the canvas // etc. Which kind of makes the on load event pointless. Though not needed // in this example I have put it in how you can use it for a web page. // Using onload event lets you put the javascript anywhere in the HTML document // if you dont use onload you must then only put the javascript after // the page elements you need eg Canvas. var canvas = document.getElementById('gameCanvas'); var ctx = canvas.getContext('2d'); ctx.font = "20px arial"; var frameCount = 0; // counts the frames var WIDTH = canvas.width; var HEIGHT = canvas.height; var currentMaxDrops = 5; // rain will increase over time const numberFramesPerRainIncrease = 60 * 5; // long name says it all. 60 frames is one second. const maxRainDrops = 150; // max number of rain drops // set up keyboard handler addEventListener("keydown", keyEvent); addEventListener("keyup", keyEvent); requestAnimationFrame(mainLoop); // request the first frame of the animation (get it all going) //========================================================================================== // Setup the keyboard input stuff const keys = { // list of keyboard keys to listen to by name. ArrowLeft : false, ArrowRight : false, } function keyEvent(event){ // the key event argument event.code hold the named key. if(keys[event.code] !== undefined){ // is this a key we want event.preventDefault(); // prevent default browser action keys[event.code] = event.type === "keydown"; // true if keydown false if not } } //========================================================================================== const player = { // object for everything to do with the player x : 0, // position y : HEIGHT - 20, width : 20, // size height : 20, speed : 4, // speed per frame color : "red", showHit : 0, // when this is > 0 then draw the player blue to indicate a hit. // This counter is counted down each frame so setting its value // determins how long to flash the blue hitCount : 0, // a count of the number of drops that hit the player. status(){ // uses hit count to get a status string if(player.hitCount === 0){ return "Dry as a bone."; } if(player.hitCount < 5){ return "A little damp."; } if(player.hitCount < 15){ return "Starting to get wet."; } return "Soaked to the core"; }, draw(){ // draw the player if(player.showHit > 0){ player.showHit -= 1; // count down show hit ctx.fillStyle = "blue"; }else{ ctx.fillStyle = player.color; } ctx.fillRect(player.x,player.y,player.width,player.height); }, update(){ // this updates anything to do with the player // Not sure how you wanted movement. You had it so that you move only when key down events // so I have done the same if(keys.ArrowLeft){ player.x -= player.speed; // move to the left keys.ArrowLeft = false; // turn off the key. If you remove this line then will move left while // the key is down and stop when the key is up. if(player.x < 0){ // is the player on or past left side of canvas player.x = 0; // move player back to zero. } } if(keys.ArrowRight){ player.x += player.speed; // move to the right keys.ArrowRight = false; // turn off the key. If you remove this line then will move right while // the key is down and stop when the key is up. if(player.x + player.width >= WIDTH){ // is the player on or past right side of canvas player.x = WIDTH - player.width; // move player back to inside the canvas. } } } } //========================================================================================== const rain = { // object to hold everything about rain numberRainDrops : 50, drops : [], // an array of rain drops. randomizeDrop(drop){ // sets a drop to random position etc. drop.x = Math.random() * WIDTH; // random pos on canvas drop.y = -10; // move of screen a little so we dont see it just appear drop.radius = Math.random() *3 + 1; // give the drops a little random size drop.speed = Math.random() * 4 + 1; // and some speed Dont want 0 speed so add 1 return drop; }, createDrop(){ // function to create a rain drop and add it to the array of drops if(rain.drops.length < currentMaxDrops){ // only add if count is below max rain.drops.push(rain.randomizeDrop({})); // create and push a drop. {} creates an empty object that the function // randomizeDrop will fill with the starting pos of the drop. rain.numberRainDrops = rain.drops.length; } }, draw(){ // draw all the rain ctx.beginPath(); // start a new path ctx.fillStyle = 'blue'; // set the colour for(var i = 0; i < rain.drops.length; i ++){ var drop = rain.drops[i]; // get the indexed drop ctx.arc(drop.x, drop.y, drop.radius, 0, 2 * Math.PI); ctx.closePath(); // stops the drops rendered as one shape } ctx.fill(); // now draw all the drops. }, update(){ for(var i = 0; i < rain.drops.length; i ++){ var drop = rain.drops[i]; // get the indexed drop drop.y += drop.speed; // move down a bit. if(drop.y + drop.radius >= player.y){ // is this drop at or below player height // checks if the drop is to the left or right of the player // as we want to know if the player is hit we use ! (not) // Thus the next if statement is if rain is not to the left or to the right then // it must be on the player. if(!(drop.x + drop.radius < player.x || // is the rigth side of the drop left of the players left side drop.x - drop.radius > player.x + player.width)){ // rain has hit the player. player.hitCount += 1; player.showHit += 5; rain.randomizeDrop(drop); // reset this drop. } } if(drop.y > HEIGHT + drop.radius){ // is it off the screen ? rain.randomizeDrop(drop); // restart the drop } } } } function mainLoop () { // main animation loop requestAnimationFrame(mainLoop); // request next frame (don`t need to specify window as it is the default object) ctx.clearRect(0, 0, WIDTH, HEIGHT); frameCount += 1; // count the frames // when the remainder of frame count and long name var is 0 increase rain drop count if(frameCount % numberFramesPerRainIncrease === 0){ if(currentMaxDrops < maxRainDrops){ currentMaxDrops += 1; } } rain.createDrop(); // a new drop (if possible) per frame rain.update(); // move the rain and checks if the player is hit player.update(); // moves the player if keys are down and check if play hits the side of the canvas player.draw(); // draw player rain.draw(); // draw rain after player so its on top. ctx.fillStyle = "black"; ctx.fillText("Hit " + player.hitCount + " times.",5,20); ctx.setTransform(0.75,0,0,0.75,5,34); // makes status font 3/4 size and set position to 5 34 so I dont have to work out where to draw the smaller font ctx.fillText(player.status(),0,0); // the transform set the position so text is just drawn at 0,0 ctx.setTransform(1,0,0,1,0,0); // reset the transform to the default so all other rendering is not effected }; }); canvas { border : 2px black solid; } <canvas id="gameCanvas" width=512 height=200></canvas> Hope this was helpfull.
Animated footsteps in html svg
Is it possible to create small animated footsteps in HTML SVG or Canvas. I am just starting out with these technologies, and it is very much necessary for a small project i am working on. My current idea was to create and use a "gif" of animated footsteps. But i would like to know if it can be achieved in any other way through HTML/CSS/JS PS : The footsteps i keep mentioning should be similar to the footsteps that appear on the "Marauders Map" in harry potter Movies. Thanks for any help
Walk about. I have never seen the movie you talk about so I am guessing what you are after. To do it on the canvas is easy, and I am more than happy to write an example of how it's done for you. Draw a foot You need an image of a foot, or some way to draw a foot. I used a set of shapes I happened to have to draw a foot. Then create a function that draws the foot at a location and in a direction. You also need to specify if its a left or right step. If you have a foot image you want to use just replace the code in the drawFoot function after the comment // draw the foot... with ctx.drawImage(footImage,-footImage.width / 2, -footImage.height / 2); You may have to scale the image, and the foot image should be of the left foot toes pointing to the right of screen Path The path to walk along is just an array of points, [x,y,x,y...] in order that define the path to travel along, They are just a guide as each foot step is a fixed distance apart and the points can be any distance appart. Animate Each step is some time apart (demo is 500ms or half a second). I use setTimeout to call the function to draw the next step. When the function is called I find the next position on the path and draw the next foot step. When the walk has gone past the end of the path I clear the canvas and restart. Demo It's self explanatory in the demo. I track two positions on the path, one is where the foot step is, and the other is just ahead and used to get the direction the foot should be drawn. The function that gets the distance along the path is recursive because the path points are not uniform in distance apart, so may have to skip line segments if the distance to travel is greater than the current or next or few line segments. // create a canvas and add it to the DOM var createImage=function(w,h){var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i;} var canvas = createImage(1024,512); var ctx = canvas.ctx; document.body.appendChild(canvas); // shapes for the foot. Left foot const foot = [ [50.86759744022923,-21.798383338076917,54.16000854997335,-23.917474843549847,57.778065334829385,-25.310771525314685,61.579706305344985,-24.823762596975733,65.0168431653022,-22.69100336700319,65.22736925598322,-19.777045647294724,63.09649708656406,-16.826669881834157,59.66512409715465,-15.356252875428147,56.12216018899968,-14.92259970211097,52.06404407417057,-16.231839553378,50.2945579442406,-18.938589556263246], [60.12039562389232,-12.45714817900668,63.92394094034509,-14.994440399059425,68.75013312287521,-15.737202635924493,73.10937504616481,-14.878459739068003,76.36064492186433,-12.559833524837757,77.6863729180915,-9.181208344485064,75.4625672565435,-5.673231626251427,71.79886053681197,-3.7381471592011817,66.8618942467243,-3.4903609701416993,62.29264392518654,-5.1248680438596885,58.98975517513061,-8.760952968033395], [65.57414109270752,1.1797411270282658,69.37768640916029,-1.3575510930244814,74.20387859169041,-2.1003133298895467,78.56312051498001,-1.241570433033059,81.81439039067953,1.0770557811971881,83.14011838690669,4.455680961549877,80.9163127253587,7.963657679783514,77.25260600562717,9.898742146833756,72.3156397155395,10.146528335893239,67.74638939400174,8.512021262175251,64.44350064394581,4.875936338001546], [65.89915812212375,15.917267748549033,69.97688522977245,12.635767809535894,76.25232402290966,11.736330263416008,81.47328014710077,12.477566678331382,86.09545897877996,15.579569438835726,86.99032987455637,21.425830739951824,83.82647235747945,24.97297628549917,79.18353918133074,27.064789354098487,73.69821507617947,27.23418460503707,68.46309469655478,24.972976285499172,64.88602415530316,20.55351481505123], [58.48881251239855,36.09589759380796,65.7080603859302,29.82038065752831,74.19222753183148,28.331948884004674,82.75081761131048,31.085582242549528,88.10923922724437,34.28575070762116,91.45825273720305,41.65358042953028,87.067323913035,47.45853718012533,79.77391671356942,50.28659303297933,71.73628428966856,50.06332546564875,64.14518700042888,47.45853718012533,60.12637078847845,42.69549574373964], [-73.48953442580058,20.579088801900756,-80.48690958722098,13.959950657259256,-81.93681598574045,6.269142804242765,-81.49554012532147,-1.6107832746678348,-77.90207999991593,-9.181527272091415,-71.6611785393426,-14.98115303708649,-64.60076477263831,-17.880965834138024,-57.35123278004056,-19.078714598132443,-50.09111381970131,-19.902008890566687,-42.96765884171789,-19.08249722231963,-35.655087439697766,-18.51514254492067,-28.90987071615029,-18.578181953551955,-21.74774447703016,-19.60773669210723,-14.309090001741001,-20.364210136314323,-6.933479190821032,-21.688037717705875,0.33383104043200396,-22.888118253462963,7.772483543580581,-23.77067027395373,15.274173171058239,-24.338024951681817,22.460665755024706,-24.590182586206954,29.710197747622452,-23.707630865368966,36.8557533915613,-22.565778766908277,44.16832856198768,-19.28772830000901,51.48089996424725,-14.370654426435763,56.713170880643965,-7.499358885625703,60.24016927073608,-0.41616112008138906,61.75311234056134,6.518193267525415,62.38350642684393,13.515567625813127,61.67231220934209,20.50500542283382,59.08769637136125,27.56541945045329,54.35974072401175,34.87799085169213,48.686193947196124,39.41682827314464,41.87793781501736,42.379680478815025,34.313208779263185,43.26223219965301,27.063676786665432,42.25360166155246,19.625026568173826,38.28211891778152,13.457927624759604,31.720792179781256,9.486440924356895,25.290772320002496,6.019273449215143,18.7346738223298,0.21964785513691965,13.565442314564446,-5.832135373466421,9.467880753530935,-12.632472285211591,8.882365666622222,-19.188577231399584,11.277861070017005,-26.31203040678842,14.49287091019486,-32.99420772170462,17.833959567652954,-39.2981485848331,20.670732956060768,-45.854247082486715,23.192309301312157,-52.47338498877162,24.89437333435685,-59.5968381641068,25.020452151619416,-67.33461307855538,23.697386555044208], ] const footScale = 0.2; // how big the foot is const stepLen = 60; // distance between steps var stepCount = 0; // current set num so left and right can be known var stepTime = 500; // time ms between steps // path to walk const path = [56.20191403126277,162.4451015086347,83.38688266344644,172.80127602121507,107.98280549274708,192.86637096042372,121.57528916149568,221.34586055208607,124.81159479691195,256.2979614145819,141.64038410107662,293.8391067854107,168.82535143857336,311.9624183437419,203.77745230106916,336.5583411729056,238.0822920364817,344.9727358249879,278.2124819156436,341.0891690624884,316.40088841355566,329.43846877498976,343.58585575105235,310.6678960895754,370.77082308854904,275.7157952270795,359.12012280105046,244.64726112708325,344.23311687813566,207.10611575625444,355.23655603855093,168.9177092583423,394.0722236635463,137.2019140312628,438.0859803052077,137.84917515834604,487.27782596353507,174.0957982750084,507.9901820301992,221.9931216791693,513.1682710468652,269.243183956247,500.87030963228347,318.43502961457443,480.1579535656192,354.68165273123674,453.62744426338134,396.86543776550707,414.1445200788371,427.9340428271046,372.7198079555102,447.3518767949864,320.2916566617712,442.173787778395,272.39433325761024,427.9340429825634,218.02439858261675,441.5265266513118,185.66134222845398,472.59506075130815,160.418158272207,514.6670340117198,168.2291881671332,557.5405924870362,200.59229872785795,598.9654951914354,232.9553551615553,627.4449850627141,273.08554504131894,651.3936467669101,320.3356073183967,663.0443470544095,368.2329307225576,663.6916081814927,417.4247763808851,649.4518633856611,460.7912718954633,626.150462810664,509.33585642670744,593.1401453294179,530.6954736204549,556.8935222127556,559.9273870166451,517.9197870310509,582.4287517306153,484.11964343037323,597.1560832290169,459.03222473087396,621.0274949086466,438.11039022321154,651.3689440081158,429.43667093843567,686.3731150817684,432.05029606733103,726.1878666750503,421.6902139845064,748.5744620042316,397.8927935245363,778.6337708564557,367.5111094723503,792.6287871481064,335.0802046803193,795.4641381478963,294.8623601925252,790.3177294792127,255.26933447013639,776.3228370821212,225.344431214269,746.3711518226298,192.73203550406103,713.7991149596966,199.06094085265394,674.3068624609349,207.5062077919911,638.4763261746227,190.31310645331482,613.6509940547375,146.74931837304698,621.5992452450397,103.454341485492,665.5124383180124,60.96567428151931,716.1845355322713,48.49595708249788,763.6383682758693,51.23726133320403,810.1045243122669,71.53440096982465,842.407749982487,97.97907893142005,879.5993779794437,131.14717279570036,903.6790094213126,175.24174017377706,915.9471803279671,219.31612086267396,902.1335310600084,270.1561321514687,880.1365756762476,315.0232456643523,884.5103070340778,370.89556334366307,909.7723644212043,407.9691345807976,947.0675376346722,439.8492118274288,990.6429384281869,439.26727537005956,1036.8675099917996,414.23364852545194,1070.3264506272462,387.0500494883014,1100.6074853525497,351.4546920217324,1119.943854180156,306.7958514306488,1128.5371035594999,259.67124425611814,1122.6651029017848,208.79760059460207,1106.8898009575623,162.16340658911932,1082.4004208812885,108.81054339506417,1050.2046949092428,81.72759371897288,1016.627194271211,46.42875173061529]; const pLen = path.length; // fix the path so it starts at zero for(var i = 2; i < pLen; i += 2){ path[i] -= path[0]; path[i+1] -= path[1]; } path[0] = path[1] = 0; // tracks the foot pos var pos = { x : path[0], y : path[1], index : 0, }; // tracks the foot pointing to pos var pos1 = { x : path[0], y : path[1], index : 0, }; // get a distance alone the path function getDistOnPath(dist,pos){ var nx = path[(pos.index + 2) % pLen] - pos.x; var ny = path[(pos.index + 3) % pLen] - pos.y; var d = Math.hypot(nx, ny); if(d > dist){ pos.x += (nx / d) * dist; pos.y += (ny / d) * dist; return pos; } dist -= d; pos.index += 2; pos.x = path[pos.index % pLen]; pos.y = path[(pos.index + 1) % pLen]; return getDistOnPath(dist, pos); } function drawFoot(x,y,dir,left){ var i,j,shape; var xdx = Math.cos(dir) * footScale; var xdy = Math.sin(dir) * footScale; if(left){ ctx.setTransform(xdx, xdy, -xdy, xdx, x + xdy * 50, y - xdx * 50); ctx.rotate(-0.1); // make the foot turn out a bit }else{ ctx.setTransform(xdx, xdy, -xdy, xdx, x - xdy * 50, y + xdx * 50); ctx.rotate(-0.1); // make the foot turn out a bit ctx.scale(1,-1); // right foot needs to be mirrored } // draw the foot as a set of paths points ctx.beginPath(); for(j = 0; j < foot.length; j ++){ shape = foot[j]; i = 0; ctx.moveTo(shape[i++],shape[i++]); while(i < shape.length){ ctx.lineTo(shape[i++],shape[i++]); } ctx.closePath(); } ctx.fill(); } ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0,0,canvas.width,canvas.height); pos1 = getDistOnPath(stepLen/10,pos1); // put the second pos infront so that a direction can be found function drawStep(){ if(pos.index > pLen){ // if past end of path clear and restart ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0,0,canvas.width,canvas.height); pos.index = 0; pos1.index = 0; pos1.x = pos.x = path[0]; pos1.y = pos.y = path[1]; pos1 = getDistOnPath(stepLen/10,pos1); } pos = getDistOnPath(stepLen,pos); pos1 = getDistOnPath(stepLen,pos1); drawFoot(pos.x,pos.y,Math.atan2(pos1.y - pos.y, pos1.x - pos.x),(stepCount++) % 2 === 0); setTimeout(drawStep,stepTime); } drawStep(); Note some of the code is ES6 and will require babel (or equivilent) to run on legacy browsers.
Realistic mouse movement coordinates in javascript?
In javascript, is there a way I can create a variable and a function that "simulates" smooth mouse movement? i.e., say the function simulates a user starts from lower left corner of the browser window, and then moves mouse in a random direction slowly... The function would return x and y value of the next position the mouse would move each time it is called (would probably use something like setInterval to keep calling it to get the next mouse position). Movement should be restricted to the width and height of the screen, assuming the mouse never going off of it. What I don't want is the mouse to be skipping super fast all over the place. I like smooth movements/positions being returned.
A "realistic mouse movement" doesn't mean anything without context : Every mouse user have different behaviors with this device, and they won't even do the same gestures given what they have on their screen. If you take an FPS game, the movements will in majority be in a small vertical range, along the whole horizontal screen. Here is a "drip painting" I made by recording my mouse movements while playing some FPS game. If we take the google home page however, I don't even use the mouse. The input is already focused, and I just use my keyboard. On some infinite scrolling websites, my mouse can stay at the same position for dozens of minutes and just go to a link at some point. I think that to get the more realistic mouse movements possible, you would have to record all your users' gestures, and repro them. Also, a good strategy could be to get the coordinates of the elements that will attract user's cursor the more likely (like the "close" link under SO's question) and make movements go to those elements' coordinates. Anyway, here I made a snippet which uses Math.random() and requestAnimationFrame() in order to make an object move smoothly, with some times of pausing, and variable speeds. // Canvas is here only to show the output of function var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); document.body.appendChild(canvas); var maxX = canvas.width = window.innerWidth; var maxY = canvas.height = window.innerHeight; window.onresize = function(){ maxX = canvas.width = window.innerWidth; maxY = canvas.height = window.innerHeight; } gc.onclick = function(){ var coords = mouse.getCoords(); out.innerHTML = 'x : '+coords.x+'<br>y : '+coords.y; } var Mouse = function() { var that = {}, size = 15, border = size / 2, maxSpeed = 50, // pixels per frame maxTimePause = 5000; // ms that.draw = function() { if (that.paused) return; that.update(); // just for the example ctx.clearRect(0, 0, canvas.width, canvas.height); if(show.checked){ ctx.drawImage(that.img, that.x - border, that.y - border, size, size) } // use requestAnimationFrame for smooth update requestAnimationFrame(that.draw); } that.update = function() { // take a random position, in the same direction that.x += Math.random() * that.speedX; that.y += Math.random() * that.speedY; // if we're out of bounds or the interval has passed if (that.x <= border || that.x >= maxX - border || that.y <= 0 || that.y >= maxY - border || ++that.i > that.interval) that.reset(); } that.reset = function() { that.i = 0; // reset the counter that.interval = Math.random() * 50; // reset the interval that.speedX = (Math.random() * (maxSpeed)) - (maxSpeed / 2); // reset the horizontal direction that.speedY = (Math.random() * (maxSpeed)) - (maxSpeed / 2); // reset the vertical direction // we're in one of the corner, and random returned farther out of bounds if (that.x <= border && that.speedX < 0 || that.x >= maxX - border && that.speedX > 0) // change the direction that.speedX *= -1; if (that.y <= border && that.speedY < 0 || that.y >= maxY - border && that.speedY > 0) that.speedY *= -1; // check if the interval was complete if (that.x > border && that.x < maxX - border && that.y > border && that.y < maxY - border) { if (Math.random() > .5) { // set a pause and remove it after some time that.paused = true; setTimeout(function() { that.paused = false; that.draw(); }, (Math.random() * maxTimePause)); } } } that.init = function() { that.x = 0; that.y = 0; that.img = new Image(); that.img.src ="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAABJUlEQVRIic2WXbHEIAyFI6ESKgEJkVIJlYCTSqiESIiESqiEb19gL9Od3f5R5mbmPPHwBTgnIPJfChiAGbCkCQgtG7BpmgAWIALaDDyOI2bGuq40BasqIoKZATgwNAWHEEjHbkBsBhYRVJUYIwBNwVlFaVOwiDDPMylmQ1OwquY7d0CBrglYkuEeidoeOKt61I6Cq0ftKFhqR+0MOKuo2BQsInnndvnOr4JvR+0qWO5G7Q44K0XtOXDf96jqh9z9WXAy1FJ8l0qd+zbtvU7lWs7wIzkuh8SvpqqDi3zGndPQauDkzvdESm8xZvbh4mVZ7k8ud/+aR0C3YPk7mVvgkCZPVrdZV3dHVem6bju1roMPNmbAmq8kG+/ynD7ZwNsAVVz9dL0AhBrZq7F+CSQAAAAASUVORK5CYII="; that.reset(); } that.getCoords = function(){ return {x: that.x, y:that.y}; } that.init() return that; } var mouse = new Mouse() mouse.draw(); html,body {margin: 0} canvas {position: absolute; top:0; left:0;z-index:-1} #out{font-size: 0.8em} <label for="show">Display cursor</label><input name="show" type="checkbox" id="show" checked="true"/><br> <button id="gc">get cursor Coords</button> <p id="out"></p>
Last I heard the browser's mouse position cannot be altered with JavaScript, so the question really has no answer "as is". The mouse position can be locked though. I'm not certain whether it would be possible to implement a custom cursor that allows setting the position. This would include hiding and perhaps locking the stock cursor.
Having something smoothly follow the cursor is quite straight forward. You may be able to reverse this process to achieve what you need. Here's a code snippet which simply calculates the distance between the cursor and a div every frame and then moves the div 10% of that distance towards the cursor: http://jsfiddle.net/hpp0qb0d/ var p = document.getElementById('nextmove') var lastX,lastY,cursorX,cursorY; window.addEventListener('mousemove', function(e){ cursorX = e.pageX; cursorY = e.pageY; }) setInterval(function(){ var newX = p.offsetLeft + (cursorX - lastX)/10 var newY = p.offsetTop + (cursorY - lastY)/10 p.style.left = newX+'px' p.style.top = newY+'px' lastX = p.offsetLeft lastY = p.offsetTop },20)
Returning precise vector components in js canvas
I have been wrestling with rendering an animation that fires a projectile accurately from an "enemy" node to a "player" node in a 2D 11:11 grid (0:0 = top-left) in JS/Canvas. After a lot of reading up I've managed to get the shots close, but not quite bang on. I think my velocity function is a little out but I really don't know why. This is the trigonometric function: this.getVelocityComponents = function(speed){ // loc (location of enemy actor) = array(2) [X_coord, Y_coord] // des (destination (ie. player in this instance)) = array(2) [X_coord, Y_coord] var i, sum, hyp, output = [], dis = []; var higher = false; for (i in loc) { sum = 0; if (loc[i] > des[i]) sum = loc[i] - des[i]; if (loc[i] < des[i]) sum = des[i] - loc[i]; dis.push(sum); } hyp = Math.sqrt(Math.pow(dis[X], 2) + Math.pow(dis[Y], 2)); if (dis[X] > dis[Y]) { output[X] = (speed * Math.cos(dis[X]/hyp)) output[Y] = (speed * Math.sin(dis[Y]/hyp)) } else if (dis[X] < dis[Y]) { output[X] = (speed * Math.cos(dis[Y]/hyp)) output[Y] = (speed * Math.sin(dis[X]/hyp)) } return output; } and this is the instruction that tells the X and the Y of the projectile frame to advance: var distance = []; for (i in loc) { var sum = 0; if (loc[i] > des[i]) sum = loc[i] - des[i]; if (loc[i] < des[i]) sum = des[i] - loc[i]; distance.push(sum); } if (distance[X] > distance[Y]) { frm[X] += (loc[X] < des[X]) ? v[X] : -v[X]; frm[Y] += (loc[Y] < des[Y]) ? v[Y] : -v[Y]; } else { frm[Y] += (loc[Y] < des[Y]) ? v[X] : -v[X]; frm[X] += (loc[X] < des[X]) ? v[Y] : -v[Y]; } Below is a screenshot. Blue is player, pink enemy and the yellow circles are projectiles as you can see, it's almost on the mark. Have I done something wrong? what do I need to do?
To calculate the direction from enemy to player you can simplify the calculations a little. Find direction angle var diffX = Player.x - Enemy.x, // difference in position diffY = Player.y - Enemy.y, angle = Math.atan2(diffY, diffX); // atan2 will give the angle in radians Notice also difference for Y comes first for atan2 as canvas is oriented 0° pointing right. Velocity vector Then calculate the velocity vector using angle and speed: // calculate velocity vector var speed = 8, vx = Math.cos(angle) * speed, // angle x speed vy = Math.sin(angle) * speed; You might want to consider using time as a factor if that is important. You can see my answer from a while back here for an example on this. Demo Using these calculations you will be able to always "hit" the player with the projectile (reload demo to change enemy position to random y): var ctx = document.querySelector("canvas").getContext("2d"), Player = { x: 470, y: 75 }, Enemy = { x: 100, y: Math.random() * 150 // reload demo to change y-position }; // calculate angle var diffX = Player.x - Enemy.x, diffY = Player.y - Enemy.y, angle = Math.atan2(diffY, diffX); // calculate velocity vector var speed = 8, vx = Math.cos(angle) * speed, // angle x speed vy = Math.sin(angle) * speed, x = Enemy.x, // projectil start y = Enemy.y + 50; // render (function loop() { ctx.clearRect(0, 0, 500, 300); ctx.fillRect(Player.x, Player.y, 30, 100); ctx.fillRect(Enemy.x, Enemy.y, 30, 100); ctx.fillRect(x - 3, y -3, 6, 6); x += vx; y += vy; if (x < 500) requestAnimationFrame(loop); })(); <canvas width=500 height=300></canvas>
The solution is much simpler than that. What should you do ? 1) compute the vector that leads from you enemy to the player. That will be the shooting direction. 2) normalize the vector : meaning you build a vector that has a length of 1, with the same direction. 3) multiply that vector by your speed : now you have a correct speed vector, with the right norm, aimed at the player. Below some code to help you understand : function spawnBullet(enemy, player) { var shootVector = []; shootVector[0] = player[0] - enemy[0]; shootVector[1] = player[1] - enemy[1]; var shootVectorLength = Math.sqrt(Math.pow(shootVector[0], 2) + Math.pow(shootVector[1],2)); shootVector[0]/=shootVectorLength; shootVector[1]/=shootVectorLength; shootVector[0]*=bulletSpeed; shootVector[1]*=bulletSpeed; // ... here return an object that has the enemy's coordinate // and shootVector as speed } Then, since you don't use time in your computations (!! wrooong !! ;-) ) you will make the bullet move with the straightforward : bullet[0] += bullet.speed[0]; bullet[1] += bullet.speed[1]; Now the issue with fixed-step is that your game will run, say, twice slower on a 30fps device than on a 60fps device. The solution is to compute how much time elapsed since the last refresh, let's call this time 'dt'. Using that time will lead you to an update like : bullet[0] += dt * bullet.speed[0]; bullet[1] += dt * bullet.speed[1]; and now you'll be framerate-agnostic, your game will feel the same on any device.