How to detect collisions between fast moving objects - javascript

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>

Related

How do I get the X and Y coordinates of an image that I move around the canvas

As I move the image of player.hero around the canvas I would like a variable that holds the current x and y pos of the hero. So I can make zombie image move towards current position of hero. Thanks and if my code so far is terrible please suggest amendments thanks.
(function() {
var canvas, context, width, height, speed = 8;
var interval_id;
var zombies = [];
var bullets = [];
var moveLeft = false;
var moveRight = false;
var moveUp = false;
var moveDown = false;
var player = {
x : 0,
y : 0,
width : 35,
height : 60,
hero : new Image(),
};
for (var i = 0; i < 10; i += 1){
var zombie = {
x : 10,
y : 10,
undead : new Image(),
targetToGox : 0,
targetToGoy : 0,
};
zombies.push(zombie);
}
var mouse = {
x : 0,
y : 0,
}
document.addEventListener('DOMContentLoaded', init, false);
function init() {
canvas = document.querySelector('canvas');
context = canvas.getContext('2d');
width = canvas.width;
height = canvas.height;
player.x = width / 2 - 18;
player.y = height / 2 - 30;
player.hero.src = 'hero.png';
zombie.undead.src = 'zombie.png';
//context.drawImage(player.hero, player.x, player.y);
window.addEventListener("keydown", activate,false);
window.addEventListener("keyup",deactivate,false);
//window.addEventListener("mouseover", drawImagePointingAt, false);
interval_player = window.setInterval(drawPlayer, 33);
}
function drawPlayer() {
context.clearRect(0 ,0 ,width, height);
context.drawImage(player.hero,player.x, player.y);
//******** need zombie to go to position of player.hero******///
context.drawImage(zombie.undead (somthing for x and y coordinats of player.hero);
// stops player moveing beyond the bounds of the canvas
if (player.x + player.width >= width) {
moveRight = false
}
if (player.y + player.height >= height) {
moveDown = false
}
if (player.x <= 0) {
moveLeft = false
}
if (player.y <= 0) {
moveUp = false
}
if (moveRight) {
player.x += speed;
}
if (moveUp) {
player.y -= speed;
}
if (moveDown) {
player.y += speed;
}
if (moveLeft){
player.x -= speed;
}
function activate(event) {
var keyCode = event.keyCode;
if (keyCode === 87){
moveUp = true;
}
else if (keyCode === 68){
moveRight = true;
}
else if (keyCode === 83){
moveDown = true;
}
else if (keyCode === 65){
moveLeft = true;
}
}
function deactivate(event) {
var keyCode = event.keyCode;
if (keyCode === 87){
moveUp = false;}
else if (keyCode === 68){
moveRight = false;}
else if (keyCode === 83){
moveDown = false;}
else if (keyCode === 65){
moveLeft = false;}
}
function getRandomNumber(min, max) {
return Math.round(Math.random() * (max - min)) + min;
}
function stop() {
clearInterval(interval_player);
}
})();
This is a pretty long wall of text about why I think it would be better that you restructured your code instead of "... getting the X and Y coordinates of an image that I move around the screen".
The end of this post contains a script that tries to show how you might go about doing that.
You asked about your code's quality. Your code is not terrible for a new programmer, but you are falling into some classic traps will be painful as your codebase gets larger.
An example of this might be keeping variables for each of the possible directions your player should move after a keypress (which is manipulated & used in separate functions). The problem with this is that when you decide to change any aspect of this system, it's going to crumble.
Instead, consider having an object representing the player which contains it's own internal logic for 'moving' by changing it's own coordinates.
I cannot emphasize this idea enough - Excellent hackers always give themselves a room to work. You shouldn't ever (for example), make the Player directly manipulate the game drawing routines. This is such a pervasive concept that there are actually words in software engineering for different facets of this organizational principle (words like 'Encapsulation' or 'Loose coupling' or 'Law of Demeter'). It's that important.
This leads me to another point: Global variables.
This is a tougher one because it's one where all programmers eventually make hypocrites of themselves if they are too critical of it (especially if they are doing low-level work). Still, it's best to consolidate whatever global variables you do have, and perhaps make functions that serve as their interface to the 'outside world'.
In your game, this would mean moving like moveLeft into a "game loop" that simply checks all of the 'objects' coordinates, clearing the screen & drawing those objects appropriately.
Another important idea here is that 'duplicate' functionality should share a single method. In your case, this would entail that both Player and Zombie become instances of some more abstract category which I'll name GameObject. This allows you to write all your major functions once for GameObject and 'inherit' their functionality inside of the Player and Zombie (there are many other, perhaps even better, ways to accomplish this without prototypes or inheritance at all)
In consideration of all of this, I tried to spend 20 minutes whipping up something that can hopefully give you something to work from. If this is totally unrelated to what you were going for, at the very least you can notch another round of possibly pointless internet pontification under your belt.
My code's "inheritance" is done in a very plain Javascript style, even in spite of the fact there are no less than a dozen 'new and improved' ways to share implementation details between code in JS, each with great variety in their depth of adherence to the principles of either prototypical or object oriented programming.
I cannot hope to cover Stamps, jSL, even Javascript's now infamous new 'class' keyword, so I would advise you read up about these and perhaps put them to profitable use yourself, but for now I'm sticking with the basics.
const ZOMBIE_COUNT = 10
function GameState() {
this.player = null;
this.enemies = []
}
var Game = new GameState() // our global game state
// An abstract 'game object' or character
function GameObject({x, y, image}) {
this.x = x
this.y = y
this.image = image
}
GameObject.prototype.draw = function() {
Game.ctx.fillStyle = this.color
Game.ctx.fillRect(this.x, this.y, 10, 10)
}
GameObject.prototype.moveLeft = function(n) { if(this.x > 0) this.x -= n }
GameObject.prototype.moveRight = function(n) { if(this.x < Game.width) this.x += n }
GameObject.prototype.moveDown = function(n) { if(this.y < Game.height) this.y += n}
GameObject.prototype.moveUp = function(n) { if(this.y > 0) this.y -= n }
function Player({x, y, width}) {
GameObject.call(this, {x, y}) // setup x, y & image
this.color = 'red'
}
Player.prototype = Object.create(GameObject.prototype, {})
function Zombie({x, y, target}) {
GameObject.call(this, {x, y}) // setup x, y & image
this.target = target // target contains the player
this.color = 'blue'
}
Zombie.prototype = Object.create(GameObject.prototype, {})
Zombie.prototype.moveToPlayer = function() {
let {x, y} = Game.player
// very basic 'movement' logic
if (this.x < x) {
this.moveRight(1/4)
} else if (this.x > x) {
this.moveLeft(1/4)
}
if (this.y > y) {
this.moveUp(1/4)
} else if (this.y < y) {
this.moveDown(1/4)
}
}
function init() {
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
var ctx = canvas.getContext('2d')
} else {
console.log("No canvas")
return -1
}
let {width, height} = canvas
// Setup our game object
Game.width = width
Game.height = height
Game.ctx = ctx
// Create our player in the middle
Game.player = new Player({x: width / 2, y: height / 2})
// Create our enemies
for(let i = 0; i < ZOMBIE_COUNT; i++) {
Game.enemies.push(new Zombie({x: Math.random() * width | 0, // truncate our value
y: Math.random() * height | 0}))
}
game_loop()
}
function game_loop() {
window.requestAnimationFrame(game_loop)
Game.ctx.fillStyle = 'white'
Game.ctx.fillRect(0, 0, Game.width, Game.height);
Game.player.draw()
Game.enemies.map(enemy => {
enemy.moveToPlayer()
enemy.draw()
})
}
function process_key(ev) {
let speed = 3
let key = ev.keyCode
if (key === 68)
Game.player.moveRight(speed)
if (key === 87)
Game.player.moveUp(speed)
if (key === 65)
Game.player.moveLeft(speed)
if (key === 83)
Game.player.moveDown(speed)
}
window.addEventListener('keydown', process_key, false);
init()
canvas { border: 3px solid #333; }
<canvas id="canvas" width="400" height="400"></canvas>
I assume you mean this line?
//******** need zombie to go to position of player.hero******///
context.drawImage(zombie.undead (somthing for x and y coordinats of player.hero);
I would change the code to something like:
function init() {
...
interval_player = window.setInterval(updateGame, 33);
}
function updateGame() {
context.clearRect(0 ,0 ,width, height);
updatePlayer();
for (let zombie of zombies) {
updateZombie(zombie);
}
function updatePlayer() {
// stops player moveing beyond the bounds of the canvas
if (player.x + player.width >= width) {
moveRight = false
}
if (player.y + player.height >= height) {
moveDown = false
}
if (player.x <= 0) {
moveLeft = false
}
if (player.y <= 0) {
moveUp = false
}
if (moveRight) {
player.x += speed;
}
if (moveUp) {
player.y -= speed;
}
if (moveDown) {
player.y += speed;
}
if (moveLeft){
player.x -= speed;
}
context.drawImage(player.x, player.y);
}
function updateZombie(zombie) {
// Move zombie closer to player
if (zombie.x > player.x)
zombie.x -= zombieSpeed;
else if (zombie.x < player.x)
zombie.x += zombie.Speed;
if (zombie.y > player.y)
zombie.y -= zombieSpeed;
else if (zombie.y < player.y)
zombie.y += zombie.Speed;
context.drawImage(zombie.undead, zombie.x, zombie.y);
}
This line here:
zombie.undead.src = 'zombie.png';
will only change the last zombie created. You really need to move that:
for (var i = 0; i < 10; i += 1) {
var zombie = {
x : 10,
y : 10,
undead : new Image(),
targetToGox : 0,
targetToGoy : 0,
};
zombie.undead.src = 'zombie.png';
zombies.push(zombie);
}

detecting collisions on the canvas

im trying to make a game where collisions happen between the protagonist and the antagonist. I cant get the collision to work though, i've tried using the x and y position then the x and y positions plus the width and the height of the protagonist and the antagonist
var canvas = document.getElementById('canvas');
var PX = 10;
var PY = 10;
var PW = 10;
var PH = 10;
var P = PX + PY;
var EX1 = 100;
var EY1 = 100;
var EW1 = 10;
var EH1 = 10;
var E1 = EX1 + EY1;
window.addEventListener("keydown", charMove);
window.addEventListener("keyup", charMove);
window.addEventListener("keypress", charMove);
window.onload = function() {
context = canvas.getContext("2d");
canvas.style.background = "black";
var framesPerSecond = 30;
setInterval(function() {
draw();
move();
}, 1000/framesPerSecond);
}
function draw() {
//EX context.fillRect(PosX, PosY, width, height);
//draws protagonist
context.beginPath();
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "blue"
context.fillRect(PX, PY, PW, PH);
context.stroke();
context.closePath();
//draws antagonist(s)
context.beginPath();
context.fillStlyle = "red";
context.fillRect(EX1, EY1, EW1, EH1);
context.stroke();
context.closePath();
}
function move() {
}
function charMove(){
var x = event.which || event.keyCode;
if(x == 37){
PX -= 1;
}
if(x == 38){
PY -= 1;
}
if(x == 39){
PX += 1;
}
if(x == 40){
PY += 1;
}
}
//detect collision
setInterval(function() {
if(PX > EX1 || PX + PW < EX1 + EW1 && PY + PH > EY1 + EH1 || PY + PH < EY1 + EH1){
window.alert("collision");
}
}, 1);
<html>
<head>
</head>
<body>
<canvas width="500px" height="500px" id="canvas" class="canvas">
</body>
</html>
Your formula for collision is wrong.
This problem is called Axis Aligned Bounding Box collision.
Two AABBs collide if their projections to each axis collide. In your 2-dimensinal case you have to consider the horizontal and vertical projections.
The projections are segments of 1-d space. Collision for those is very easy: if the start or the end of a segment is on the other they collide. Formally start2 <= start1 <= end2 or start2 <= end1 <= end2
In code:
intersects([p.x, p.x + p.width], [e.x, e.x + e.width]) && intersects([p.y, p.y + p.height], [e.y, e.y + e.height])
where
function intersects(seg1, seg2) {
return contains(seg1, seg2[0]) || contains(seg1, seg2[1])
}
function contains(segment, point) {
return segment[0] <= point && point <= segment[1]
}

How to perform per pixel collision test for transparent images? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
If I have two partially transparent images (GIF, PNG, SVG etc.), how do I check if the non-transparent areas of the images intersect?
I'm fine with using canvas if it's necessary. The solution needs to work with all image formats that support transparency. No jQuery please.
Touching
Not Touching
Fast GPU assisted Pixel / Pixel collisions using 2D API.
By using the 2D context globalCompositeOperation you can greatly increase the speed of pixel pixel overlap test.
destination-in
The comp operation "destination-in" will only leave pixels that are visible on the canvas and the image you draw on top of it. Thus you create a canvas, draw one image, then set the comp operation to "destination-in" then draw the second image. If any pixels are overlapping then they will have a non zero alpha. All you do then is read the pixels and if any of them are not zero you know there is an overlap.
More speed
Testing all the pixels in the overlapping area will be slow. You can get the GPU to do some math for you and scale the composite image down. There is some loss as pixels are only 8bit values. This can be overcome by reducing the image in steps and rendering the results several times. Each reduction is like calculating a mean. I scale down by 8 effectively getting the mean of 64 pixels. To stop pixels at the bottom of the range disappearing due to rounding I draw the image several times. I do it 32 time which has the effect of multiplying the alpha channel by 32.
Extending
This method can easily be modified to allow both images to be scaled, skewed and rotated without any major performance hit. You can also use it to test many images with it returning true if all images have pixels overlapping.
Pixels are small so you can get extra speed if you reduce the image size before creating the test canvas in the function. This can give a significant performance boost.
There is a flag reuseCanvas that allows you to reuse the working canvases. If you use the test function a lot (many times a second) then set the flag to true. If you only need the test every now and then then set it to false.
Limits
This method is good for large images that need occasional tests; it is not good for small images and many tests per frame (such as in games where you may need to test 100's of images). For fast (almost perfect pixel) collision tests see Radial Perimeter Test.
The test as a function.
// Use the options to set quality of result
// Slow but perfect
var slowButPerfect = false;
// if reuseCanvas is true then the canvases are resused saving some time
const reuseCanvas = true;
// hold canvas references.
var pixCanvas;
var pixCanvas1;
// returns true if any pixels are overlapping
// img1,img2 the two images to test
// x,y location of img1
// x1,y1 location of img2
function isPixelOverlap(img1,x,y,img2,x1,y1){
var ax,aw,ay,ah,ctx,canvas,ctx1,canvas1,i,w,w1,h,h1;
w = img1.width;
h = img1.height;
w1 = img2.width;
h1 = img2.height;
// function to check if any pixels are visible
function checkPixels(context,w,h){
var imageData = new Uint32Array(context.getImageData(0,0,w,h).data.buffer);
var i = 0;
// if any pixel is not zero then there must be an overlap
while(i < imageData.length){
if(imageData[i++] !== 0){
return true;
}
}
return false;
}
// check if they overlap
if(x > x1 + w1 || x + w < x1 || y > y1 + h1 || y + h < y1){
return false; // no overlap
}
// size of overlapping area
// find left edge
ax = x < x1 ? x1 : x;
// find right edge calculate width
aw = x + w < x1 + w1 ? (x + w) - ax : (x1 + w1) - ax
// do the same for top and bottom
ay = y < y1 ? y1 : y;
ah = y + h < y1 + h1 ? (y + h) - ay : (y1 + h1) - ay
// Create a canvas to do the masking on
if(!reuseCanvas || pixCanvas === undefined){
pixCanvas = document.createElement("canvas");
}
pixCanvas.width = aw;
pixCanvas.height = ah;
ctx = pixCanvas.getContext("2d");
// draw the first image relative to the overlap area
ctx.drawImage(img1,x - ax, y - ay);
// set the composite operation to destination-in
ctx.globalCompositeOperation = "destination-in"; // this means only pixels
// will remain if both images
// are not transparent
ctx.drawImage(img2,x1 - ax, y1 - ay);
ctx.globalCompositeOperation = "source-over";
// are we using slow method???
if(slowButPerfect){
if(!reuseCanvas){ // are we keeping the canvas
pixCanvas = undefined; // no then release referance
}
return checkPixels(ctx,aw,ah);
}
// now draw over its self to amplify any pixels that have low alpha
for(var i = 0; i < 32; i++){
ctx.drawImage(pixCanvas,0,0);
}
// create a second canvas 1/8th the size but not smaller than 1 by 1
if(!reuseCanvas || pixCanvas1 === undefined){
pixCanvas1 = document.createElement("canvas");
}
ctx1 = pixCanvas1.getContext("2d");
// reduced size rw, rh
rw = pixCanvas1.width = Math.max(1,Math.floor(aw/8));
rh = pixCanvas1.height = Math.max(1,Math.floor(ah/8));
// repeat the following untill the canvas is just 64 pixels
while(rw > 8 && rh > 8){
// draw the mask image several times
for(i = 0; i < 32; i++){
ctx1.drawImage(
pixCanvas,
0,0,aw,ah,
Math.random(),
Math.random(),
rw,rh
);
}
// clear original
ctx.clearRect(0,0,aw,ah);
// set the new size
aw = rw;
ah = rh;
// draw the small copy onto original
ctx.drawImage(pixCanvas1,0,0);
// clear reduction canvas
ctx1.clearRect(0,0,pixCanvas1.width,pixCanvas1.height);
// get next size down
rw = Math.max(1,Math.floor(rw / 8));
rh = Math.max(1,Math.floor(rh / 8));
}
if(!reuseCanvas){ // are we keeping the canvas
pixCanvas = undefined; // release ref
pixCanvas1 = undefined;
}
// check for overlap
return checkPixels(ctx,aw,ah);
}
The demo (Use full page)
The demo lets you compare the two methods. The mean time for each test is displayed. (will display NaN if no tests done)
For the best results view the demo full page.
Use left or right mouse buttons to test for overlap. Move the splat image over the other to see overlap result. On my machine I am getting about 11ms for the slow test and 0.03ms for the quick test (using Chrome, much faster on Firefox).
I have not spent much time testing how fast I can get it to work but there is plenty of room to increase the speed by reducing the number of time the images are drawn over each other. At some point faint pixels will be lost.
// Use the options to set quality of result
// Slow but perfect
var slowButPerfect = false;
const reuseCanvas = true;
var pixCanvas;
var pixCanvas1;
// returns true if any pixels are overlapping
function isPixelOverlap(img1,x,y,w,h,img2,x1,y1,w1,h1){
var ax,aw,ay,ah,ctx,canvas,ctx1,canvas1,i;
// function to check if any pixels are visible
function checkPixels(context,w,h){
var imageData = new Uint32Array(context.getImageData(0,0,w,h).data.buffer);
var i = 0;
// if any pixel is not zero then there must be an overlap
while(i < imageData.length){
if(imageData[i++] !== 0){
return true;
}
}
return false;
}
// check if they overlap
if(x > x1 + w1 || x + w < x1 || y > y1 + h1 || y + h < y1){
return false; // no overlap
}
// size of overlapping area
// find left edge
ax = x < x1 ? x1 : x;
// find right edge calculate width
aw = x + w < x1 + w1 ? (x + w) - ax : (x1 + w1) - ax
// do the same for top and bottom
ay = y < y1 ? y1 : y;
ah = y + h < y1 + h1 ? (y + h) - ay : (y1 + h1) - ay
// Create a canvas to do the masking on
if(!reuseCanvas || pixCanvas === undefined){
pixCanvas = document.createElement("canvas");
}
pixCanvas.width = aw;
pixCanvas.height = ah;
ctx = pixCanvas.getContext("2d");
// draw the first image relative to the overlap area
ctx.drawImage(img1,x - ax, y - ay);
// set the composite operation to destination-in
ctx.globalCompositeOperation = "destination-in"; // this means only pixels
// will remain if both images
// are not transparent
ctx.drawImage(img2,x1 - ax, y1 - ay);
ctx.globalCompositeOperation = "source-over";
// are we using slow method???
if(slowButPerfect){
if(!reuseCanvas){ // are we keeping the canvas
pixCanvas = undefined; // no then release reference
}
return checkPixels(ctx,aw,ah);
}
// now draw over its self to amplify any pixels that have low alpha
for(var i = 0; i < 32; i++){
ctx.drawImage(pixCanvas,0,0);
}
// create a second canvas 1/8th the size but not smaller than 1 by 1
if(!reuseCanvas || pixCanvas1 === undefined){
pixCanvas1 = document.createElement("canvas");
}
ctx1 = pixCanvas1.getContext("2d");
// reduced size rw, rh
rw = pixCanvas1.width = Math.max(1,Math.floor(aw/8));
rh = pixCanvas1.height = Math.max(1,Math.floor(ah/8));
// repeat the following untill the canvas is just 64 pixels
while(rw > 8 && rh > 8){
// draw the mask image several times
for(i = 0; i < 32; i++){
ctx1.drawImage(
pixCanvas,
0,0,aw,ah,
Math.random(),
Math.random(),
rw,rh
);
}
// clear original
ctx.clearRect(0,0,aw,ah);
// set the new size
aw = rw;
ah = rh;
// draw the small copy onto original
ctx.drawImage(pixCanvas1,0,0);
// clear reduction canvas
ctx1.clearRect(0,0,pixCanvas1.width,pixCanvas1.height);
// get next size down
rw = Math.max(1,Math.floor(rw / 8));
rh = Math.max(1,Math.floor(rh / 8));
}
if(!reuseCanvas){ // are we keeping the canvas
pixCanvas = undefined; // release ref
pixCanvas1 = undefined;
}
// check for overlap
return checkPixels(ctx,aw,ah);
}
function rand(min,max){
if(max === undefined){
max = min;
min = 0;
}
var r = Math.random() + Math.random() + Math.random() + Math.random() + Math.random();
r += Math.random() + Math.random() + Math.random() + Math.random() + Math.random();
r /= 10;
return (max-min) * r + min;
}
function createImage(w,h){
var c = document.createElement("canvas");
c.width = w;
c.height = h;
c.ctx = c.getContext("2d");
return c;
}
function createCSSColor(h,s,l,a) {
var col = "hsla(";
col += (Math.floor(h)%360) + ",";
col += Math.floor(s) + "%,";
col += Math.floor(l) + "%,";
col += a + ")";
return col;
}
function createSplat(w,h,hue, hue2){
w = Math.floor(w);
h = Math.floor(h);
var c = createImage(w,h);
if(hue2 !== undefined) {
c.highlight = createImage(w,h);
}
var maxSize = Math.min(w,h)/6;
var pow = 5;
while(maxSize > 4 && pow > 0){
var count = Math.min(100,Math.pow(w * h,1/pow) / 2);
while(count-- > 0){
const rhue = rand(360);
const s = rand(25,75);
const l = rand(25,75);
const a = (Math.random()*0.8+0.2).toFixed(3);
const size = rand(4,maxSize);
const x = rand(size,w - size);
const y = rand(size,h - size);
c.ctx.fillStyle = createCSSColor(rhue + hue, s, l, a);
c.ctx.beginPath();
c.ctx.arc(x,y,size,0,Math.PI * 2);
c.ctx.fill();
if (hue2 !== undefined) {
c.highlight.ctx.fillStyle = createCSSColor(rhue + hue2, s, l, a);
c.highlight.ctx.beginPath();
c.highlight.ctx.arc(x,y,size,0,Math.PI * 2);
c.highlight.ctx.fill();
}
}
pow -= 1;
maxSize /= 2;
}
return c;
}
var splat1,splat2;
var slowTime = 0;
var slowCount = 0;
var notSlowTime = 0;
var notSlowCount = 0;
var onResize = function(){
ctx.font = "14px arial";
ctx.textAlign = "center";
splat1 = createSplat(rand(w/2, w), rand(h/2, h), 0, 100);
splat2 = createSplat(rand(w/2, w), rand(h/2, h), 100);
}
function display(){
ctx.clearRect(0,0,w,h)
ctx.setTransform(1.8,0,0,1.8,w/2,0);
ctx.fillText("Fast GPU assisted Pixel collision test using 2D API",0, 14)
ctx.setTransform(1,0,0,1,0,0);
ctx.fillText("Hold left mouse for Traditional collision test. Time : " + (slowTime / slowCount).toFixed(3) + "ms",w /2 , 28 + 14)
ctx.fillText("Hold right (or CTRL left) mouse for GPU assisted collision. Time: "+ (notSlowTime / notSlowCount).toFixed(3) + "ms",w /2 , 28 + 28)
if((mouse.buttonRaw & 0b101) === 0) {
ctx.drawImage(splat1, w / 2 - splat1.width / 2, h / 2 - splat1.height / 2)
ctx.drawImage(splat2, mouse.x - splat2.width / 2, mouse.y - splat2.height / 2);
} else if(mouse.buttonRaw & 0b101){
if((mouse.buttonRaw & 1) && !mouse.ctrl){
slowButPerfect = true;
}else{
slowButPerfect = false;
}
var now = performance.now();
var res = isPixelOverlap(
splat1,
w / 2 - splat1.width / 2, h / 2 - splat1.height / 2,
splat1.width, splat1.height,
splat2,
mouse.x - splat2.width / 2, mouse.y - splat2.height / 2,
splat2.width,splat2.height
)
var time = performance.now() - now;
ctx.drawImage(res ? splat1.highlight: splat1, w / 2 - splat1.width / 2, h / 2 - splat1.height / 2)
ctx.drawImage(splat2, mouse.x - splat2.width / 2, mouse.y - splat2.height / 2);
if(slowButPerfect){
slowTime += time;
slowCount += 1;
}else{
notSlowTime = time;
notSlowCount += 1;
}
if(res){
ctx.setTransform(2,0,0,2,mouse.x,mouse.y);
ctx.fillText("Overlap detected",0,0)
ctx.setTransform(1,0,0,1,0,0);
}
//mouse.buttonRaw = 0;
}
}
// Boilerplate code below
const RESIZE_DEBOUNCE_TIME = 100;
var w, h, cw, ch, canvas, ctx, mouse, createCanvas, resizeCanvas, setGlobals, globalTime = 0, resizeCount = 0;
var firstRun = true;
createCanvas = function () {
var c,
cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if(firstRun){
onResize();
firstRun = false;
}else{
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") {
m.buttonRaw |= m.bm[e.which - 1];
} else if (t === "mouseup") {
m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") {
m.buttonRaw = 0;
m.over = false;
} else if (t === "mouseover") {
m.over = true;
}
e.preventDefault();
}
m.start = function (element) {
if (m.element !== undefined) {
m.removeMouse();
}
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => {
m.element.removeEventListener(n, mouseMove);
});
m.element = undefined;
m.active = false;
}
}
return mouse;
})();
resizeCanvas();
mouse.start(canvas, true);
window.addEventListener("resize", resizeCanvas);
function update1(timer) { // Main update loop
if(ctx === undefined){
return;
}
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update1);
}
requestAnimationFrame(update1);

