How to make collision in HTML5 Canvas? - javascript

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

Related

Land a character on a block after he jumps on it in p5.js game

I'm just trying to create a small 2d game where we control a character that can place block on the "map" with the mouse and jump on it to climb etc. It's just a test to practice.
I've achieved to make the character stops when he walk into blocks.
But, i'm facing a problem when I want my character to jump on a block, i'm not able to make him land on it. I can't find the condition to.
Can someone helps me on it ? The code that handle the collision between the character and blocks is in the draw function and in the for.
Controls
Left: Q
Right: D
Jump: Space Bar
Place a block: Mouse Click
Remove a block: Mouse Click
Edit: You might have ask yourself why i'm using some variables that are not declared anywhere. It's because they are given by P5.js. (ex: width)
function Man() {
this.w = 20;
this.h = 40;
this.x = width/2;
this.y = height - this.h;
this.canJump = true;
this.velocity = 0;
this.show = function(){
fill(255);
rect(this.x, this.y, this.w, this.h);
}
this.update = function(){
this.velocity += gravity;
this.y += this.velocity;
if(this.y + this.h > height) {
this.y = height - this.h ;
this.velocity = 0;
this.canJump = true;
}
if(this.x <= 0) {
this.x = 0;
}
if(this.x + this.w >= width) {
this.x = width - this.w;
}
}
this.jump = function(){
if(this.canJump) {
man.velocity -= 6;
this.canJump = false;
}
}
}
var man;
var ground;
var gravity = 0.4;
function setup(){
frameRate(60);
createCanvas(640, 480);
man = new Man();
}
var tilesSize = 20;
var tiles = [];
for(var i = 0; i < 640; i+= tilesSize) {
for(var j = 0; j < 480; j+= tilesSize) {
tiles.push({x:i, y:j, empty: true});
}
}
function draw(){
background(0);
noStroke();
push();
for(var i = 0; i < tiles.length; i++) {
if(!tiles[i].empty) {
fill(150);
rect(tiles[i].x, tiles[i].y, tilesSize, tilesSize);
}
if(mouseX >= tiles[i].x && mouseX < tiles[i].x + tilesSize && mouseY >= tiles[i].y && mouseY < tiles[i].y + tilesSize) {
if(tiles[i].empty) {
fill(50);
rect(tiles[i].x, tiles[i].y, tilesSize, tilesSize);
}else {
fill(255,0,0);
rect(tiles[i].x, tiles[i].y, tilesSize, tilesSize);
}
}
// HERE IS CONDITIONS FOR COLLISION WITH BLOCK
if(!tiles[i].empty) {
if(tiles[i].x + tilesSize >= man.x && tiles[i].x < man.x && tiles[i].y < man.y + man.h ) {
man.x = tiles[i].x + tilesSize;
}
if(man.x + man.w >= tiles[i].x && man.x <= tiles[i].x && tiles[i].y < man.y + man.h) {
man.x = tiles[i].x - man.w;
}
}
}
pop();
man.show();
man.update();
moveMan();
}
function moveMan(){
if(keyIsDown(81)) {
man.x -= 2;
}
if(keyIsDown(68)) {
man.x += 2;
}
}
function keyPressed() {
if(keyIsDown(32)) {
man.jump();
}
}
function mouseClicked() {
for(var i = 0; i < tiles.length; i++) {
if(mouseX >= tiles[i].x && mouseX < tiles[i].x + tilesSize && mouseY >= tiles[i].y && mouseY < tiles[i].y + tilesSize) {
if(tiles[i].empty) {
tiles[i].empty = false;
}else {
tiles[i].empty = true;
}
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.2/p5.min.js"></script>

how can I detect collision in a 2D tile game map

I made this basic game where I drew a map and a player, the player can move anywhere but how can I make so that it wont move when its on the tile[1] in the map?
also when I try to check if the player.x is greater than 50 it could go left it works but than if I click 2 keys at once it goes through
const context = document.querySelector("canvas").getContext("2d");
var rgb = 'rgb(' + Math.random()*256 + ',' + Math.random()*256 + ',' + Math.random()*256 + ','+Math.random() + ')';
document.onload = Loop();
var width = 1500;
var height = 800;
function Loop(){
var width = 1500;
var height = 800;
context.canvas.height = height;
context.canvas.width = width;
this.interval = setInterval(Update, 1000/100);
}
const Player = function(x, y, w, h, color) {
this.x = x; this.y = y; this.w = w; this.h = h;
this.speedY = 0; this.speedX = 0;
this.Draw = function(){
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
};
this.Move = function(){
this.x += this.speedX;
this.y += this.speedY;
};
};<code>
var player = new Player(100,100,50, 50, rgb);
var Key = {};
function Update(){
context.clearRect(0, 0, width, height);
Map();
player.Draw();
player.Move();
onkeydown = onkeyup = function(e){
player.speedX = 0;
player.speedY = 0;
e = e || event;
Key[e.keyCode] = e.type == 'keydown';
if(Key[37] || Key[65]) {player.speedX -= 2}
if(Key[38] || Key[87]) {player.speedY -= 2}
if(Key[39] || Key[68]) {player.speedX += 2}
if(Key[40] || Key[83]) {player.speedY += 2}
if(Key[32]) {player.color = 'rgb(' + Math.random()*256 + ',' + Math.random()*256 + ',' + Math.random()*256 + ','+Math.random()*1 + ')';}
};
}
var map = [
1, 1, 1, 1, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 1, 1, 1, 1
];
var row = 5;
var column = 5;
function Map(){
for(let y = -1; y < column; y++){
for(let x = -1; x < row; x++){
switch(map[((y*row) + x)]) {
case 0: context.fillStyle = player.color;
break;
case 1: context.fillStyle = "#ffffff";
break;
default: context.fillStyle = "#000000";
}
context.fillRect(x*50, y*50, 50, 50);
}
}
}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: black;
}
canvas {
display: block;
margin: auto;
border: solid 1px white;
border-radius: 10px;
}
script {
display: none;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="application/javascript">
void function() {
"use strict";
// Classes
function Camera(x,y) {
this.x = x || 0.0;
this.y = y || 0.0;
}
Camera.prototype = {
set: function(x,y) {
this.x = x || 0.0;
this.y = y || 0.0;
},
pan: function(x,y) {
this.x += x || 0.0;
this.y += y || 0.0;
}
};
var nextID = 0;
function Tile(colour) {
this.id = nextID++;
this.colour = colour || "black";
}
function Map(width,height) {
this.width = width || 1;
this.height = height || 1;
this.map = [];
this.map.length = this.height;
for (var y = 0; y < this.height; ++y) {
this.map[y] = [];
this.map[y].length = width;
for (var x = 0; x < this.width; ++x) {
this.map[y][x] = Math.random() < 0.2 ?
this.TILE_WALL:
this.TILE_GRASS;
}
this.map[y][0] = this.TILE_WALL;
this.map[y][this.width - 1] = this.TILE_WALL;
}
for (var x = 0; x < this.width; ++x) {
this.map[0][x] = this.TILE_WALL;
this.map[this.height - 1][x] = this.TILE_WALL;
}
}
Map.prototype = {
TILE_WIDTH: 32.0,
TILE_HEIGHT: 32.0,
INV_TILE_WIDTH: 0.0,
INV_TILE_HEIGHT: 0.0,
TILE_AIR: new Tile("#00000000"),
TILE_GRASS: new Tile("#00AA00FF"),
TILE_WALL: new Tile("#555555FF"),
set: function(x,y,tile) {
this.map[y][x] = tile;
},
scaleX: function(x) {
return (x * this.INV_TILE_WIDTH) | 0;
},
scaleY: function(y) {
return (y * this.INV_TILE_HEIGHT) | 0;
},
isColliding: function(x,y) {
return x > -1 && x < this.width
&& y > -1 && y < this.height
&& this.map[y][x].id > 1;
},
render: function(ctx,camera) {
for (var y = 0; y < this.height; ++y) {
for (var x = 0; x < this.width; ++x) {
var tile = this.map[y][x];
var _x = x * this.TILE_WIDTH - camera.x;
var _y = y * this.TILE_HEIGHT - camera.y;
ctx.fillStyle = tile.colour;
ctx.fillRect(_x,_y,this.TILE_WIDTH - 1,this.TILE_HEIGHT - 1);
}
}
}
};
Map.prototype.INV_TILE_WIDTH = 1.0 / Map.prototype.TILE_WIDTH;
Map.prototype.INV_TILE_HEIGHT = 1.0 / Map.prototype.TILE_HEIGHT;
function Player(x,y) {
this.x = x || 0.0;
this.y = y || 0.0;
this.dx = 0.0;
this.dy = 0.0;
this.isUp = false;
this.isDown = false;
this.isLeft = false;
this.isRight = false;
}
Player.prototype = {
WIDTH: 20.0,
HEIGHT: 20.0,
ACCELERATION: 1.0,
DEACCELERATION: 0.5,
MAX_SPEED: 3.0,
tick: function(map) {
// Movement
if (this.isUp) {
this.dy -= this.ACCELERATION;
if (this.dy < -this.MAX_SPEED) {
this.dy = -this.MAX_SPEED;
}
} else if (this.dy < 0.0) {
this.dy += this.DEACCELERATION;
if (this.dy > 0.0) {
this.dy = 0.0;
}
}
if (this.isDown) {
this.dy += this.ACCELERATION;
if (this.dy > this.MAX_SPEED) {
this.dy = this.MAX_SPEED;
}
} else if (this.dy > 0.0) {
this.dy -= this.DEACCELERATION;
if (this.dy < 0.0) {
this.dy = 0.0;
}
}
if (this.isLeft) {
this.dx -= this.ACCELERATION;
if (this.dx < -this.MAX_SPEED) {
this.dx = -this.MAX_SPEED;
}
} else if (this.dx < 0.0) {
this.dx += this.DEACCELERATION;
if (this.dx > 0.0) {
this.dx = 0.0;
}
}
if (this.isRight) {
this.dx += this.ACCELERATION;
if (this.dx > this.MAX_SPEED) {
this.dx = this.MAX_SPEED;
}
} else if (this.dx > 0.0) {
this.dx -= this.DEACCELERATION;
if (this.dx < 0.0) {
this.dx = 0.0;
}
}
// Collision
if (this.dx !== 0.0) {
var minY = map.scaleY(this.y);
var maxY = map.scaleY(this.y + this.HEIGHT);
var minX = 0;
var maxX = 0;
if (this.dx < 0.0) {
minX = map.scaleX(this.x + this.dx);
maxX = map.scaleX(this.x);
} else {
minX = map.scaleX(this.x + this.WIDTH);
maxX = map.scaleX(this.x + this.WIDTH + this.dx);
}
loop:
for (var y = minY; y <= maxY; ++y) {
for (var x = minX; x <= maxX; ++x) {
if (map.isColliding(x,y)) {
this.x = this.dx < 0.0 ?
(x + 1) * map.TILE_WIDTH:
x * map.TILE_WIDTH - this.WIDTH - 1;
this.dx = 0.0;
break loop;
}
}
}
}
if (this.dy !== 0.0) {
var minX = map.scaleX(this.x);
var maxX = map.scaleX(this.x + this.WIDTH);
var minY = 0;
var maxY = 0;
if (this.dy < 0.0) {
minY = map.scaleY(this.y + this.dy);
maxY = map.scaleY(this.y);
} else {
minY = map.scaleY(this.y + this.HEIGHT);
maxY = map.scaleY(this.y + this.HEIGHT + this.dy);
}
loop:
for (var y = minY; y <= maxY; ++y) {
for (var x = minX; x <= maxX; ++x) {
if (map.isColliding(x,y)) {
this.y = this.dy < 0.0 ?
(y + 1) * map.TILE_HEIGHT:
y * map.TILE_HEIGHT - this.HEIGHT - 1;
this.dy = 0.0;
break loop;
}
}
}
}
this.x += this.dx;
this.y += this.dy;
},
render: function(ctx,camera) {
camera.set(this.x,this.y);
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.fillStyle = "darkred";
ctx.beginPath();
ctx.rect(this.x - camera.x,this.y - camera.y,this.WIDTH,this.HEIGHT);
ctx.fill();
ctx.stroke();
}
};
// Variables
var canvasWidth = 180;
var canvasHeight = 160;
var canvas = null;
var ctx = null;
var camera = null;
var map = null;
var player = null;
// Functions
function onKeyDown(e) {
switch(e.key.toUpperCase()) {
case "W": player.isUp = true; break;
case "S": player.isDown = true; break;
case "A": player.isLeft = true; break;
case "D": player.isRight = true; break;
}
}
function onKeyUp(e) {
switch(e.key.toUpperCase()) {
case "W": player.isUp = false; break;
case "S": player.isDown = false; break;
case "A": player.isLeft = false; break;
case "D": player.isRight = false; break;
}
}
function loop() {
// Tick
player.tick(map);
// Render
ctx.fillStyle = "gray";
ctx.fillRect(-canvasWidth >> 1,-canvasHeight >> 1,canvasWidth,canvasHeight);
map.render(ctx,camera);
player.render(ctx,camera);
//
requestAnimationFrame(loop);
}
// Entry point (first to execute)
onload = function() {
canvas = document.getElementById("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
ctx = canvas.getContext("2d");
ctx.translate(canvasWidth >> 1,canvasHeight >> 1);
camera = new Camera(0.0,0.0);
map = new Map(10,10);
player = new Player(40.0,40.0);
map.set(1,1,map.TILE_GRASS);
addEventListener("keydown",onKeyDown);
addEventListener("keyup",onKeyUp);
loop();
}
}();
</script>
</body>
</html>
Firstly, looking at your code, there are some things that are missing which is required to implement basic collision detection and those are:
The player's current direction that he/she is moving in. This is important because it allows the function determining the collision detection to distinguish which side it is checking for the collision (Up, down, left, or right) since a player can only collide with one side at a time.
The tile's position and size. This is also very important because like the first point, there is only one side of the tile that the player can collide with and knowing the size and position can determine if it is a collision or not based on the players size and position.
Also, since you mentioned it is a basic game, the implementation below is a basic collision detection. If you were to make a more complex and bigger game, you should try looking into quad trees for more efficient collision detection:
https://gamedevelopment.tutsplus.com/tutorials/quick-tip-use-quadtrees-to-detect-likely-collisions-in-2d-space--gamedev-374
Now this is the function for detecting collision, for the sake of readability and shortness, p will represent the player object and t would represent the tile object. This function returns whether or not the player is colliding with a tile based on their direction of movement.
function isColliding(p, t){
if (p.direction == 'up') {
return p.y +(p.height/2)-p.speedY< t.y + t.height && p.y > t.y
&& p.x + p.width > t.x && p.x < t.x + t.width;
}
if (p.direction == 'down') {
return p.y + (p.height/2)+p.speedY > t.y && p.y < t.y
&& p.x + p.width > t.x && p.x < t.x + t.width;
}
if (p.direction == 'right') {
return p.x + p.width+p.speedX > t.x && p.x < t.x
&& p.y +(p.height/2)> t.y && p.y + p.height < t.y +t.height+ (p.height / 2);
}
if (p.direction == 'left') {
return p.x -p.speedX< t.x + t.width && p.x > t.x
&& p.y +(p.height/2)> t.y && p.y + p.height < t.y +t.height+ (p.height / 2);
}
return false;
}
You would probably want to put this in the player move function to constantly detect for tiles as it is moving. To do that, you'd want to modify your keydown detection so that with each different keydown, it would update the player's direction, here's a simple example:
document.onkeydown = function(event){
if (event.keyCode == 87)
player.up = true;
else if (event.keyCode == 65)
player.left = true;
else if (event.keyCode == 83)
player.down = true;
else if (event.keyCode == 68)
player.right = true;
}
and another simple example for every time the player moves (user presses a keydown):
const Player= function(/*Param stuff*/){
/*Property stuff*/
//tileArray is the array (or object, your choice) of all the current tiles in the map
this.move=function(tileArray){
//Go through all tiles to see if player is colliding with any of them
for(var t in tileArray){
if(this.up){
if(isColliding(this, tileArray[t]){
//functionality for when player collides
}else{
//functionality for when player doesn't collide
}
}
//check if player is going down, left, etc
}
}
}
These are just examples of how to implement the detection. You should use it as a reference to implement it relatively to how your code function because I didn't write it based on what you posted.
PS.
Make sure to also convert the directions to false after the user stops pressing the key.

I am making an Atari Breakout clone & I am wondering how to tell if the ball hits a certain side of a block

I have gotten the part where when the ball collides with the block it deletes the block, but I am also wanting to tell if the ball hits the top or bottom or left or right of the block and bounce accordingly. I have attempted it, but it's not working quite right. It just freaks out jumping around. I have deleted that portion from the code below as it does not work. Can anyone help me out with this problem? Maybe give an example or tell me how it would work?
<canvas id="can" height="500" width="1000"></canvas>
var c = document.getElementById("can");
var ctx = c.getContext("2d");
var blocks= [];
var paddle = {x:450,y:480,h:10,w:100};
var ball = {r:7,x:500,y:469};
var rows=[0,1,2,3,4];
var px = paddle.x, py = paddle.y;
var pxv=0;
var by = ball.y, bx = ball.x;
var bxv = -1.5, byv = -1.5;
function Block(h,w,x,y,c) {
this.h = h;
this.w = w;
this.x = x;
this.y = y;
this.c = c;
}
for(var i =0, len=rows.length;i<len;i++){
for(var j=0; j<20;j++) {
blocks.push(new Block(20,50,j*50,i*20,rows[i]))
}
}
document.addEventListener("keydown",keyPush);
document.addEventListener("keyup",keyRelease);
function keyRelease(evt) {
switch(evt.keyCode) {
case 37:
pxv=0;
break;
case 39:
pxv=0;
break;
}
}
function keyPush(evt) {
switch(evt.keyCode) {
case 37:
pxv=-5;
break;
case 39:
pxv=5
break;
}
}
function AABBIntersect(ax, ay, aw, ah, bx, by, bw, bh) {
return ax < bx+bw && bx < ax+aw && ay < by+bh && by < ay+ah;
};
function draw(){
ctx.clearRect(0,0,1000,500)
bx+=bxv;
by+=byv;
px+=pxv;
if(px > 900) {
px = 900;
}
else if(px < 0) {
px = 0;
}
for(var i = 0, len=blocks.length;i<len;i++) {
var bl = blocks[i];
if(AABBIntersect(bx,by,ball.r,ball.r,bl.x,bl.y,bl.w,bl.h)) {
blocks.splice(i,1);
i--;
len--;
}
}
if(bx < 0) {
bxv = bxv*-1;
}
if(bx > 1000) {
bxv = bxv*-1;
}
ctx.fillStyle = "#ff4b38"
ctx.fillRect(px,py,paddle.w,paddle.h);
for(var i = 0, len=blocks.length; i<len; i++){
if(blocks[i].c === 0) {
ctx.fillStyle = "#ff4b38"
}
else if(blocks[i].c === 1) {
ctx.fillStyle = "#ffba19"
}
else if(blocks[i].c === 2) {
ctx.fillStyle = "#fcee25"
}
else if(blocks[i].c === 3) {
ctx.fillStyle = "#26db02"
}
else if(blocks[i].c === 4) {
ctx.fillStyle = "#2d69ff"
}
ctx.fillRect(blocks[i].x,blocks[i].y,blocks[i].w,blocks[i].h);
ctx.beginPath();
ctx.arc(bx,by,ball.r,0,2*Math.PI,false);
ctx.fillStyle = "gray";
ctx.fill();
}
}
setInterval(draw,10);
I'm sure there are more ways to do it but this is how I would do it.
Inside your collision detection function you should have if-statements to detect if from the x or y side. You made need to tweak it as not sure if it will error later but the brunt of it all is like this:
function AABBIntersect(ax, ay, aw, ah, bx, by, bw, bh) {
var bool = ax < bx + bw && bx < ax + aw && ay < by + bh && by < ay + ah;
if(bool){
if(ax == bx || ax == bx + bw){
bxv *= -1;
cl("x");
}else{
byv *= -1;
cl("y");
}
}
return bool;
};
Your next issue is you have no paddle collision detection, so it will bounce back but it will go through your paddle. So you can do the following in your draw() function. I put it after your if(bx > 1000):
if(bx >= px && bx <= px + paddle.w && by >= py && by <= py + paddle.h){
byv *= -1;
}
I would also put your setInterval on a var so you can clear it when either all the blocks are gone or your ball goes below the paddle. Otherwise it's just going to go everywhere infinitely.

Snake game: how to check if the head collides with its own body

You may have played Snake, a game where you have to eat food to grow and you fail if you collide with the snake's body or certain obstacles. The first part was easy, but the latter seems impossible to achieve.
I have tried to make a for loop check if the last element of my snake array is colliding with its other parts. My condition was like this: if the x position of the last item in my array is bigger than any of the array items' x position, and smaller than their x position plus their width, and so on. That didn't work.
Here's my code :
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="myCanvas" width="200px" height="200px" style="border:1px solid black"/>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var yPos = 20;
var width = 15;
var variable = 1;
var currentDir = 1;
//var xPos = (width+5)*variable;
var xPos = 20;
var myArr = [{myX:xPos,myY:yPos},{myX:xPos,myY:yPos},{myX:xPos,myY:yPos}];
var downPressed = false;
var upPressed = false;
var leftPressed = false;
var rightPressed = false;
var first = [0,20,40,60,80,100,120,140,160,180];
var firstX = Math.floor(Math.random()*10);
var firstY = Math.floor(Math.random()*10);
var okayed = first[firstX];
var notOkayed = first[firstY];
var maths = myArr[myArr.length-1];
function drawFood() {
ctx.beginPath();
ctx.rect(okayed,notOkayed,15,15);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
}
function drawRectangle() {
ctx.clearRect(0,0,200,200);
drawFood();
for(var i = 0;i<myArr.length;i++) {
ctx.beginPath();
ctx.rect(myArr[i].myX,myArr[i].myY,width,15);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
requestAnimationFrame(drawRectangle);
}
setInterval("calledin()",100);
function calledin() {
var secondX = Math.floor(Math.random()*10);
var secondY = Math.floor(Math.random()*10);
var newobj = {myX:myArr[myArr.length-1].myX+20,myY:myArr[myArr.length-1].myY};
var newobjTwo = {myX:myArr[myArr.length-1].myX,myY:myArr[myArr.length-1].myY+20};
var newobjLeft = {myX:myArr[myArr.length-1].myX-20,myY:myArr[myArr.length-1].myY};
var newobjUp = {myX:myArr[myArr.length-1].myX,myY:myArr[myArr.length-1].myY-20};
var okayNewObj = {myX:myArr[1].myX - 20,myY:myArr[1].myY};
if(myArr[myArr.length-1].myX > 180 || myArr[myArr.length-1].myX < 0 || myArr[myArr.length-1].myY > 180 || myArr[myArr.length-1].myY < 0)
{alert("Game Over");window.location.reload();}
if(myArr[myArr.length-1].myX > okayed-5 && myArr[myArr.length-1].myX < okayed+20 && myArr[myArr.length-1].myY < notOkayed+20 &&
myArr[myArr.length-1].myY > notOkayed-5) {
okayed = first[secondX];
notOkayed = first[secondY];
myArr.unshift(okayNewObj);
}
if(currentDir == 1) {
myArr.push(newobj);
myArr.shift();}
if(currentDir == 2) {
myArr.push(newobjTwo);
myArr.shift();
}
if(currentDir == 4) {
myArr.push(newobjLeft);
myArr.shift();
}
if(currentDir == 3) {
myArr.push(newobjUp);
myArr.shift();
}
for(var i = 0;i<myArr.length-2;i++) {
if(myArr[myArr.length-1].myX > myArr[i].myX &&
myArr[myArr.length-1].myX < myArr[i].myX + 15 && myArr[myArr.length-1].myY > myArr[i].myY && myArr[myArr.length-1].myY > myArr[i].myY + 15)
{alert("Game over");window.location.reload();}
}
}
function downed(e) {
if(e.keyCode==40) {if(currentDir != 3) {currentDir = 2;}}
if(e.keyCode==38) {if(currentDir != 2) {currentDir = 3;}}
if(e.keyCode==39) {if(currentDir != 4) {currentDir = 1;}}
if(e.keyCode==37) {if(currentDir != 1) {currentDir = 4;}}
}
function upped(e) {
if(e.keyCode == 40) {downPressed = false;}
}
document.addEventListener("keydown",downed,false);
document.addEventListener("keyup",upped,false);
drawRectangle();
</script>
</body>
</html>
Suppose the snake is represented by an array called snake in which the head is at index snake.length - 1. We have to compare the position of the head against the positions of the body segments at indices 0 through snake.length - 2.
The following code sets okay to false if the snake head has collided with a body segment. Otherwise, okay remains true.
var head = snake[snake.length - 1],
x = head.x,
y = head.y,
okay = true;
for (var i = snake.length - 2; i >= 0; --i) {
if (snake[i].x == x && snake[i].y == y) {
okay = false;
break;
}
}
Below is a snippet in which I have modified your code to clarify the game logic and to simplify many of the calculations.
Instead of working directly with canvas coordinates, I represent each position with the column index x and row index y of a virtual grid cell. This lets us calculate the neighboring grid positions by adding 1 or -1 to x or y. When it comes time to paint the canvas, we multiply the virtual coordinates by the cell size.
I have replaced most of your literal values with variables. For example, instead of setting the canvas dimensions to 200 by 200, we can do this:
canvas.width = numCols * cellSize;
canvas.height = numRows * cellSize;
This lets us change numCols and numRows in one place to resize the whole game grid. All the calculations work out because they evaluate variables instead of using literals.
I altered the key-event handling to recognize the key codes for the W-A-S-D keys in addition to the arrow keys. When the game is embedded in a long web page, as it is here, you'll probably want to use the W-A-S-D keys so that the page doesn't scroll up and down while you're playing.
var canvas,
ctx,
currentDir,
startX = 1,
startY = 1,
startSnakeLength = 3,
snake,
cellSize = 18,
cellGap = 1,
foodColor = '#a2302a',
snakeBodyColor = '#2255a2',
snakeHeadColor = '#0f266b',
numRows = 10,
numCols = 10,
canvasWidth = numCols * cellSize,
canvasHeight = numRows * cellSize;
var food = {};
function placeFood() {
// Find a random location that isn't occupied by the snake.
var okay = false;
while (!okay) {
food.x = Math.floor(Math.random() * numCols);
food.y = Math.floor(Math.random() * numRows);
okay = true;
for (var i = 0; i < snake.length; ++i) {
if (snake[i].x == food.x && snake[i].y == food.y) {
okay = false;
break;
}
}
}
}
function paintCell(x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x * cellSize + cellGap,
y * cellSize + cellGap,
cellSize - cellGap,
cellSize - cellGap);
}
function paintCanvas() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
paintCell(food.x, food.y, foodColor);
var head = snake[snake.length - 1];
paintCell(head.x, head.y, snakeHeadColor);
for (var i = snake.length - 2; i >= 0; --i) {
paintCell(snake[i].x, snake[i].y, snakeBodyColor);
}
}
function updateGame() {
var head = snake[snake.length - 1],
x = head.x,
y = head.y;
// Move the snake.
var tail = snake.shift();
switch (currentDir) {
case 'up':
snake.push(head = { x: x, y: y - 1 });
break;
case 'right':
snake.push(head = { x: x + 1, y: y });
break;
case 'down':
snake.push(head = { x: x, y: y + 1 });
break;
case 'left':
snake.push(head = { x: x - 1, y: y });
break;
}
paintCanvas();
x = head.x;
y = head.y;
// Check for wall collision.
if (x < 0 || x >= numCols || y < 0 || y >= numRows) {
stopGame('wall collision');
return;
}
// Check for snake head colliding with snake body.
for (var i = snake.length - 2; i >= 0; --i) {
if (snake[i].x == x && snake[i].y == y) {
stopGame('self-collision');
return;
}
}
// Check for food.
if (x == food.x && y == food.y) {
placeFood();
snake.unshift(tail);
setMessage(snake.length + ' segments');
}
}
var dirToKeyCode = { // Codes for arrow keys and W-A-S-D.
up: [38, 87],
right: [39, 68],
down: [40, 83],
left: [37, 65]
},
keyCodeToDir = {}; // Fill this from dirToKeyCode on page load.
function keyDownHandler(e) {
var keyCode = e.keyCode;
if (keyCode in keyCodeToDir) {
currentDir = keyCodeToDir[keyCode];
}
}
function setMessage(s) {
document.getElementById('messageBox').innerHTML = s;
}
function startGame() {
currentDir = 'right';
snake = new Array(startSnakeLength);
snake[snake.length - 1] = { x: startX, y: startY };
for (var i = snake.length - 2; i >= 0; --i) {
snake[i] = { x: snake[i + 1].x, y: snake[i + 1].y + 1 };
}
placeFood();
paintCanvas();
setMessage('');
gameInterval = setInterval(updateGame, 200);
startGameButton.disabled = true;
}
function stopGame(message) {
setMessage(message + '<br> ended with ' + snake.length + ' segments');
clearInterval(gameInterval);
startGameButton.disabled = false;
}
var gameInterval,
startGameButton;
window.onload = function () {
canvas = document.getElementById('gameCanvas'),
ctx = canvas.getContext('2d');
canvas.width = numCols * cellSize;
canvas.height = numRows * cellSize;
Object.keys(dirToKeyCode).forEach(function (dir) {
dirToKeyCode[dir].forEach(function (keyCode) {
keyCodeToDir[keyCode] = dir;
})
});
document.addEventListener("keydown", keyDownHandler, false);
startGameButton = document.getElementById('startGameButton');
startGameButton.onclick = startGame;
}
body {
font-family: sans-serif;
}
#gameCanvas {
border: 1px solid #000;
float: left;
margin-right: 15px;
}
#startGameButton, #messageBox {
font-size: 16px;
margin-top: 15px;
}
#messageBox {
line-height: 24px;
}
<canvas id="gameCanvas"></canvas>
<button id="startGameButton">Start game</button>
<div id="messageBox"></div>

Changing color of Javascript class object dynamically?

I want to change the color of the balls to red when they collide. I tried using my function check() to change the color of the balls when they collide using balls[i].color but how do I know the positions of the balls to compare when they collide?
function randomXToY(minVal,maxVal,floatVal)
{
var randVal = minVal+(Math.random()*(maxVal-minVal));
return typeof floatVal=='undefined'?Math.round(randVal):randVal.toFixed(floatVal);
}
// The Ball class
Ball = (function() {
// constructor
function Ball(x,y,radius,color){
this.center = {x:x, y:y};
this.radius = radius;
this.color = color;
this.dx = 2;
this.dy = 2;
this.boundaryHeight = $('#ground').height();
this.boundaryWidth = $('#ground').width();
this.dom = $('<p class="circle"></p>').appendTo('#ground');
// the rectange div a circle
this.dom.width(radius*2);
this.dom.height(radius*2);
this.dom.css({'border-radius':radius,background:color});
this.placeAtCenter(x,y);
}
// Place the ball at center x, y
Ball.prototype.placeAtCenter = function(x,y){
this.dom.css({top: Math.round(y- this.radius), left: Math.round(x - this.radius)});
this.center.x = Math.round(x);
this.center.y = Math.round(y);
};
Ball.prototype.setColor = function(color) {
if(color) {
this.dom.css('background',color);
} else {
this.dom.css('background',this.color);
}
};
// move and bounce the ball
Ball.prototype.move = function(){
var diameter = this.radius * 2;
var radius = this.radius;
if (this.center.x - radius < 0 || this.center.x + radius > this.boundaryWidth ) {
this.dx = -this.dx;
}
if (this.center.y - radius < 0 || this.center.y + radius > this.boundaryHeight ) {
this.dy = -this.dy;
}
this.placeAtCenter(this.center.x + this.dx ,this.center.y +this.dy);
};
return Ball;
})();
var number_of_balls = 5;
var balls = [];
var x;
var y;
$('document').ready(function(){
for (i = 0; i < number_of_balls; i++) {
var boundaryHeight = $('#ground').height();
var boundaryWidth = $('#ground').width();
y = randomXToY(30,boundaryHeight - 50);
x = randomXToY(30,boundaryWidth - 50);
var radius = randomXToY(15,30);
balls.push(new Ball(x,y,radius, '#'+Math.floor(Math.random()*16777215).toString(16)));
}
loop();
check();
});
check = function(){
for (var i = 0; i < balls.length; i++){
for(var j=0;j<balls.length;j++){
if(x==y){
balls[i].color='#ff0000';
alert("y");
}
else{
}
}}
setTimeout(check,8);
};
loop = function(){
for (var i = 0; i < balls.length; i++){
balls[i].move();
}
setTimeout(loop, 8);
};
http://jsbin.com/imofat/743/edit
Calculate the Eucledian distance between the centers of each ball. Then, when this distance is smaller or equal to the sum of their radiuses, there's a collision:
check = function() {
for (var i = 0; i < balls.length; i++) {
for(var j = 0; j < balls.length; j++) {
if (i != j) { // ignore self-collision
if (Math.pow(balls[j].center.x - balls[i].center.x, 2) + Math.pow(balls[j].center.y - balls[i].center.y, 2) <= Math.pow(balls[i].radius + balls[j].radius, 2)) {
balls[j].setColor('red');
} else {
balls[j].setColor(balls[j].color);
}
}
}}
Here's a DEMO.
Edit:
for colliding with other balls, it will require more work.. check this post: Ball to Ball Collision
just setColor when the direction changes, assuming "collide" means "hit the wall":
// move and bounce the ball
Ball.prototype.move = function(){
var diameter = this.radius * 2;
var radius = this.radius;
if (this.center.x - radius < 0 || this.center.x + radius > this.boundaryWidth ) {
this.dx = -this.dx;
this.setColor( 'red' );
}
if (this.center.y - radius < 0 || this.center.y + radius > this.boundaryHeight ) {
this.dy = -this.dy;
this.setColor( 'red' );
}
this.placeAtCenter(this.center.x + this.dx ,this.center.y +this.dy);
};
I added an actual ball collision check inside the check method:
check = function(){
var dx, dy;
var x1, x2, y1, y2, r1, r2;
for (var i = 0; i < balls.length; i++){
x1 = balls[i].center.x;y1=balls[i].center.y;r1=balls[i].radius;
for(var j=0;j<balls.length;j++){
if (i===j) {continue;} // collision with self
x2 = balls[j].center.x;y2=balls[j].center.y;r2=balls[j].radius;
if( Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)) <=(r1+r2) ){ // check collision
balls[i].setColor('#ff0000');
}
else{
}
}
}
setTimeout(check,8);
};

Categories

Resources