the statement if (xPos === xGold3 && yPos === yGold3) , the code somehow does not recognize the values for xGold3 and yGold3 and my code does not work, but if i assign some values manually like if (xPos === 400 && yPos === 0) , then the code works. Can anyone tell me what i'm doing wrong? thanks
<!DOCTYPE html>
<html>
<head>
<title>P</title>
<style>
div.box{ width: 600px; height: 400px; border: 5px solid black;
margin: auto; position: relative; }
</style>
<button type="button" onclick="position();setGoldPos();">New Game</button>
</head>
<body onLoad ="getGoldPos()" onKeyDown = "move(event)">
<script>
var dx = 20;
var dy = 20;
var xPos = 0;
var yPos = 0;
var xGold3 = 0;
var yGold3 = 0;
//generates random gold positions at the start of every game
function getGoldPos()
{
xGold3 = Math.floor((Math.random() * 545) + 1);
yGold3 = Math.floor((Math.random() * 350) + 1);
}
//assigns randomly generated position to image "gold3"
function setGoldPos()
{
document.getElementById("gold3").style.top= yGold3 + "px";
document.getElementById("gold3").style.left= xGold3 + "px";
}
function position()
{
kitty = document.getElementById("sprite");
kitty.style.left = xPos+"px";
kitty.style.top = yPos+"px";
if (xPos === xGold3 && yPos === yGold3)
{
document.getElementById("gold3").style.top= 420 + "px";
document.getElementById("gold3").style.left= 0 + "px";
}
setTimeout("position()",10);
}
function move(event)
{
var keyPressed = String.fromCharCode(event.keyCode);
if ((keyPressed == "W" || keyPressed == "I" || event.keyCode == '38' ) && yPos >= 2)
{
yPos -= dy;
}
else if ((keyPressed == "D" || keyPressed == "L" || event.keyCode == '39') && xPos <=545)
{
xPos += dx;
}
else if ((keyPressed == "S" || keyPressed == "K" || event.keyCode == '40') && yPos <= 350)
{
yPos += dy;
}
else if ((keyPressed == "A" || keyPressed == "J" || event.keyCode == '37') && xPos >= 3 )
{
xPos -= dx;
}
}
</script>
<div STYLE="text-align:center"> <h2> Use WASD or IJKL or arrow keys to move kitty </h2> </div>
<div class="box">
<img src="sprite.jpg" alt="kitty" id="sprite" width="40px"
style="position: absolute; left: 0px; top: 0px;">
<img id="gold3" src="gold.jpg" style="position:absolute;
left: 400; top: 100; width: 30px; height: 35px;"/>
</div>
</body>
</html>
Think about the issue. You are picking a random number and your step is by 20. So if the random number is not a factor of 20, it is impossible for them to be equal.
xGold3: 86 <-- actual value
xPos: 10, 30, 50, 70, 90, 110 <-- Possible values of X
With the possible values, it is not possible for xPos and xGold3 to ever be equal with this specific value. When you hard coded it to a value in the if statement, 400 was a possible value for xPos, hence why it worked.
So either you need to position the gold on a factor of the step, or you need to be a range.
So you either need to change the random number generator to round to the nearest factor of your step OR change your if statement to see if the gold is in the range of the step. The check should be something like this: (untested)
if (xGold3 >= xPos && xGold3 < xPos+dx && yGold3 >= yPos && yGold3 < yPos+dy)
Positions will never be equal with the current code, try something like this instead to see if both x and y of the cat are within 20pxs:
if (Math.abs(xPos - xGold3) <= 20 && Math.abs(yPos - yGold3) <= 20)
As I dove into it, variable type was not the issue.
This works below! Working JSFiddle https://jsfiddle.net/t7k0a395/
If you subtract the modulo of the random number to make it divisible by 20 so it will be eaqual to your step variable of 20 it works. When it gets to the gold, now the gold moves outside the box.
xGold3 = Math.floor((Math.random()* 545) + 1);
yGold3 = Math.floor((Math.random()* 350) + 1);
xGold3 = xGold3-(xGold3 % 20);
yGold3 = yGold3-(yGold3 % 20);
RGecy
REVISED: Maybe a better way would be to take the mod of xGold3 and dx and do the same for the y values. That way if you changed dx or dy, you would not need to change the mod value.
xGold3 = Math.floor((Math.random()* 545) + 1);
yGold3 = Math.floor((Math.random()* 350) + 1);
xGold3 = xGold3-(xGold3 % dx);
yGold3 = yGold3-(yGold3 % dy);
Related
The point of this exercise is for the ball to travel a smaller distance every cycle, so it leaves an espiral like trace, and ends up in the middle of the canvas.
My logic told me to make a variable "del" that gets bigger with every cycle and add it to the limit of the travel distance. It works fine for all the sides except the left one (where the cycle ends) if I add the variable to that side the ball never hits a limit.
How can I fix it? Thank you so much in advance!
let speed = 7;
let speedX = speed;
let speedY = 0;
let ellipseD = 50;
let ellipseR = ellipseD / 2;
let x = 0;
let y = ellipseR;
let limit = 25;
let del = 0;
function setup() {
createCanvas(500, 500);
}
function draw() {
background(166, 236, 255, 10);
fill(255);
strokeWeight(2);
stroke(21, 171, 212);
ellipse(x, y, ellipseD, ellipseD);
x = x + speedX;
y = y + speedY;
if (x >= width - (limit + del)) {
speedX = 0;
speedY = speed;
}
if (y >= height - (limit + del)) {
speedX = -speed;
speedY = 0;
}
if (y >= height - (limit + del) && x < limit) {
speedX = 0;
speedY = -speed;
}
if (y < limit + del && x <= limit) {
speedX = speed;
speedY = 0;
del = del + 50;
console.log(del);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
The first approach I tried is changing your last branch (the one that shrinks the square space by incrementing del) to this:
if (y < limit + (del + 50) && x <= limit) {
The 50 here is the increment amount, which ensures that the right turn occurs before the snake overlaps with the area it's already visited.
The problem is, the condition then becomes too aggressive and fires prematurely. The fix I used is to add a check for the speedX and speedY in each branch to make sure the snake is moving in the right direction before triggering the condition. Since all conditions should be disjoint per frame, I made them else ifs for clarity.
Finally, if speed doesn't cleanly divide ellipseD and ellipseR, you might see some artifacts and gaps in the snake pattern, so I used 7, 42 and 21 (42 / 2) respectively to clean up the gaps a bit. You might need to play with these sizes a bit to get the desired effect.
const speed = 7;
let speedX = speed;
let speedY = 0;
const ellipseD = 42;
const ellipseR = ellipseD / 2;
let x = 0;
let y = ellipseR;
const limit = 25;
let del = 0;
function setup() {
createCanvas(500, 500);
}
function draw() {
background(166, 236, 255, 10);
fill(255);
strokeWeight(2);
stroke(21, 171, 212);
ellipse(x, y, ellipseD, ellipseD);
x += speedX;
y += speedY;
if (x >= width - (limit + del) && speedX > 0) {
speedX = 0;
speedY = speed;
}
else if (y >= height - (limit + del) && speedY > 0) {
speedX = -speed;
speedY = 0;
}
else if (y >= height - (limit + del) &&
x < (limit + del) && speedX < 0) {
speedX = 0;
speedY = -speed;
}
else if (y < limit + ellipseD + del && speedY < 0) {
speedX = speed;
speedY = 0;
del += ellipseD;
if (del > width / 2) {
noLoop();
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>
I think the most obvious issue with the behavior on the left hand side is that you are not taking del into account when checking x:
// This checks if the ellipse is in the bottom left
if (y >= height - (limit + del) && x < limit) {
speedX = 0;
speedY = -speed;
}
The problem with this code is that x < limit should be x < limit + del. And a similar issue exists in the check for when the ellipse is in the top left.
However, once you "fix" that you will start having issues because the check for the top left will fire repeatedly as del increases faster than the ellipse moves. So it is necessary to add checks to see what direction the ellipse is moving in before updating speed and the value of del.
let speed = 7;
let speedX = speed;
let speedY = 0;
let ellipseD = 50;
let ellipseR = ellipseD / 2;
let x = ellipseR;
let y = ellipseR;
let limit = ellipseR;
let del = 0;
function setup() {
createCanvas(500, 500);
}
function draw() {
background(166, 236, 255, 10);
fill(255);
strokeWeight(2);
stroke(21, 171, 212);
ellipse(x, y, ellipseD, ellipseD);
x = x + speedX;
y = y + speedY;
if (x >= width - (limit + del) && x >= width - (limit + del) && speedX > 0) {
speedX = 0;
speedY = speed;
console.log('top right');
}
if (y >= height - (limit + del) && x >= width - (limit + del) && speedY > 0) {
speedX = -speed;
speedY = 0;
console.log('bottom right');
}
if (y >= height - (limit + del) && x <= (limit + del) && speedX < 0) {
speedX = 0;
speedY = -speed;
console.log('bottom left');
}
if (y < (limit + del) && x <= limit + del && speedY < 0) {
speedX = speed;
speedY = 0;
del = del + 50;
console.log(del);
console.log('top left');
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
I just want to ask how to detect the mouse pointer come from using jquery when the pointer hover an element like div.
When run the function I want to know mouse pointer come from top, left, bottom and right
Thank you!
Try this code:
var getDirection = function (ev, obj) {
var w = obj.offsetWidth,
h = obj.offsetHeight,
x = (ev.pageX - obj.offsetLeft - (w / 2) * (w > h ? (h / w) : 1)),
y = (ev.pageY - obj.offsetTop - (h / 2) * (h > w ? (w / h) : 1)),
d = Math.round( Math.atan2(y, x) / 1.57079633 + 5 ) % 4;
return d;
};
$('#yourDiv').mouseover(function (event) {
var direction = getDirection(event, this);
if (direction == 0) {
$(this).html('Top side');
} else if (direction == 1) {
$(this).html('Right side');
} else if (direction == 2) {
$(this).html('Bottom side');
} else if (direction == 3) {
$(this).html('Left side');
}
});
#yourDiv {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
top: 50px;
left: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="yourDiv"></div>
getDirection copied from this page by CSS-Tricks which seems to do a similar thing to what you want in a number of different ways.
I want the box to traverse the inside of the container from left to right, then down, then right to left and finally back up to the original position. The examples I found all include extra array pos = [180,180]. I don't understand why do I need it when my IF conditions seem to cover all positions.
window.onload = function() {
var t = setInterval(slide, 5);
pos1 = [0, 0];
var box = document.getElementById('sqr');
function slide() {
if (pos1[0] < 180 && pos1[1] < 180) {
pos1[0]++;
box.style.left = pos1[0] + "px";
} else if (pos1[0] >= 180 && pos1[1] < 180) {
pos1[1]++;
box.style.top = pos1[1] + "px";
} else if (pos1[0] >= 180 && pos1[1] >= 180) {
pos1[0]--;
box.style.left = pos1[0] + "px";
} else if (pos1[0] <= 0 && pos1[1] >= 180) {
pos1[1]--;
box.style.top = pos1[1] + "px";
}
}
}
#contain {
position: relative;
width: 200px;
height: 200px;
background-color: pink;
}
#sqr {
position: absolute;
width: 20px;
height: 20px;
background-color: blue;
}
<div id="contain">
<div id="sqr"></div>
</div>
Once the box gets to the maximum X and Y positions, the program is still checking to make sure the box hasn't reached the maximum yet, which causes the all IF conditions to fail. You could do it by checking for Y=0 for the first "leg", X=MAX for the next, Y=MAX for the next, and then X=0 for the last, but instead of that, you can set a "state" which has 4 values to determine which "leg" of the animation is being run, and then just run it for 180 iterations each.
window.onload = function() {
var t = setInterval(slide, 5);
pos1 = [0, 0];
var box = document.getElementById('sqr');
state = 0;
iterations = 0;
function slide() {
if (iterations >= 180) {state = (state + 1) % 4; iterations = 0;}
if (state === 0) pos1[0]++;
else if (state == 1) pos1[1]++;
else if (state == 2) pos1[0]--;
else if (state == 3) pos1[1]--;
iterations++;
box.style.left = pos1[0] + "px";
box.style.top = pos1[1] + "px";
}
}
#contain {
position: relative;
width: 200px;
height: 200px;
background-color: pink;
}
#sqr {
position: absolute;
width: 20px;
height: 20px;
background-color: blue;
}
<div id="contain">
<div id="sqr"></div>
</div>
window.onload = function() {
var t = setInterval(slide, 5);
var box = document.getElementById('sqr');
var left = 0,
top = 0;
function slide() {
var pos1 = [parseInt(box.style.left || 0), parseInt(box.style.top || 0)]
console.log(pos1);
if (pos1[0] == 0 && pos1[1] == 0) { //Top left, go right
left = 1;
top = 0;
} else if (pos1[0] == 180 && pos1[1] == 0) { //Top right, go down
left = 0;
top = 1;
} else if (pos1[0] == 180 && pos1[1] == 180) { //Bottom right, go left
left = -1;
top = 0;
} else if (pos1[0] == 0 && pos1[1] == 180) { //Bottom left, go up
left = 0;
top = -1;
}
box.style.left = (parseInt(box.style.left || 0) + left) + "px";
box.style.top = (parseInt(box.style.top || 0) + top) + "px";
}
}
#contain {
position: relative;
width: 200px;
height: 200px;
background-color: pink;
}
#sqr {
position: absolute;
width: 20px;
height: 20px;
background-color: blue;
}
<div id="contain">
<div id="sqr"></div>
</div>
Here's my take on it. React according to the position of the element, when it reaches a corner, change directions. This makes things easier, since we do not rely on actual positions to know where to go next step...
It is because, when animation gets to pos1 = [180, 180], it executes:
else if (pos1[0] >= 180 && pos1[1] >= 180) {
pos1[0]--;
box.style.left = pos1[0] + "px";
}
And then pos1 = [179, 180], which is not covered by the code.
I suggest using something like that:
var direction = 0; //0 - right, 1 - down, 2 - left, 3 - up
function slide() {
if (pos1[0] < 180 && pos1[1] = 0) {
direction = 0;
} else if (pos1[0] = 180 && pos1[1] < 180) {
direction = 1;
} else if (pos1[0] > 0 && pos1[1] = 180) {
direction = 2;
} else if (pos1[0] = 0 && pos1[1] > 0) {
direction = 3;
}
switch(direction){
case 0:
pos1[0]++;
break;
case 1:
pos1[1]++;
break;
case 2:
pos1[0]--;
break;
case 3:
pos1[1]--;
break;
}
box.style.left = pos1[0] + "px";
box.style.top = pos1[1] + "px";
}
I'm not usually a fan of doing someone's homework for them, but I got intrigued as to what would make it work and couldn't help myself. shrugs
The if statements have been tailored to be more explicit with what they need. This of course was achieved by following the methods suggested to you by #j08691, by just adding a console.log(pos1); at the top of each if section.
This is just for reference, in fact, #Salketer has managed to post before me and it looks a lot cleaner than this. The real answer is in the comments by #j08691
window.onload = function() {
var t = setInterval(slide, 5),
pos1 = [0, 0],
box = document.getElementById('sqr');
function slide() {
if (pos1[0] < 180 && pos1[1] === 0) {
console.log(pos1);
pos1[0]++;
box.style.left = pos1[0] + "px";
} else if (pos1[0] === 180 && pos1[1] < 180) {
console.log(pos1);
pos1[1]++;
box.style.top = pos1[1] + "px";
} else if ((pos1[0] <= 180 && pos1[0] >= 1) && pos1[1] === 180) {
console.log(pos1);
pos1[0]--;
box.style.left = pos1[0] + "px";
} else if (pos1[0] === 0 && pos1[1] <= 180) {
console.log(pos1);
pos1[1]--;
box.style.top = pos1[1] + "px";
}
}
}
#contain {
position: relative;
width: 200px;
height: 200px;
background-color: pink;
}
#sqr {
position: absolute;
width: 20px;
height: 20px;
background-color: blue;
}
<div id="contain">
<div id="sqr"></div>
</div>
I am trying to make a simple platformer like game.The code i am using is shown below
window.onload = function(){
var canvas = document.getElementById('game');
var ctx = canvas.getContext("2d");
var rightKeyPress = false;
var leftKeyPress = false;
var upKeyPress = false;
var downKeyPress = false;
var playerX = canvas.width / 2;
var playerY = -50;
var dx = 3;
var dy = 3;
var dxp = 3;
var dyp = 3;
var dxn = 3;
var dyn = 3;
var prevDxp = dxp;
var prevDyp = dyp;
var prevDxn = dxn;
var prevDyn = dyn;
var playerWidth = 50;
var playerHeight = 50;
var obstacleWidth = 150;
var obstacleHeight = 50;
var obstaclePadding = 10;
var G = .98;
var currentVelocity = 0;
var obstacles = [];
var imageLoaded = false;
document.addEventListener("keyup",keyUp,false);
document.addEventListener("keydown",keyDown,false);
function keyDown(e){
if(e.keyCode == 37){
leftKeyPress = true;
if(currentVelocity > 2){
currentVelocity -= .1;
}
}
if(e.keyCode == 38){
upKeyPress = true;
}
if(e.keyCode == 39){
rightKeyPress = true;
if(currentVelocity < 2){
currentVelocity += .1;
}
}
if(e.keyCode == 40){
downKeyPress = true;
}
}
function keyUp(e){
if(e.keyCode == 37){
leftKeyPress = false;
}
if(e.keyCode == 38){
upKeyPress = false;
}
if(e.keyCode == 39){
rightKeyPress = false;
}
if(e.keyCode == 40){
downKeyPress = false;
}
}
function createObstacles(){
for(x=0;x < 4;x++){
var obX = (200 * x) + Math.round(Math.random() * 150);
var obY = 50 + Math.round(Math.random() * 400);
obstacles.push({"x":obX,"y":obY});
}
}
createObstacles();
function drawObstacles(){
ctx.beginPath();
for(x=0;x < 4;x++){
var obX = obstacles[x].x;
var obY = obstacles[x].y;
ctx.rect(obX,obY,obstacleWidth,obstacleHeight)
}
ctx.fillStyle = "grey";
ctx.fill();
ctx.closePath();
}
function initPlayer(){
ctx.beginPath();
ctx.rect(playerX,playerY,50,50);
ctx.fillStyle="orange";
ctx.fill();
ctx.closePath();
}
function KeyPressAndGravity(){
checkObstacleCollision();
playerX += currentVelocity;
if(rightKeyPress && playerX + 50 < canvas.width){
playerX += dxp;
}
if(leftKeyPress && playerX > 0){
playerX -= dxn;
}
if(upKeyPress && playerY > 0){
playerY -= dyn;
}
if(downKeyPress && playerY + 50 < canvas.height){
playerY += dyp;
}
if(playerY+50 < canvas.height){
playerY += G;
}
if(playerX <= 0){
currentVelocity = 0;
}else if(playerX + 50 >= canvas.width){
currentVelocity = 0;
}
dxp = prevDxp;
dyp = prevDyp;
dxn = prevDxn;
dyn = prevDyn;
G = .98;
if(currentVelocity != 0){
if(currentVelocity > 0){
currentVelocity -= .01;
}else{
currentVelocity += .01;
}
}
}
/*-----------------------------------------------------------
-------------------------------------------------------------
-------------------------------------------------------------
---------------------------Check this part-------------------
-------------------------------------------------------------
-------------------------------------------------------------
-------------------------------------------------------------
------------------------------------------------------------*/
function checkObstacleCollision(){
var obLen = obstacles.length;
for(var x=0;x<obLen;x++){
var obX = obstacles[x].x;
var obY = obstacles[x].y;
if((playerX + playerWidth > obX && playerX + playerWidth < obX + obstacleWidth || playerX > obX && playerX < obX + obstacleWidth) && playerY + playerHeight > obY - obstaclePadding && playerY + playerHeight < obY){
dyp = 0;
G = 0;
}else if((playerX + playerWidth > obX && playerX + playerWidth < obX + obstacleWidth || playerX > obX && playerX < obX + obstacleWidth) && playerY > obY + obstacleHeight && playerY < obY + obstacleHeight + obstaclePadding){
dyn = 0;
}else if(playerX + playerWidth > obX - obstaclePadding && playerX + playerWidth < obX && ((playerY + playerHeight > obY && playerY + playerHeight < obY + obstacleHeight) || (playerY > obY && playerY < obY + obstacleHeight))){
dxp = 0;
}else if(playerX > obX + obstacleWidth && playerX < obX + obstacleWidth + obstaclePadding && ((playerY + playerHeight > obY && playerY + playerHeight < obY + obstacleHeight) || (playerY > obY && playerY < obY + obstacleHeight))){
dxn = 0;
}
}
}
function draw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
initPlayer();
KeyPressAndGravity();
drawObstacles();
}
setInterval(draw,15);
}
<canvas id="game" width="1000" height="600" style="border:1px solid #000;"></canvas>
The problem is that sometimes when the speed of the "player" is high it can go through obstacles like the below image. How can i stop that from happening ?
So what i want is that the player should stop right as he reaches the obstacle and not pass through it
There is a complication when collision testing objects that are moving quickly
You must determine if your player and obstacle intersected at any time during the move -- even if the player has moved beyond the obstacle by the end of the move. Therefore you must account for the complete path the player has moved from start to end of the move.
...
Then you can check if the player ever intersected the obstacle during the move by checking if the player's track intersects the obstacle.
A relatively efficient method for testing collisions involving fast moving objects
Define the 3 line segments that connect the 3 vertices of the player's starting rectangle that are closest to the player's ending rectangle.
For any of the 3 lines that intersect an obstacle, calculate the distance of the line segment to the obstacle. Select the line that has the shortest distance between starting vertex and the obstacle.
Calculate the "x" & "y" distances of the selected line segment.
var dx = obstacleIntersection.x - start.x;
var dy = obstacleIntersection.y - start.y;
Move the player from their starting position by the distance calculated in #3. This results in the player moving to the spot where it first collided with the obstacle.
player.x += dx;
player.y += dy;
Code and Demo:
Useful functions in the code:
setPlayerVertices determines the 3 line segments that connect the 3 vertices of the player's starting rectangle that are closest to the player's ending rectangle.
hasCollided finds the shortest segment connecting a vertex from the player's starting position with the collision point on the obstacle.
line2lineIntersection finds the intersection point (if any) between 2 lines. This is used to test for an intersection between a start-to-end segment (from #1) and any of the 4 line segments that make up the obstacle rectangle. Attribution: This function is adapted from Paul Bourke's useful treatice on intersections.
Here is example code and a Demo showing how to halt the player at the collision point on the obstacle:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
var isDown=false;
var startX,startY,dragging;
ctx.translate(0.50,0.50);
ctx.textAlign='center';
ctx.textBaseline='middle';
var pts;
var p1={x:50,y:50,w:25,h:25,fill:''};
var p2={x:250,y:250,w:25,h:25,fill:''};
var ob={x:100,y:150,w:125,h:25,fill:''};
var obVertices=[
{x:ob.x,y:ob.y},
{x:ob.x+ob.w,y:ob.y},
{x:ob.x+ob.w,y:ob.y+ob.h},
{x:ob.x,y:ob.y+ob.h}
];
var s1,s2,s3,e1,e2,e3,o1,o2,o3,o4;
draw();
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUpOut(e);});
$("#canvas").mouseout(function(e){handleMouseUpOut(e);});
function draw(){
ctx.clearRect(0,0,cw,ch);
//
ctx.lineWidth=4;
ctx.globalAlpha=0.250;
ctx.strokeStyle='blue';
ctx.strokeRect(ob.x,ob.y,ob.w,ob.h);
ctx.globalAlpha=1.00;
ctx.fillStyle='black';
ctx.fillText('obstacle',ob.x+ob.w/2,ob.y+ob.h/2);
//
ctx.globalAlpha=0.250;
ctx.strokeStyle='gold';
ctx.strokeRect(p1.x,p1.y,p1.w,p1.h);
ctx.strokeStyle='purple';
ctx.strokeRect(p2.x,p2.y,p2.w,p2.h);
ctx.fillStyle='black';
ctx.globalAlpha=1.00;
ctx.fillText('start',p1.x+p1.w/2,p1.y+p1.h/2);
ctx.fillText('end',p2.x+p2.w/2,p2.y+p2.h/2);
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
var mx=startX;
var my=startY;
if(mx>p1.x && mx<p1.x+p1.w && my>p1.y && my<p1.y+p1.h){
isDown=true;
dragging=p1;
}else if(mx>p2.x && mx<p2.x+p2.w && my>p2.y && my<p2.y+p2.h){
isDown=true;
dragging=p2;
}
}
function handleMouseUpOut(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// Put your mouseup stuff here
isDown=false;
dragging=null;
}
function handleMouseMove(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
var dx=mouseX-startX;
var dy=mouseY-startY;
startX=mouseX;
startY=mouseY;
//
dragging.x+=dx;
dragging.y+=dy;
//
draw();
//
setPlayerVertices(p1,p2);
var c=hasCollided(obVertices);
if(c.dx){
ctx.strokeStyle='gold';
ctx.strokeRect(p1.x+c.dx,p1.y+c.dy,p1.w,p1.h);
ctx.fillStyle='black';
ctx.fillText('hit',p1.x+c.dx+p1.w/2,p1.y+c.dy+p1.h/2);
line(c.s,c.i,'red');
}
}
function setPlayerVertices(p1,p2){
var tl1={x:p1.x, y:p1.y};
var tl2={x:p2.x, y:p2.y};
var tr1={x:p1.x+p1.w, y:p1.y};
var tr2={x:p2.x+p2.w, y:p2.y};
var br1={x:p1.x+p1.w, y:p1.y+p1.h};
var br2={x:p2.x+p2.w, y:p2.y+p2.h};
var bl1={x:p1.x, y:p1.y+p1.h};
var bl2={x:p2.x, y:p2.y+p2.h};
//
if(p1.x<=p2.x && p1.y<=p2.y){
s1=tr1; s2=br1; s3=bl1;
e1=tr2; e2=br2; e3=bl2;
o1=0; o2=1; o3=3; o4=0;
}else if(p1.x<=p2.x && p1.y>=p2.y){
s1=tl1; s2=tr1; s3=br1;
e1=tl2; e2=tr2; e3=br2;
o1=2; o2=3; o3=3; o4=0;
}else if(p1.x>=p2.x && p1.y<=p2.y){
s1=tl1; s2=br1; s3=bl1;
e1=tl2; e2=br2; e3=bl2;
o1=0; o2=1; o3=1; o4=2;
}else if(p1.x>=p2.x && p1.y>=p2.y){
s1=tl1; s2=tr1; s3=bl1;
e1=tl2; e2=tr2; e3=bl2;
o1=1; o2=2; o3=2; o4=3;
}
}
function hasCollided(o){
//
var i1=line2lineIntersection(s1,e1,o[o1],o[o2]);
var i2=line2lineIntersection(s2,e2,o[o1],o[o2]);
var i3=line2lineIntersection(s3,e3,o[o1],o[o2]);
var i4=line2lineIntersection(s1,e1,o[o3],o[o4]);
var i5=line2lineIntersection(s2,e2,o[o3],o[o4]);
var i6=line2lineIntersection(s3,e3,o[o3],o[o4]);
//
var tracks=[];
if(i1){tracks.push(track(s1,e1,i1));}
if(i2){tracks.push(track(s2,e2,i2));}
if(i3){tracks.push(track(s3,e3,i3));}
if(i4){tracks.push(track(s1,e1,i4));}
if(i5){tracks.push(track(s2,e2,i5));}
if(i6){tracks.push(track(s3,e3,i6));}
//
var nohitDist=10000000;
var minDistSq=nohitDist;
var halt={dx:null,dy:null,};
for(var i=0;i<tracks.length;i++){
var t=tracks[i];
var testdist=t.dx*t.dx+t.dy*t.dy;
if(testdist<minDistSq){
minDistSq=testdist;
halt.dx=t.dx;
halt.dy=t.dy;
halt.s=t.s;
halt.i=t.i;
}
}
return(halt);
}
//
function track(s,e,i){
dot(s);dot(i);line(s,i);line(i,e);
return({ dx:i.x-s.x, dy:i.y-s.y, s:s, i:i });
}
function line2lineIntersection(p0,p1,p2,p3) {
var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
var denominator = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);
// Test if Coincident
// If the denominator and numerator for the ua and ub are 0
// then the two lines are coincident.
if(unknownA==0 && unknownB==0 && denominator==0){return(null);}
// Test if Parallel
// If the denominator for the equations for ua and ub is 0
// then the two lines are parallel.
if (denominator == 0) return null;
// If the intersection of line segments is required
// then it is only necessary to test if ua and ub lie between 0 and 1.
// Whichever one lies within that range then the corresponding
// line segment contains the intersection point.
// If both lie within the range of 0 to 1 then
// the intersection point is within both line segments.
unknownA /= denominator;
unknownB /= denominator;
var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1)
if(!isIntersecting){return(null);}
return({
x: p0.x + unknownA * (p1.x-p0.x),
y: p0.y + unknownA * (p1.y-p0.y)
});
}
function dot(pt){
ctx.beginPath();
ctx.arc(pt.x,pt.y,3,0,Math.PI*2);
ctx.closePath();
ctx.fill();
}
function line(p0,p1,stroke,lw){
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
ctx.lineTo(p1.x,p1.y);
ctx.lineWidth=lw || 1;
ctx.strokeStyle=stroke || 'gray';
ctx.stroke();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Drag start & end player position rects<br>The shortest segment intersecting the obstacle is red.<br>The repositioned player is shown on the obstacle.</h4>
<canvas id="canvas" width=400 height=400></canvas>
What you are experiencing is usually called tunneling.
There are lot's of different ways to solve it, but the easiest is usually to save last position and do one of the following.
A
Calculate a new larger collision box for each element, containing the element's last position and it's new one. Think of this as a box containing your element twice. Once for it's last position (LP) and one for it's new position (NP),
------------
|| LP | |
||____| |
| ____ |
| | NP ||
|______|____||
Now if you use this new box to check collisions, it will take the path travelled into account to avoid tunneling. This might create unexpected collisions in the top right and bottom left corner, but it's a simple implementation and the tradeoff might be worth it.
B
Check collisions for each step along the path traveled from it's last position to it's new position. If your element has traveled 5 pixels since the last frame, you check the collision once for each pixel (or minimum acceptable collision distance).
____
| LP |
|____||
---- |___
|___|NP |
|____|
This will of course increase the number of collision detections and have an impact on performance. Here you could look into quadtrees to compensate for the performance loss.
Moving forward, there are a lot more elegant and advanced solutions, but the subject is to broad for a complete answer here.
Hope it helps!
Well, I made a "collision calculator" about several months ago, so you can change and use the code below as you like :) For better explanation:
p_x is last player position x plus his width
p_y is last player position y plus his height
p_x_m is last player position x
p_y_m is last player position y
y_m is new player position (his y - somevalue)
x_m is new player position (his x - somevalue)
y_p is new player position (his y + somevalue + his height)
y_p_m is new player position (his y + somevalue)
x_p is new player position (his x + somevalue + his width)
x_p_m is new player position (his x + somevalue)
w_x is wall position x
w_y is wall position y
w_w is wall width
w_h is wall height
pressedKeys is a string telling which keys player pressed (for example "was" or "wd" or "ad" etc.)
this.walls is a variable with walls (for example if I've got 4 walls the array would look like [false,'s',false,false] because I touched second wall with "s" key).
Code:
if(
pressedKeys.indexOf("s")>-1 &&
(
( // P
p_y>w_y&&p_y<(w_y+w_h)&&x_p_m>w_x && x_p-5>w_x && x_m<w_x // +----
) || // |
( // P
y_p>w_y&&p_y<(w_y+w_h) && x_p-5>w_x && x_p<=(w_x+w_w) // +--------+
) || // | |
( // P
y_p>w_y&&p_y<(w_y+w_h) && x_p>(w_x+w_w)&&p_x_m<(w_x+w_w) && x_m+5<(w_x+w_w) // ----+
) // |
)
)
{
if(this.walls[i] == false)
this.walls[i] = "";
this.walls[i] += "s";
}
if(
pressedKeys.indexOf("d")>-1 &&
(
( // P+----
p_x>w_x&&p_x<(w_x+w_w)&&y_p_m>w_y && y_p-5>w_y && y_m<w_y // |
) || // |
( // |
x_p>w_x&&p_x<(w_x+w_w) && y_p-5>w_y && y_p<=(w_y+w_h) // P|
) || // |
( // |
x_p>w_x&&p_x<(w_x+w_w) && y_p>(w_y+w_h)&&p_y_m<(w_y+w_h) && y_m+5<(w_y+w_h) // |
) // P+----
)
)
{
if(this.walls[i] == false)
this.walls[i] = "";
this.walls[i] += "d";
}
if(
pressedKeys.indexOf("w")>-1 &&
(
( // |
y_m<(w_y+w_h)&&y_p-5>w_y && x_p-5>w_x && x_m<w_x &&x_p_m>w_x // +----
) || // P
( // | |
y_m<(w_y+w_h)&&y_p-5>w_y && x_p-5>w_x && x_p<=(w_x+w_w) // +--------+
) || // P
( // |
y_m<(w_y+w_h)&&y_p-5>w_y && x_p>(w_x+w_w)&&p_x_m<(w_x+w_w) && x_m+5<(w_x+w_w) // ----+
) // P
)
)
{
if(this.walls[i] == false)
this.walls[i] = "";
this.walls[i] += "w";
}
if(
pressedKeys.indexOf("a")>-1 &&
(
( // ----+P
x_m<(w_x+w_w)&&x_p-5>w_x && y_p-5>w_y && y_m<w_y &&y_p_m>w_y // |
) || // |
( // |
x_m<(w_x+w_w)&&x_p-5>w_x && y_p-5>w_y && y_p<=(w_y+w_h) // |P
) || // |
( // |
x_m<(w_x+w_w)&&x_p-5>w_x && y_p>(w_y+w_h)&&p_y_m<(w_y+w_h) && y_m+5<(w_y+w_h) // |P
) // ----+P
)
)
{
if(this.walls[i] == false)
this.walls[i] = "";
this.walls[i] += "a";
}
Comments at the right side of the code show how player collides.
This code 100% works, I use it everytime I want to check for collision.
Hope it helped a bit :)
Generally to detect collisions in canvas games I use something like:
function collides(a, b) {
return a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y;
}
But this only detects collisions if the objects are touching at the time the frame is processed. If I have a sprite whose speed (in pixels/frame) is greater than the width of an obstacle in its path, it will pass through the obstacle without the collision being detected.
How would I go about checking what's in between the sprite and its destination?
That's a generally a hard problem and for high-quality solution something like Box 2D library will be useful.
A quick and dirty solution (that gives false positives on diagonally moving objects) — check collision between bounding boxes that cover position of object in current and previous frame.
Instead of a.x use min(a.x, a.x - a.velocity_x), instead of a.x + a.width use max(a.x + a.width, a.x + a.width - a.velocity_x), etc.
If the object that is moving fast is small (a bullet), then test collision between line (from its origin to origin + velocity) and boxes of other objects.
You should use the whole area swept (in the update interval) by the moving object as the bounding box to check against the obstacle.
check this out. try moving rect 1 with arrow keys and if it touches rect 2 it will alert "collided".
var rect1x = 0;
var rect1y = 0;
var rect1height = 10;
var rect1width = 10;
var rect2x = 200;
var rect2y = 0;
var rect2height = 10;
var rect2width = 10;
var speedX = 0;
var speedY = 0;
var ctx = document.getElementById("canvas").getContext("2d");
var interval = setInterval(function() {
document.addEventListener("keydown", function(e) {
if (e.key == "ArrowLeft") {
speedX = -1;
}
if (e.key == "ArrowRight") {
speedX = 1;
}
if (e.key == "ArrowUp") {
speedY = -1;
}
if (e.key == "ArrowDown") {
speedY = 1;
}
});
document.addEventListener("keyup", function() {
speedX = 0;
speedY = 0;
});
ctx.clearRect(rect1x, rect1y, rect1width, rect1height);
rect1x += speedX;
rect1y += speedY;
ctx.fillStyle = "blue";
ctx.fillRect(rect1x, rect1y, rect1width, rect1height);
ctx.fillStyle = "red";
ctx.fillRect(rect2x, rect2y, rect2width, rect2height);
if (((rect1x + rect1width > rect2x) && (rect1x < rect2x + rect2width)) && ((rect1y + rect1height > rect2y) && (rect1y < rect2y + rect2height))) {
clearInterval(interval);
alert("collided");
}
}, 0);
<canvas id="canvas" height="400" width="400" style="border: 1px solid black"></canvas>