How to make collision in HTML5 Canvas?

I'm making a game in JavaScript HTML5 Canvas. It is a 2D Minecraft-like game, but I can't figure out collision detection...
I have asked my brother for help, but he doesn't know how to
make collision detection in HTML5 Canvas JavaScript.
If someone can help me, I will be very grateful.
This is my game code:
var g = document.getElementById("canvas").getContext("2d");
setInterval(update, 1000/100);
window.addEventListener("keydown", function(e) {
keys[e.keyCode] = true;
});
window.addEventListener("keyup", function(e) {
delete keys[e.keyCode];
});
var keys = [];
var player = {
x: 0,
y: 0,
isFalling: true,
};
var up = true;
var left = true;
var right = true;
var w = 10;
var h = 10;
var block_size = 20;
var m = {};
for(var x = 0; x < w; x++) {
m[x] = {};
for(var y = 0; y < w; y++) {
m[x][y] = "sky";
}
}
generateWorld();
function isColl(x1,y1,w1,h1,x2,y2,w2,h2) {
return(x1 <= x2 && x1+w1 >= x2 && y1 <= y2 && y1+h1 >= y2 || x2 <= x1 && x2+w2 >= x1 && y2 <= y1 && y2+h2 >= y1);
}
function update() {
g.fillStyle = "rgb(255,255,255)";
g.fillRect(0,0,200,200);
var dx = player.x/block_size;
var dy = player.y/block_size;
for(var x = 0; x < w; x++) {
for(var y = 0; y < h; y++) {
if(m[x][y] == "grass") {
g.fillStyle = "rgb(0,200,0)";
g.fillRect(x*20,y*20,20,20);
} else if(m[x][y] == "sky") {
g.fillStyle = "rgb(100,100,255)";
g.fillRect(x*20,y*20,20,20);
} else if(m[x][y] == "dirt") {
g.fillStyle = "rgb(100,40,0)";
g.fillRect(x*20,y*20,20,20);
} else if(m[x][y] == "grass") {
g.fillStyle = "rgb(0,0,255)";
g.fillRect(x*20,y*20,20,20);
} else {
g.fillStyle = "rgb(200,50,200)";
g.fillRect(x*20,y*20,20,20);
g.fillStyle = "rgb(0,0,0)";
g.fillText("404", (x*20), (y*20)+15);
}
}
}
g.fillStyle = "rgb(255,50,0)";
g.fillRect(player.x,player.y,20,20);
if(keys[38] && player.y > 0 && up) {
player.y--;
player.isFalling = false;
} else {
player.isFalling = true;
}
if(keys[39] && player.x < 180 && right) {
player.x++;
}
if(keys[37] && player.x > 0 && left) {
player.x--;
}
if(player.isFalling && player.y < 180) {
player.y++;
}
}
Thanks!
I think this might help you a lot:
https://msdn.microsoft.com/en-us/library/gg589497%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
There are couple of ways of doing this.
Consider all of your objects as spheres and check if the distance between objects are below the radius of the said spheres.
Example:
function checkCollision_Sph(centerX,centerY,radius,centerX_2,centerY_2,radius_2){
distance = Math.sqrt((centerX_2 - centerX)^2 + (centerY_2 - centerY)^2)
if(distance <= (radius+radius_2)){
//Distance between spheres is less than the sum of theirs radius, colliding!
}
Consider all of your objects as rectangles and check if the rectangle's bounding boxes are overlapping each other.
Example:
function checkCollision_Rec(x1,w1,y1,h1,x2,w2,y2,h2){
//x1, x2 = Left
//x1 + w1, x2 + w2 = Right
//y1, y2 = Bottom
//y1 - h1, y2 - h2 = Top
if((y1 < y2) ||
((y1 - h1) > y2) ||
(x1 > (x2 + w2)) ||
((x1 + w1) < x2)
){
//Is not colliding!
}
else{
//Is colliding!
}
I found a good page that has similar examples that might help you to figure it out: Basic collision detection in 2d

dotted stroke in <canvas>

I guess it is not possible to set stroke property such as CSS which is quite easy. With CSS we have dashed, dotted, solid but on canvas when drawing lines/or strokes this doesn't seem to be an option. How have you implemented this?
I've seen some examples but they are really long for such a silly function.
For example:
http://groups.google.com/group/javascript-information-visualization-toolkit/browse_thread/thread/22000c0d0a1c54f9?pli=1
Fun question! I've written a custom implementation of dashed lines; you can try it out here. I took the route of Adobe Illustrator and allow you to specify an array of dash/gap lengths.
For stackoverflow posterity, here's my implementation (slightly altered for s/o line widths):
var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if (CP && CP.lineTo){
CP.dashedLine = function(x,y,x2,y2,dashArray){
if (!dashArray) dashArray=[10,5];
if (dashLength==0) dashLength = 0.001; // Hack for Safari
var dashCount = dashArray.length;
this.moveTo(x, y);
var dx = (x2-x), dy = (y2-y);
var slope = dx ? dy/dx : 1e15;
var distRemaining = Math.sqrt( dx*dx + dy*dy );
var dashIndex=0, draw=true;
while (distRemaining>=0.1){
var dashLength = dashArray[dashIndex++%dashCount];
if (dashLength > distRemaining) dashLength = distRemaining;
var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
if (dx<0) xStep = -xStep;
x += xStep
y += slope*xStep;
this[draw ? 'lineTo' : 'moveTo'](x,y);
distRemaining -= dashLength;
draw = !draw;
}
}
}
To draw a line from 20,150 to 170,10 with dashes that are 30px long followed by a gap of 10px, you would use:
myContext.dashedLine(20,150,170,10,[30,10]);
To draw alternating dashes and dots, use (for example):
myContext.lineCap = 'round';
myContext.lineWidth = 4; // Lines 4px wide, dots of diameter 4
myContext.dashedLine(20,150,170,10,[30,10,0,10]);
The "very short" dash length of 0 combined with the rounded lineCap results in dots along your line.
If anyone knows of a way to access the current point of a canvas context path, I'd love to know about it, as it would allow me to write this as ctx.dashTo(x,y,dashes) instead of requiring you to re-specify the start point in the method call.
This simplified version of Phrogz's code utilises the built-in transformation functionality of Canvas and also handles special cases e.g. when dx = 0
var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if (CP.lineTo) {
CP.dashedLine = function(x, y, x2, y2, da) {
if (!da) da = [10,5];
this.save();
var dx = (x2-x), dy = (y2-y);
var len = Math.sqrt(dx*dx + dy*dy);
var rot = Math.atan2(dy, dx);
this.translate(x, y);
this.moveTo(0, 0);
this.rotate(rot);
var dc = da.length;
var di = 0, draw = true;
x = 0;
while (len > x) {
x += da[di++ % dc];
if (x > len) x = len;
draw ? this.lineTo(x, 0): this.moveTo(x, 0);
draw = !draw;
}
this.restore();
}
}
I think my calculations are correct and it seems to render OK.
At the moment at least setLineDash([5,10]) works with Chrome and ctx.mozDash = [5,10] works with FF:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
if ( ctx.setLineDash !== undefined ) ctx.setLineDash([5,10]);
if ( ctx.mozDash !== undefined ) ctx.mozDash = [5,10];
ctx.beginPath();
ctx.lineWidth="2";
ctx.strokeStyle="green";
ctx.moveTo(0,75);
ctx.lineTo(250,75);
ctx.stroke();
Setting to null makes the line solid.
Phroz's solution is great. But when I used it in my application, I found two bugs.
Following code is debugged (and refactored for readability) version of Phroz's one.
// Fixed: Minus xStep bug (when x2 < x, original code bugs)
// Fixed: Vertical line bug (when abs(x - x2) is zero, original code bugs because of NaN)
var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if(CP && CP.lineTo) CP.dashedLine = function(x, y, x2, y2, dashArray){
if(! dashArray) dashArray=[10,5];
var dashCount = dashArray.length;
var dx = (x2 - x);
var dy = (y2 - y);
var xSlope = (Math.abs(dx) > Math.abs(dy));
var slope = (xSlope) ? dy / dx : dx / dy;
this.moveTo(x, y);
var distRemaining = Math.sqrt(dx * dx + dy * dy);
var dashIndex = 0;
while(distRemaining >= 0.1){
var dashLength = Math.min(distRemaining, dashArray[dashIndex % dashCount]);
var step = Math.sqrt(dashLength * dashLength / (1 + slope * slope));
if(xSlope){
if(dx < 0) step = -step;
x += step
y += slope * step;
}else{
if(dy < 0) step = -step;
x += slope * step;
y += step;
}
this[(dashIndex % 2 == 0) ? 'lineTo' : 'moveTo'](x, y);
distRemaining -= dashLength;
dashIndex++;
}
}
Mozilla has been working on an implementation of dashed stroking for canvas, so we may see it added to the spec in the near future.
There's a much simpler way to do this. According to http://www.w3.org/TR/2dcontext/#dom-context-2d-strokestyle strokeStyle accepts strings, CanvasGradients, or CanvasPatterns. So we just take an image like this:
<img src="images/dashedLineProto.jpg" id="cvpattern1" width="32" height="32" />
load it into a canvas, and draw our little rectangle with it.
var img=document.getElementById("cvpattern1");
var pat=ctx.createPattern(img,"repeat");
ctx.strokeStyle = pat;
ctx.strokeRect(20,20,150,100);
that doesnt result in a perfect dashed line, but it's really straightforward and modifiable. Results may of course become imperfect when you're drawing lines which arent horizontal or vertical, a dotted pattern might help there.
PS. keep in mind SOP applies when you're trying to use imgs from external sources in your code.
Looks like context.setLineDash is pretty much implemented.
See this.
" context.setLineDash([5])
will result in a dashed line where both the dashes and spaces are 5 pixels in size. "
There is currently no support in HTML5 Canvas specification for dashed lines.
check this out:
http://davidowens.wordpress.com/2010/09/07/html-5-canvas-and-dashed-lines/
or
Check out the Raphael JS Library:
http://raphaeljs.com/
There are support for it in Firefox at least
ctx.mozDash = [5,10];
seems like ctx.webkitLineDash worked before, but they removed it because it had some compabillity issues.
The W3C specs says ctx.setLineDash([5,10]); but it doesn't seem to be implemented yet anywhere.
I made modified the dashedLine function to add support for offsetting. It utilizes native dashed lines if the browser supports ctx.setLineDash and ctx.lineDashOffset.
Example: http://jsfiddle.net/mLY8Q/6/
var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if (CP.lineTo) {
CP.dashedLine = CP.dashedLine || function (x, y, x2, y2, da, offset) {
if (!da) da = [10, 5];
if (!offset) offset = 0;
if (CP.setLineDash && typeof (CP.lineDashOffset) == "number") {
this.save();
this.setLineDash(da);
this.lineDashOffset = offset;
this.moveTo(x, y);
this.lineTo(x2, y2);
this.restore();
return;
}
this.save();
var dx = (x2 - x),
dy = (y2 - y);
var len = Math.sqrt(dx * dx + dy * dy);
var rot = Math.atan2(dy, dx);
this.translate(x, y);
this.moveTo(0, 0);
this.rotate(rot);
var dc = da.length;
var di = 0;
var patternLength = 0;
for (var i = 0; i < dc; i++) {
patternLength += da[i];
}
if (dc % 2 == 1) {
patternLength *= 2;
}
offset = offset % patternLength;
if (offset < 0) {
offset += patternLength;
}
var startPos = 0;
var startSegment = 0;
while (offset >= startPos) {
if (offset >= startPos + da[startSegment % dc]) {
startPos += da[startSegment % dc];
startSegment++;
} else {
offset = Math.abs(offset - startPos);
break;
}
if (startSegment > 100) break;
}
draw = startSegment % 2 === 0;
x = 0;
di = startSegment;
while (len > x) {
var interval = da[di++ % dc];
if (x < offset) {
interval = Math.max(interval - offset, 1);
offset = 0;
}
x += interval;
if (x > len) x = len;
draw ? this.lineTo(x, 0) : this.moveTo(x, 0);
draw = !draw;
}
this.restore();
};
}
I found properties mozDash and mozDashOffset in Mozilla specification:
http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/canvas/nsIDOMCanvasRenderingContext2D.idl
They're probaly used to control dashes, but i haven't used them.

Categories

Resources