Related
There are two squares, they are moved with the arrows and asdw, and I have been trying to get this collision detection down for ages, it's killing me. Anyways, I have tried so many methods, I got really really close with one method, and everything was fine with it, until I found out that when you jumped next to it, it didn't complete the full jump because one of the sides was able to go through the other square. After a long time, I found out that method wouldn't work because with GetContext.2d, you cannot reference the bottom left of a square. There also was the issue that one corner could touch two different sides, which would set off conflicting messages. My solution for that was to make a third condition to specify which side the corner was touching, weather the top/bottom or left/right. I made a model of how the code would be structured:
The code I have currently looks like this:
const context = document.getElementById("canvasmulti").getContext("2d");
canvasmulti.width = window.innerWidth;
canvasmulti.height = window.innerHeight;
//CHARACTER:
const square = {
height: 75,
jumping: true,
width: 75,
x: canvasmulti.width - 75,
xVelocity: 0,
y: canvasmulti.height / 2,
yVelocity: 0,
jumpHeight: 30
};
const square2 = {
height: 75,
jumping: true,
width: 75,
x: 0,
xVelocity: 0,
y: canvasmulti.height / 2,
yVelocity: 0,
jumpHeight: 30
};
//MOVEMENT:
const controller = {
left: false,
right: false,
up: false,
keyListener: function (event) {
let key_state = (event.type == "keydown") ? true : false;
switch (event.keyCode) {
case 37: // left arrow
controller.left = key_state;
break;
case 38: // up arrow
controller.up = key_state;
break;
case 39: // right arrow
controller.right = key_state;
break;
}
}
};
const controller2 = {
left: false,
right: false,
up: false,
keyListener: function (event) {
let key_state = (event.type == "keydown") ? true : false;
switch (event.keyCode) {
case 65: // left arrow
controller2.left = key_state;
break;
case 87: // up arrow
controller2.up = key_state;
break;
case 68: // right arrow
controller2.right = key_state;
break;
}
}
};
const loop = function () {
//controller one
if (controller.up && square.jumping == false) {
square.yVelocity -=square.jumpHeight;
square.jumping = true;}
if (controller.left) {
square.xVelocity -= 0.5;}
if (controller.right) {
square.xVelocity += 0.5;}
//controller two
if (controller2.up && square2.jumping == false) {
square2.yVelocity -= square2.jumpHeight;
square2.jumping = true;}
if (controller2.left) {
square2.xVelocity -= 0.5;}
if (controller2.right) {
square2.xVelocity += 0.5;}
//controller one
square.yVelocity += 1.5;// gravity
square.x += square.xVelocity;
square.y += square.yVelocity;
square.xVelocity *= 0.9;// friction
square.yVelocity *= 0.9;// friction
//controller two
square2.yVelocity += 1.5;// gravity
square2.x += square2.xVelocity;
square2.y += square2.yVelocity;
square2.xVelocity *= 0.9;// friction
square2.yVelocity *= 0.9;// friction
// if square1 is falling below floor line
if (square.y > canvasmulti.height - 75) {
square.jumping = false;
square.y = canvasmulti.height - 75;
square.yVelocity = 0;
}
// if square2 is falling below floor line
if (square2.y > canvasmulti.height - 75) {
square2.jumping = false;
square2.y = canvasmulti.height - 75;
square2.yVelocity = 0;
}
// if square1 is going off the left of the screen
if (square.x < 0) {
square.x = 0;
} else if (square.x > canvasmulti.width - 75) {// if square goes past right boundary
square.x = canvasmulti.width - 75;
}
// if square2 is going off the left of the screen
if (square2.x < 0) {square2.x = 0;}
else if (square2.x > canvasmulti.width - 75) {// if square goes past right boundary
square2.x = canvasmulti.width - 75;
}
// Creates the backdrop for each frame
context.fillStyle = "#394129";
context.fillRect(0, 0, canvasmulti.width, canvasmulti.height); // x, y, width, height
// Creates and fills square1 for each frame
context.fillStyle = "#8DAA9D"; // hex for cube color
context.beginPath();
context.rect(square.x, square.y, square.width, square.height);
context.fill();
// Creates and fills square2 for each frame
context.fillStyle = "#781818"; // hex for cube color
context.beginPath();
context.rect(square2.x, square2.y, square2.width, square2.height);
context.fill();
// Collision detection of squares
if (square.x <= square2.x + square2.width && square.x >= square2.x &&
square.y >= square2.y && square.y <= square2.y + square2.height &&
square2.y + square2.height >= square.y && square2.y + square2.height <= square.y + square.height)
{square.y = square2.y - square.height; //set it to a position where they don't overlap
square.yVelocity = 0;}; //One move down
if (square.x <= square2.x + square2.width && square.x >= square2.x &&
square.y >= square2.y && square.y <= square2.y + square2.height &&
square2.x + square2.width >= square.x && square2.x + square2.width <= square.x + square.width);
{square.x = square2.x + square2.width; // set it to a position where they don't overlap
square.xVelocity = 0;}; //One move right
if (square2.x >= square.x && square2.x <= square.x + square.width &&
square2.y >= square.y && square2.y <= square.y + square.heights &&
square.y + square.height >= square2.y && square.y + square.height <= square2.y + square2.height)
{square.y = square2.y - square.height; // set it to a position where they don't overlap
square.yVelocity = 0;}; //One move up
if (square2.x >= square.x && square2.x <= square.x + square.width &&
square2.y >= square.y && square2.y <= square.y + square.heights &&
square.x + square.width >= square2.y && square.x + square.width <= square2.y + square2.height)
{square.x = square2.x - square.width; // set it to a position where they don't overlap
square.xVelocity = 0;}; //One move left
// call update when the browser is ready to draw again
window.requestAnimationFrame(loop);
};
//square1
window.addEventListener("keydown", controller.keyListener)
window.addEventListener("keyup", controller.keyListener);
//square2
window.addEventListener("keydown", controller2.keyListener)
window.addEventListener("keyup", controller2.keyListener);
window.requestAnimationFrame(loop);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height">
<title>ToneMain</title>
<style>
body {
height:100vh;
width:100vh;
margin: 0;
}
</style>
</head>
<body>
<canvas id="canvasmulti"></canvas>
<script src="ToneMainMulti.js"></script>
</body>
</html>
For the love of my sanity this was the last time in a month I tried to fix this. This was my last blow. I would really apprestiate any effort or advice to solve my problem, this is tough to wrap your head around and may take a long time, thxxx <3
I feel your pain. CD is not as easy as one would hope. I was unable to get your code working so I rewrote it using ES6 classes because I like them. I also don't put everything into a loop function but rather place controlling parameters into separate functions and call those functions in the animate loop. Hopefully it isn't confusing from how you write.
The CD portion calculates the distance between the different sides of the blocks and determines which sides from each block are the closest and will collide. By doing this only one function will get called at a time. This prevents any of the unwanted behavior you get because of the blocks having a slight overlap and causing other functions to trigger also. i.e. colliding on the X but having the Y trigger too. I've also set it to where if the blocks are stacked only the top one can jump.
You can tailor this to whatever suits your needs.
const canvas = document.getElementById('canvasmulti');
const context = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 500;
let gravity = 1.5;
let friction = 0.9;
//CHARACTER:
class Player {
constructor(x, y, vx, vy, c, j) {
//each player must have separate values for control purposes i.e. velocity/jump/attack etc.
this.x = x;
this.y = y;
this.w = 50;
this.h = 50;
this.vx = vx;
this.vy = vy;
this.color = c;
this.jumping = j;
}
draw() {
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
}
canvasCollision() {
if (this.x <= 0) this.x = 0;
if (this.y <= 0) this.y = 0;
if (this.x + this.w >= canvas.width) this.x = canvas.width - this.w;
if (this.y + this.h >= canvas.height) {this.y = canvas.height - this.h; this.vy = 0; this.jumping = false};
}
update() {
this.draw();
this.vy += gravity;
this.x += this.vx;
this.y += this.vy;
this.vx *= friction;
this.vy *= friction;
this.canvasCollision() //must be after other updates
}
}
let player1 = new Player(0, 0, 0, 0, 'red', false); //must have separate variables like jump/attack etc
let player2 = new Player(canvasmulti.width - 50, 0, 0, 0, 'blue', false);
function controlPlayer1(obj) {
//this order matters. If update is before jump then obj won't jump when on top of other block.
if (controller1.up1 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
if (controller1.left1) { obj.vx -= 0.5 };
if (controller1.right1) { obj.vx += 0.5 };
obj.update();
}
function controlPlayer2(obj) {
if (controller2.up2 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
if (controller2.right2) { obj.vx += 0.5 };
if (controller2.left2) { obj.vx -= 0.5 };
obj.update();
}
//MOVEMENT:
class Controller {
constructor() {
this.left1 = false;
this.up1 = false;
this.right1 = false;
this.left2 = false;
this.up2 = false;
this.right2 = false;
let controller1 = (e) => {
if (e.code === 'ArrowRight') { this.right1 = e.type === 'keydown' }
if (e.code === 'ArrowLeft') { this.left1 = e.type === 'keydown' }
if (e.code === 'ArrowUp') { this.up1 = e.type === 'keydown' }
}
let controller2 = (e) => {
if (e.code === 'KeyD') { this.right2 = e.type === 'keydown' }
if (e.code === 'KeyA') { this.left2 = e.type === 'keydown' }
if (e.code === 'KeyW') { this.up2 = e.type === 'keydown' }
}
window.addEventListener('keydown', controller1);
window.addEventListener('keyup', controller1);
window.addEventListener('keydown', controller2);
window.addEventListener('keyup', controller2);
}
}
let controller1 = new Controller();
let controller2 = new Controller();
function collisionDetection(obj, obj2) {
//center point of each side of obj1
let objLeft = {x: obj.x, y: obj.y + obj.h/2};
let objTop = {x: obj.x + obj.w/2, y: obj.y};
let objRight = {x: obj.x + obj.w, y: obj.y + obj.h/2};
let objBottom = {x: obj.x + obj.w/2, y: obj.y + obj.h};
//center point of each side a obj2
let obj2Left = {x: obj2.x, y: obj2.y + obj2.h/2};
let obj2Top = {x: obj2.x + obj2.w/2, y: obj2.y};
let obj2Right = {x: obj2.x + obj2.w, y: obj2.y + obj2.h/2};
let obj2Bottom = {x: obj2.x + obj2.w/2, y: obj2.y + obj2.h};
//distance between obj1 and obj2 opposing sides
let rightDistX = objRight.x - obj2Left.x;
let rightDistY = objRight.y - obj2Left.y;
let leftDistX = objLeft.x - obj2Right.x;
let leftDistY = objLeft.y - obj2Right.y;
let topDistX = objTop.x - obj2Bottom.x;
let topDistY = objTop.y - obj2Bottom.y;
let bottomDistX = objBottom.x - obj2Top.x;
let bottomDistY = objBottom.y - obj2Top.y;
//pythagorean theorem for distance. dRight is from the right side of obj1 to the left of obj2. the rest follow suit.
let dRight = Math.sqrt(rightDistX*rightDistX + rightDistY*rightDistY);
let dLeft = Math.sqrt(leftDistX*leftDistX + leftDistY*leftDistY);
let dTop = Math.sqrt(topDistX*topDistX + topDistY*topDistY);
let dBottom = Math.sqrt(bottomDistX*bottomDistX + bottomDistY*bottomDistY);
//Math.min return the smallest value thus variable minimum will be which ever sides are closest together
let minimum = Math.min(dRight, dLeft, dBottom, dTop);
let val = 0;
//compare minimum to d*** and set val based on which ever side is closest
if (dTop == minimum) {
val = 1;
//the context stuff can be deleted. It's just here for visual. The if statements can be one line each.
context.lineWidth = 2;
context.strokeStyle = 'blue';
context.beginPath();
context.moveTo(objTop.x, objTop.y);
context.lineTo(obj2Bottom.x, obj2Bottom.y);
context.stroke();
}
else if (dRight == minimum) {
val = 2;
context.strokeStyle = 'orange';
context.beginPath();
context.moveTo(objRight.x, objRight.y);
context.lineTo(obj2Left.x, obj2Left.y);
context.stroke();
}
else if (dBottom == minimum) {
val = 3;
context.strokeStyle = 'green';
context.beginPath();
context.moveTo(objBottom.x, objBottom.y);
context.lineTo(obj2Top.x, obj2Top.y);
context.stroke();
}
else if (dLeft == minimum) {
val = 4;
context.strokeStyle = 'pink';
context.beginPath();
context.moveTo(objLeft.x, objLeft.y);
context.lineTo(obj2Right.x, obj2Right.y);
context.stroke();
}
//pass the objects and val to collisionActions
collisionActions(obj, obj2, val)
}
function collisionActions(obj, obj2, val) {
//player1 top to player2 bottom
if (obj.y <= obj2.y + obj2.h && obj2.y + obj2.h >= obj.y && val == 1) {
obj2.y = obj.y - obj2.h;
obj.y = obj2.y + obj2.h;
obj.vy = 0;
obj2.vy = 0;
obj2.jumping = false;
obj.jumping = true;
}
//player1 right to player2 left
if (obj.x + obj.w >= obj2.x && obj2.x <= obj.x + obj.w && val == 2) {
obj2.x = obj.x + obj.w;
obj.x = obj2.x - obj.w - 1;
obj.vx = 0;
obj2.vx = 0;
}
//player1 bottom to player2 top
if (obj.y + obj.h >= obj2.y && obj2.y <= obj.y + obj.h && val == 3) {
obj.y = obj2.y - obj.h;
obj2.y = obj.y + obj.h;
obj.vy = 0;
obj2.vy = 0;
obj.jumping = false;
obj2.jumping = true;
}
//player1 left to player2 right
if (obj.x <= obj2.x + obj2.w && obj2.x + obj2.w >= obj.x && val == 4) {
obj2.x = obj.x - obj2.w;
obj.x = obj2.x + obj2.w + 1;
obj.vx = 0;
obj2.vx = 0;
}
}
function loop() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'grey';
context.fillRect(0, 0, canvas.width, canvas.height);
controlPlayer1(player1);
controlPlayer2(player2);
collisionDetection(player1, player2)
requestAnimationFrame(loop)
}
loop();
It may seem like a lot but it's really not and it works very well. I was having the same issues a while back and came up with this to use on a collision map because my character would randomly shoot of in the wrong direction or jumping next to a block wouldn't work. I have modified that code to be used here with just 2 moving blocks. Let me know if this works for you.
I am trying to create a simple snake game. For now I am trying to catch the food but the position of the snake is never the same as the position of food.
(function() {
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
x = 0,
y = 0,
speed = 2;
x_move = speed,
y_move = 0,
food_position_x = Math.floor(Math.random() * canvas.width);
food_position_y = Math.floor(Math.random() * canvas.height);
function eat() {
console.log('food_x:' + food_position_x + ' x:' + x + ' / food_y:' + food_position_y + ' y:' + y);
if (y == food_position_y && x == food_position_x) {
throw new Error("MATCH!"); // This is not an error. Just trying to stop the script
}
}
// Drawing
function draw() {
eat();
requestAnimationFrame(function() {
draw();
});
// Draw the snake
ctx.beginPath();
ctx.rect(Math.floor(x/10)*10, Math.floor(y/10)*10, 10, 10);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.closePath();
// Draw the food
ctx.beginPath();
ctx.rect(Math.floor(food_position_x/10)*10, Math.floor(food_position_y/10)*10, 10, 10);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
// Increase the value of x and y in order to animate
x = x + x_move;
y = y + y_move;
}
draw();
// Key Pressing
document.addEventListener('keydown', function(event) {
switch(event.keyCode) {
case 40: // Moving down
if (x_move != 0 && y_move != -1) {
x_move = 0;
y_move = speed;
}
break;
case 39: // Moving right
if (x_move != -1 && y_move != 0) {
x_move = speed;
y_move = 0;
}
break;
case 38: // Moving top
if (x_move != 0 && y_move != 1) {
x_move = 0;
y_move = -speed;
}
break;
case 37: // Moving left
if (x_move != 1 && y_move != 0) {
x_move = -speed;
y_move = 0;
}
break;
}
});
})();
canvas { background-color: #000022 }
<canvas id="canvas" width="400" height="400"></canvas>
console.log() will return all positions.
jsfiddle
Am I calculating something wrong? Maybe I have to change the variables food_position_x and food_position_y. If not, I have no idea why the positions are never the same.
Your game area is supposed to be a grid of cells with widths and heights of 10. That's why you do this: Math.floor(x/10)*10 when drawing the snake.
To make the eat function work you just need to draw the food in the same way:
food_position_x = Math.floor(Math.random() * canvas.width / 10) * 10;
food_position_y = Math.floor(Math.random() * canvas.height / 10) * 10;
and add the same thing to the if condition of the eat function:
if (Math.floor(y/10)*10 == food_position_y && Math.floor(x/10)* 10 == food_position_x)
(function() {
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
x = 0,
y = 0,
speed = 2;
x_move = speed,
y_move = 0,
food_position_x = Math.floor(Math.random() * canvas.width / 10) * 10;
food_position_y = Math.floor(Math.random() * canvas.height / 10) * 10;
function eat() {
//console.log('food_x:' + food_position_x + ' x:' + x + ' / food_y:' + food_position_y + ' y:' + y);
if (Math.floor(y/10)*10 == food_position_y && Math.floor(x/10)* 10 == food_position_x) {
throw new Error("MATCH!"); // This is not an error. Just trying to stop the script
}
}
// Drawing
function draw() {
eat();
requestAnimationFrame(function() {
draw();
});
// Increase the value of x and y in order to animate
x = x + x_move;
y = y + y_move;
if (x > canvas.width) x = 0;
if (y > canvas.height) y = 0;
if (y < 0) y = canvas.height;
if (x < 0) x = canvas.width;
// Draw the snake
ctx.beginPath();
ctx.rect(Math.floor(x/10)*10, Math.floor(y/10)*10, 10, 10);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.closePath();
// Draw the food
ctx.beginPath();
ctx.rect(Math.floor(food_position_x/10)*10, Math.floor(food_position_y/10)*10, 10, 10);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
draw();
// Key Pressing
document.addEventListener('keydown', function(event) {
switch(event.keyCode) {
case 40: // Moving down
if (x_move != 0 && y_move != -1) {
x_move = 0;
y_move = speed;
}
break;
case 39: // Moving right
if (x_move != -1 && y_move != 0) {
x_move = speed;
y_move = 0;
}
break;
case 38: // Moving top
if (x_move != 0 && y_move != 1) {
x_move = 0;
y_move = -speed;
}
break;
case 37: // Moving left
if (x_move != 1 && y_move != 0) {
x_move = -speed;
y_move = 0;
}
break;
}
});
})();
canvas { background-color: #000022 }
<canvas id="canvas" width="400" height="400"></canvas>
Your game looks like it's on big chunky grid, but it really isn't, that's only in the graphics. The state variables, which are what matter, are little fiddly small numbers.
Instead of drawing the snake like this:
ctx.rect(Math.floor(x/10)*10, Math.floor(y/10)*10, 10, 10);
Try drawing it like this:
ctx.rect( x, y, 1, 1 );
And you'll see the real state of the game. This is what you're really playing: https://jsfiddle.net/xeyrmsLc/11/ - and that's why it's hard to get the food.
The main problem is that there are so many possibilities for x and food_position_x, and precise control is difficult, that it's very difficult to land exactly on an X that matches in combination with a Y that matches. You have to move back and forth a lot around the same area to get an exact match for X or an exact match for Y (from which you can then proceed to go in some orthogonal direction to land on the food).
But your code does work at a base level. You might consider reducing the speed and making the visual square location exactly match the internal numbers (less visual granularity which doesn't match the underlying state).
I am trying to create a simple snake game.
(function() {
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
x = 0,
y = 0,
speed = 2;
x_move = speed,
y_move = 0,
food_position_x = Math.floor(Math.random() * canvas.width / 10) * 10,
food_position_y = Math.floor(Math.random() * canvas.height / 10) * 10,
size_x = 10;
function eat() {
console.log('food_x:' + food_position_x + ' x:' + x + ' / food_y:' + food_position_y + ' y:' + y);
if (Math.floor(y / 10) * 10 == food_position_y && Math.floor(x / 10) *10 == food_position_x) {
size_x += 2;
//throw new Error("MATCH!"); // This is not an error. Just trying to stop the script
}
}
// Drawing
function draw() {
eat();
requestAnimationFrame(function() {
draw();
});
// Draw the snake
ctx.beginPath();
ctx.rect(Math.floor(x/10)*10, Math.floor(y/10)*10, size_x, 10);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.closePath();
// Draw the food
ctx.beginPath();
ctx.rect(Math.floor(food_position_x/10)*10, Math.floor(food_position_y/10)*10, 10, 10);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
// Increase the value of x and y in order to animate
x = x + x_move;
y = y + y_move;
}
draw();
// Key Pressing
document.addEventListener('keydown', function(event) {
switch(event.keyCode) {
case 40: // Moving down
if (x_move != 0 && y_move != -1) {
x_move = 0;
y_move = speed;
}
break;
case 39: // Moving right
if (x_move != -1 && y_move != 0) {
x_move = speed;
y_move = 0;
}
break;
case 38: // Moving top
if (x_move != 0 && y_move != 1) {
x_move = 0;
y_move = -speed;
}
break;
case 37: // Moving left
if (x_move != 1 && y_move != 0) {
x_move = -speed;
y_move = 0;
}
break;
}
});
})();
canvas { background-color: #000022 }
<canvas id="canvas" width="400" height="400"></canvas>
jsfiddle
The problem
Every time when I catch the food, the snake becomes longer but when you press the down or up key, it moves horizontally.
Maybe a solution
This is what I believe the solution could be:
The snake should be an array! Every time when the key is pressed, define the position of HEAD of snake and move the snake step by step, because it is an array. So the body follows the head. But in this case, I have no idea how to make an array from it.
Maybe there are other solutions. Any helps would be appreciated!
You need to maintain an array of the points currently occupied by the snake body, and add new point (unshift) to the array as the snake approaches and remove point from the back of the array (pop). The following code is a starter, you need to make it your own :).
(function () {
const COLORS={ SNAKE:'#ff7bf5', FOOD:'blue' };
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
var snake=[], score=0;
var x, y, food_position_x, food_position_y, x_move, y_move;
var frameCount=0, framesRequiredToMove=10;
//the less the framesToMove the faster the sname moves
function draw(){
if(++frameCount==framesRequiredToMove){
frameCount=0;
move();
}
requestAnimationFrame(draw);
}
function init(){
snake = [{x:3,y:0},{x:2,y:0},{x:1,y:0},{x:0,y:0}];
snake.forEach((p)=>{plot(p.x,p.y,COLORS.SNAKE)})
x=snake[0].x;y=snake[0].y;
score=0;x_move=1;y_move=0;
scoreboard.innerText=score;
newfood();
setTimeout(draw,1000);
}
function plot(x,y,color){
ctx.beginPath();
ctx.rect(x * 10, y * 10, 10, 10);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}
function move(){
snakepx.innerText = x;
snakepy.innerText = y;
x = x + x_move;
y = y + y_move;
// Advance The Snake
plot(x,y,COLORS.SNAKE);
snake.unshift({x:x,y:y});
// Check food encounter
if(x==food_position_x && y==food_position_y){
scoreboard.innerText=++score;
newfood();
}
else{
var last=snake.pop();
ctx.clearRect(last.x * 10, last.y * 10, 10, 10);
}
}
function newfood(){
food_position_x=Math.floor(Math.random() * canvas.width / 10);
food_position_y=Math.floor(Math.random() * canvas.height / 10);
plot(food_position_x,food_position_y,COLORS.FOOD);
foodpx.innerText = food_position_x;
foodpy.innerText = food_position_y;
}
init();
// Key Pressing
document.addEventListener('keydown', function (event) {
event.preventDefault();
switch (event.keyCode) {
case 40: // Moving down
if (x_move != 0 && y_move != -1) {
x_move = 0;
y_move = 1;
}
break;
case 39: // Moving right
if (x_move != -1 && y_move != 0) {
x_move = 1;
y_move = 0;
}
break;
case 38: // Moving top
if (x_move != 0 && y_move != 1) {
x_move = 0;
y_move = -1;
}
break;
case 37: // Moving left
if (x_move != 1 && y_move != 0) {
x_move = -1;
y_move = 0;
}
break;
}
});
})();
canvas {
background-color: #000022;
float: left;
}
<canvas id="canvas" width="400" height="180"></canvas>
<div style="margin-left: 410px">
Snake: (<span id="snakepx"></span>, <span id="snakepy"></span>)<br>
Food: (<span id="foodpx"></span>, <span id="foodpy"></span>)<br>
Score: <span id="scoreboard"></span>
</div>
Well as I had some free time to spare I created my own JS snake to demonstrate you how it can be done. Most important parts are in this.snakeBody where array of body is stored and this.moveForward() where you can see how body is updated.
https://jsfiddle.net/nooorz24/p8xtdv3h/13/
moveForward: function() {
var next = this.getNextfieldValue();
if (next == "frame" || next == "body") {
console.log("You lose!")
this.isAlive = false;
} else {
var newHead = this.getNextfieldCoords();
this.draw.snake(newHead.x, newHead.y);
this.body.unshift(newHead);
if (next == "food") {
this.generateFood();
this.snakeSize++;
} else {
var last = this.body.pop();
this.draw.empty(last.x, last.y);
}
}
},
I tried to make it as readable as I could, but note that this is unfinished example and would need need loads of improvements to be a playable game
I think that you have hit on the answer with an array. In the past, I have found that Snake works best with an array, tracking each block of the snake and checking for any two blocks being in the same position. For movement, however, you must directly control the head, and have the body follow based on the position of the array space one ahead of it. The snake head would be the first item in the array. I am not sure what you mean about not understanding the implementation of the array, but the coordinates for each block would be an item in the array.
Have a length variable and make that the x or y size based on direction, like this:
(function() {
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
x = 0,
y = 0,
speed = 2,
x_move = speed,
y_move = 0,
food_position_x = Math.floor(Math.random() * canvas.width / 10) * 10,
food_position_y = Math.floor(Math.random() * canvas.height / 10) * 10,
size_x = 10,
size_y = 10,
snake_length = 10;
function eat() {
console.log('food_x:' + food_position_x + ' x:' + x + ' / food_y:' + food_position_y + ' y:' + y);
if (Math.floor(y / 10) * 10 == food_position_y && Math.floor(x / 10) *10 == food_position_x) {
snake_length += 2;
//throw new Error("MATCH!"); // This is not an error. Just trying to stop the script
}
}
// Drawing
function draw() {
eat();
requestAnimationFrame(function() {
draw();
});
// Draw the snake
ctx.beginPath();
ctx.rect(Math.floor(x/10)*10, Math.floor(y/10)*10, size_x, size_y);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.closePath();
// Draw the food
ctx.beginPath();
ctx.rect(Math.floor(food_position_x/10)*10, Math.floor(food_position_y/10)*10, 10, 10);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
// Increase the value of x and y in order to animate
x = x + x_move;
y = y + y_move;
}
draw();
// Key Pressing
document.addEventListener('keydown', function(event) {
switch(event.keyCode) {
case 40: // Moving down
if (x_move != 0 && y_move != -1) {
x_move = 0;
y_move = speed;
size_x = 10;
size_y = snake_length;
}
break;
case 39: // Moving right
if (x_move != -1 && y_move != 0) {
x_move = speed;
y_move = 0;
size_x = snake_length;
size_y = 10;
}
break;
case 38: // Moving top
if (x_move != 0 && y_move != 1) {
x_move = 0;
y_move = -speed;
size_x = 10;
size_y = snake_length;
}
break;
case 37: // Moving left
if (x_move != 1 && y_move != 0) {
x_move = -speed;
y_move = 0;
size_x = snake_length;
size_y = 10;
}
break;
}
});
})();
canvas { background-color: #000022 }
<canvas id="canvas" width="400" height="400"></canvas>
I make the game using C++ soi can advice make a new object for every snake tile and on move set it to parent position
function Tile(x, y)
{
this.x = x;
this.y = y;
}
To debug your down or up problem, I would add some console.log('message') calls in your code for debugging.
Specifically, add some console messages in the switch statement so that you know the correct event and branch is firing. For Example:
switch(event.keyCode) {
case 40:
console.log('down');
...
case 39:
console.log('right');
...
}
Then add more and more debugging log messages until you find your problem.
You can view the console messages in the F12 developer tools in your browser of choice while running your game.
Also make sure you are not calling ctx.translate(...) on the canvas context to flip the canvas, this would change the direction of the x and/or y axis.
I'm trying to update a barrage of bullets each frame in JavaScript, but it seems it's not working properly. Here's the code I put on JSFiddle.
https://jsfiddle.net/Reverseblade/co2mpLnv/5/
var ctx;
var hero;
var enemy;
var beams = [];
var beam_hitting = false;
var continuous_hit = false;
var count = 0;
var canvas_w = 500, canvas_y= 700;
var keycode = NaN;
var laser_degree = 200;
function init(){
createCanvas();
createMainHero();
createEnemy();
draw();
mainRoutine();
}
function mainRoutine(){
count++;
ctx.clearRect(0, 0, canvas_w, canvas_y);
// laserTest();
hero.move(keycode);
enemyUpdate();
// hero.setBullets();
// hero.moveBullets();
draw();
window.setTimeout(mainRoutine, 7);
}
function createCanvas(){
var canvas = document.createElement("canvas");
canvas.id = "canvas";
canvas.width = canvas_w;
canvas.height = canvas_y;
document.body.appendChild(canvas);
}
function createMainHero(){
hero = new Hero();
}
function createEnemy(){
enemy = new Enemy;
}
function Hero(){
this.w = this.h = 25,
this.x = canvas_w/2 - this.w/2,
this.y = canvas_y - this.h,
this.dx = this.dy = 2.5;
this.bullets = [];
this.move = function(key){
switch(key){
case 37: if (hero.x > 0) {hero.x -= this.dx;} break;
case 38: if (hero.y > 0) {hero.y -= this.dy;} break;
case 39: if (hero.x < canvas_w - hero.w) {hero.x += this.dx;} break;
case 40: if (hero.y < canvas_y - hero.h) {hero.y += this.dy;} break;
}
};
this.setBullets = function(){
if (count % 20 === 0) {
var w = h = 8;
var dx = dy = 5;
var x = hero.x + hero.w/2 - w/2;
var y = hero.y;
hero.bullets.push({x: x, y: y, w: w, h: h, dx: dx, dy: dy, moving:true});
}
};
this.moveBullets = function(){
for (var i = 0; i < this.bullets.length; i++) {
if (this.bullets[i].y < 0) {
this.bullets[i].moving = false;
continue;
}
if (this.bullets[i].moving === false) {
continue;
}
this.bullets[i].y -= this.bullets[i].dy;
if (this.bullets[i].y < -100) {this.bullets.splice(i, 1)}
}
}
}
function Enemy(){
this.w = this.h = 25;
this.x = canvas_w/2 - this.w/2;
this.y = 50;
this.bullets = [];
this.moving = false;
this.move_to = 0;
this.bullets_count = 0;
this.bullets_angle = 0;
this.current_skill = 1;
this.barrage_count = 0;
this.skill = function(){
enemySkill();
};
}
function enemyUpdate(){
function move(){
function changeDirection(){
var options = ["left", "right", "up", "down"];
var id;
if (enemy.x <= 50) {id = options.indexOf("left"); options.splice(id, 1);}
if (enemy.x >= 450) {id = options.indexOf("right");options.splice(id, 1);}
if (enemy.y <= 50) {id = options.indexOf("up");options.splice(id, 1);}
if (enemy.y >= 200) {id = options.indexOf("down");options.splice(id, 1);}
var rand = Math.floor(Math.random() * options.length);
enemy.moving = options[rand];
switch(enemy.moving){
case "left": enemy.move_to = enemy.x - 150 ; break;
case "right": enemy.move_to = enemy.x + 150 ; break;
case "up": enemy.move_to = enemy.y - 150 ; break;
case "down": enemy.move_to = enemy.y + 150 ; break;
}
} /* end changeDirection() */
if (count % 800 === 0) {changeDirection(); console.log("changing");}
switch(enemy.moving){
case "left": if (enemy.x > 50 && enemy.x > enemy.move_to) {enemy.x -= 0.5;} break;
case "right": if (enemy.x < 450 && enemy.x < enemy.move_to) {enemy.x += 0.5;} break;
case "up": if (enemy.y > 50 && enemy.y > enemy.move_to) {enemy.y -= 0.5; } break;
case "down": if (enemy.y < 200 && enemy.y < enemy.move_to) {enemy.y += 0.5; } break;
}
} /* end move()*/
move();
enemy.skill();
} /* end enemyUpdate() */
function enemySkill(){
// console.log("enemy skill");
function setBullets(){
var prev_status = enemy.bullets_count === 0 ? 500 : enemy.bullets[enemy.bullets.length - 1]["radius"];
if (prev_status >25) {
// console.log("bullets set");
var center_x = enemy.x + enemy.w/2;
var center_y = enemy.y + enemy.h/2;
var radius = 20;
var ceil = enemy.bullets.length === 0 ? 0 : enemy.bullets.length -1;
for (var angle = enemy.bullets_angle, i= ceil; angle < enemy.bullets_angle + 360; angle += 40, i++ ) {
// console.log("i: " + i);
var radian = angle * Math.PI / 180;
var set_x = center_x + radius * Math.cos(radian);
var set_y = center_y + radius * Math.sin(radian);
// console.log("angle: " + /angle + "set_x: " + set_x + "set_y: " + set_y);
enemy.bullets.push({"x": set_x, "y": set_y, "moving": true, "radius": radius, "center_x": center_x, "center_y": center_y, "w": 25, "h": 25, "radian": radian});
if (enemy.bullets_count === 0) {enemy.bullets_count++;}
// console.log(enemy.bullets[0][i]["x"]);
}
enemy.bullets_angle += 10;
enemy.barrage_count ++;
if (enemy.barrage_count % 100 === 0) {
enemy.current_skill = 0;
}
}
} /* end setBullets */
function moveBullets(){
if (count % 4 ===0) {
for (var i = 0; i < enemy.bullets.length; i++) {
if (enemy.bullets[i]["moving"] === true) {
var radian = enemy.bullets[i]["radian"];
var center_x = enemy.bullets[i]["center_x"];
var center_y = enemy.bullets[i]["center_y"];
enemy.bullets[i]["radius"] += 5;
var radius = enemy.bullets[i]["radius"];
var set_x = center_x + radius * Math.cos(radian);
var set_y = center_y + radius * Math.sin(radian);
// console.log(set_y);
enemy.bullets[i]["x"] = set_x;
enemy.bullets[i]["y"] = set_y;
if (enemy.bullets[i]["x"] < -100 || enemy.bullets[i]["x"] > canvas_w + 100 || enemy.bullets[i]["y"] < -100 || enemy.bullets[i]["y"] > canvas_y + 100 ) {
// enemy.bullets[i]["moving"] = false;
enemy.bullets.splice(i, 1);
}
}
}
}
}
if (enemy.current_skill === 1) {
setBullets();
}
moveBullets();
}
function draw(){
var canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
//hero
//ctx.fillStyle = "blue";
//ctx.fillRect(hero.x, hero.y ,hero.w, hero.h);
//enemy
//ctx.fillStyle = "red";
//ctx.fillRect(enemy.x, enemy.y ,enemy.w, enemy.h);
//heroの弾
ctx.fillStyle = "blue";
for (var i = 0; i < hero.bullets.length; i++) {
if (hero.bullets[i].moving === false) {
continue;
}
ctx.fillRect(hero.bullets[i].x, hero.bullets[i].y ,hero.bullets[i].w, hero.bullets[i].h);
}
//敵の弾
ctx.fillStyle = "red";
for (var i = 0; i < enemy.bullets.length; i++) {
ctx.fillStyle = "green";
if (enemy.bullets[i]["moving"] === false) {
continue;
}
ctx.beginPath();
ctx.arc(enemy.bullets[i]["x"], enemy.bullets[i]["y"], 15, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.fill();
}
}
window.addEventListener("keydown", function(e){
switch(e.keyCode){
case 37: keycode = 37; break;
case 38: keycode = 38; break;
case 39: keycode = 39; break;
case 40: keycode = 40; break;
}
}, false);
window.addEventListener("keyup", function(e){
switch(e.keyCode){
case 37: keycode = NaN; break;
case 38: keycode = NaN; break;
case 39: keycode = NaN; break;
case 40: keycode = NaN; break;
}
}, false);
init();
*{
margin:0;
padding:0;
}
#canvas{
background:url("../img/seamles_space.jpg");
animation: mainBack 5s linear infinite;
animation-play-state:paused;
background: black;
display: block;
position:relative;
margin:50px auto;
}
<body>
<script src="js/main.js"></script>
</body>
var ctx;
var hero;
var enemy;
var beams = [];
var beam_hitting = false;
var continuous_hit = false;
var count = 0;
var canvas_w = 500, canvas_y= 700;
var keycode = NaN;
var laser_degree = 200;
function init(){
createCanvas();
createMainHero();
createEnemy();
draw();
mainRoutine();
}
function mainRoutine(){
count++;
ctx.clearRect(0, 0, canvas_w, canvas_y);
// laserTest();
hero.move(keycode);
enemyUpdate();
// hero.setBullets();
// hero.moveBullets();
draw();
window.setTimeout(mainRoutine, 7);
}
function createCanvas(){
var canvas = document.createElement("canvas");
canvas.id = "canvas";
canvas.width = canvas_w;
canvas.height = canvas_y;
document.body.appendChild(canvas);
}
function createMainHero(){
hero = new Hero();
}
function createEnemy(){
enemy = new Enemy;
}
function Hero(){
this.w = this.h = 25,
this.x = canvas_w/2 - this.w/2,
this.y = canvas_y - this.h,
this.dx = this.dy = 2.5;
this.bullets = [];
this.move = function(key){
switch(key){
case 37: if (hero.x > 0) {hero.x -= this.dx;} break;
case 38: if (hero.y > 0) {hero.y -= this.dy;} break;
case 39: if (hero.x < canvas_w - hero.w) {hero.x += this.dx;} break;
case 40: if (hero.y < canvas_y - hero.h) {hero.y += this.dy;} break;
}
};
this.setBullets = function(){
if (count % 20 === 0) {
var w = h = 8;
var dx = dy = 5;
var x = hero.x + hero.w/2 - w/2;
var y = hero.y;
hero.bullets.push({x: x, y: y, w: w, h: h, dx: dx, dy: dy, moving:true});
}
};
this.moveBullets = function(){
for (var i = 0; i < this.bullets.length; i++) {
if (this.bullets[i].y < 0) {
this.bullets[i].moving = false;
continue;
}
if (this.bullets[i].moving === false) {
continue;
}
this.bullets[i].y -= this.bullets[i].dy;
if (this.bullets[i].y < -100) {this.bullets.splice(i, 1)}
}
}
}
function Enemy(){
this.w = this.h = 25;
this.x = canvas_w/2 - this.w/2;
this.y = 50;
this.bullets = [];
this.moving = false;
this.move_to = 0;
this.bullets_count = 0;
this.bullets_angle = 0;
this.current_skill = 1;
this.barrage_count = 0;
this.skill = function(){
enemySkill();
};
}
function enemyUpdate(){
function move(){
function changeDirection(){
var options = ["left", "right", "up", "down"];
var id;
if (enemy.x <= 50) {id = options.indexOf("left"); options.splice(id, 1);}
if (enemy.x >= 450) {id = options.indexOf("right");options.splice(id, 1);}
if (enemy.y <= 50) {id = options.indexOf("up");options.splice(id, 1);}
if (enemy.y >= 200) {id = options.indexOf("down");options.splice(id, 1);}
var rand = Math.floor(Math.random() * options.length);
enemy.moving = options[rand];
switch(enemy.moving){
case "left": enemy.move_to = enemy.x - 150 ; break;
case "right": enemy.move_to = enemy.x + 150 ; break;
case "up": enemy.move_to = enemy.y - 150 ; break;
case "down": enemy.move_to = enemy.y + 150 ; break;
}
} /* end changeDirection() */
if (count % 800 === 0) {changeDirection(); console.log("changing");}
switch(enemy.moving){
case "left": if (enemy.x > 50 && enemy.x > enemy.move_to) {enemy.x -= 0.5;} break;
case "right": if (enemy.x < 450 && enemy.x < enemy.move_to) {enemy.x += 0.5;} break;
case "up": if (enemy.y > 50 && enemy.y > enemy.move_to) {enemy.y -= 0.5; } break;
case "down": if (enemy.y < 200 && enemy.y < enemy.move_to) {enemy.y += 0.5; } break;
}
} /* end move()*/
move();
enemy.skill();
} /* end enemyUpdate() */
function enemySkill(){
// console.log("enemy skill");
function setBullets(){
var prev_status = enemy.bullets_count === 0 ? 500 : enemy.bullets[enemy.bullets.length - 1]["radius"];
if (prev_status >25) {
// console.log("bullets set");
var center_x = enemy.x + enemy.w/2;
var center_y = enemy.y + enemy.h/2;
var radius = 20;
var ceil = enemy.bullets.length === 0 ? 0 : enemy.bullets.length -1;
for (var angle = enemy.bullets_angle, i= ceil; angle < enemy.bullets_angle + 360; angle += 40, i++ ) {
// console.log("i: " + i);
var radian = angle * Math.PI / 180;
var set_x = center_x + radius * Math.cos(radian);
var set_y = center_y + radius * Math.sin(radian);
// console.log("angle: " + /angle + "set_x: " + set_x + "set_y: " + set_y);
enemy.bullets.push({"x": set_x, "y": set_y, "moving": true, "radius": radius, "center_x": center_x, "center_y": center_y, "w": 25, "h": 25, "radian": radian});
if (enemy.bullets_count === 0) {enemy.bullets_count++;}
// console.log(enemy.bullets[0][i]["x"]);
}
enemy.bullets_angle += 10;
enemy.barrage_count ++;
if (enemy.barrage_count % 100 === 0) {
enemy.current_skill = 0;
}
}
} /* end setBullets */
function moveBullets(){
if (count % 4 ===0) {
for (var i = 0; i < enemy.bullets.length; i++) {
if (enemy.bullets[i]["moving"] === true) {
var radian = enemy.bullets[i]["radian"];
var center_x = enemy.bullets[i]["center_x"];
var center_y = enemy.bullets[i]["center_y"];
enemy.bullets[i]["radius"] += 5;
var radius = enemy.bullets[i]["radius"];
var set_x = center_x + radius * Math.cos(radian);
var set_y = center_y + radius * Math.sin(radian);
// console.log(set_y);
enemy.bullets[i]["x"] = set_x;
enemy.bullets[i]["y"] = set_y;
if (enemy.bullets[i]["x"] < -100 || enemy.bullets[i]["x"] > canvas_w + 100 || enemy.bullets[i]["y"] < -100 || enemy.bullets[i]["y"] > canvas_y + 100 ) {
// enemy.bullets[i]["moving"] = false;
enemy.bullets.splice(i, 1);
}
}
}
}
}
if (enemy.current_skill === 1) {
setBullets();
}
moveBullets();
}
function draw(){
var canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
//hero
//ctx.fillStyle = "blue";
//ctx.fillRect(hero.x, hero.y ,hero.w, hero.h);
//enemy
//ctx.fillStyle = "red";
//ctx.fillRect(enemy.x, enemy.y ,enemy.w, enemy.h);
//heroの弾
ctx.fillStyle = "blue";
for (var i = 0; i < hero.bullets.length; i++) {
if (hero.bullets[i].moving === false) {
continue;
}
ctx.fillRect(hero.bullets[i].x, hero.bullets[i].y ,hero.bullets[i].w, hero.bullets[i].h);
}
//敵の弾
ctx.fillStyle = "red";
for (var i = 0; i < enemy.bullets.length; i++) {
ctx.fillStyle = "green";
if (enemy.bullets[i]["moving"] === false) {
continue;
}
ctx.beginPath();
ctx.arc(enemy.bullets[i]["x"], enemy.bullets[i]["y"], 15, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.fill();
}
}
window.addEventListener("keydown", function(e){
switch(e.keyCode){
case 37: keycode = 37; break;
case 38: keycode = 38; break;
case 39: keycode = 39; break;
case 40: keycode = 40; break;
}
}, false);
window.addEventListener("keyup", function(e){
switch(e.keyCode){
case 37: keycode = NaN; break;
case 38: keycode = NaN; break;
case 39: keycode = NaN; break;
case 40: keycode = NaN; break;
}
}, false);
init();
There's no problem at the beginning, but then some of the bullets would start acting weirdly as if after some point.
The cause is that an item is removed while in a for-loop, causing one bullet to be skipped, more precisely, this line:
enemy.bullets.splice(i, 1);
I would suggest a different approach - build a new array only consisting of the active bullets (moving===true), then after the loop replace the array with the new.
For example:
function moveBullets(){
if (count % 4 ===0) {
// will hold active bullets in current pass
var newBullets = [];
for (var i = 0; i < enemy.bullets.length; i++) {
// cut code for clarity
if (!(enemy.bullets[i].x < -100 || enemy.bullets[i].x > canvas_w + 100 ||
enemy.bullets[i].y < -100 || enemy.bullets[i].y > canvas_y + 100 )) {
newBullets.push(enemy.bullets[i]);
}
}
// replace array with only active bullets
enemy.bullets = newBullets;
}
}
The new array will simply hold references to existing active bullets.
Modified Fiddle
Particles and object pools.
Creating a new array is not a good strategy when dealing with particle systems. Which is what bullets are in effect.
Each bullet deleted, will need to be cleaned up by GC (Garbage Collection), each bullet added needs to be created and requires memory allocation. These overhead can have a negative effect on a game. GC can cause an animation to hang at random times.
For consistent, smooth animation you should aim for zero allocation and deletion (which is possible as asm.js and web Assembly do not allocate or delete inside a running modula).
Object pool
In vanilla JS it is possible but the code is too complex for this answer. The next best solution is to use a object pool.
As bullets are first created as normal, but when a bullet is no longer needed rather than dereference it you move it to another array (called the pool), next time a bullet is needed you first check if any are available on the pool and use that rather than create a new object.
This ensures that GC only has to clean up the changing array sizes and not the data used by each bullet.
var bullets = [];
var bulletPool = [];
function createBullet(x,y,dir,whatNot){
var newBullet;
if(bulletPool.length > 0){
newBullet = bulletPool.pop(); // reuse old bullet memory
newBullet.x = x;
newBullet.y = y;
newBullet.dir = dir;
newBullet.whatNot = whatNot;
newBullet.active = true;
}else{
newBullet = {x,y,dir,whatNot,active:true}; // create only if needed
}
return newBullet;
}
function fire(){
bullets.push(createBullet(10,10,0,0)); /// add a bullet
}
In your code when a bullet is no longer needed just set the active flag to false. At the end of the game loop remove all the inactive bullets.
When you delete a bullet just move it from the bullet array to the pool
function cleanBulletsArray(){
for(var i = 0; i < bullets.length; i ++){
if(!bullets[i].active){
bulletPool.push(bullets.splice(i--,1)[0]);
}
}
}
Precipitation array
So called because the active items fall to the bottom of the array
An even better method is to use just one array. When a bullet is not active it stays in the array, but as you iterate the array you swap active bullets for inactive, with active bullets moving down and inactive up. There is at most one swap per iteration. You also keep a count of the number of active bullets. When you need a new bullet you just reset the properties of the bullet at index count + 1 and then increase count by one.
This method (if you pre allocate all the bullets at the start of the game) has zero GC and Memory overhead and is significantly quicker than the creation, destruction, and array replacement methods.
I am making a 2d game in JavaScript but my character can only move around the length of the screen so I was wondering if there was a way to make the canvas move but my character stay on the middle of the screen could Simone please help me
here is my code for the test game
<html>
<body>
<canvas id="canvas">
</canvas>
</body>
</html>
<script>
var audio = new Audio('sounds/theServerRoom.mp3');
audio.play();
// Create the canvas
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
// Background image
var bgReady = false;
var bgImage = new Image();
bgImage.onload = function() {
bgReady = true;
};
bgImage.src = "images/gamemap.png";
//computer
var computerReady = false;
var computerImage = new Image();
computerImage.onload = function() {
computerReady = true;
};
computerImage.src = "images/computer1.png";
//hp box
var hpBoxReady = false;
var hpBoxImage = new Image();
hpBoxImage.onload = function() {
hpBoxReady = true;
};
hpBoxImage.src = "images/hpbox.png";
// player image
var playerReady = false;
var playerImage = new Image();
playerImage.onload = function() {
playerReady = true;
};
playerImage.src = "images/char.png";
// enemy image
var enemyReady = false;
var enemyImage = new Image();
enemyImage.onload = function() {
enemyReady = true;
};
enemyImage.src = "images/enemy_idle01.png";
var computer = {
wifi: true,
x: 399,
y: 200
}
// Game objects
var hpBox = {
restoreHealth: 34,
x: 300,
y: 300
}
var player = {
hackingSkill : 10,
stamina: 7,
health: 100,
sprintSpeed: 400,
weakSpeed: 150,
speed: 300 // movement in pixels per second
};
var enemy = {
speed: 250,
viewDistance: 40
};
var enemysCaught = 0;
// Handle keyboard controls
var keysDown = {};
addEventListener("keydown", function(e) {
keysDown[e.keyCode] = true;
}, false);
addEventListener("keyup", function(e) {
delete keysDown[e.keyCode];
}, false);
// Reset the game when the player catches a enemy
var reset = function() {
player.x = canvas.width / 2;
player.y = canvas.height / 2;
// Throw the enemy somewhere on the screen randomly
enemy.x = 32 + (Math.random() * (canvas.width - 64));
enemy.y = 32 + (Math.random() * (canvas.height - 64));
};
//w is 87
//a is 65
//s is 83
//d is 68
// Update game objects
var update = function(modifier) {
if (87 in keysDown) { // Player holding up
player.y -= player.speed * modifier;
}
if (83 in keysDown) { // Player holding down
player.y += player.speed * modifier;
}
if (65 in keysDown) { // Player holding left
player.x -= player.speed * modifier;
}
if (68 in keysDown) { // Player holding right
player.x += player.speed * modifier;
}
if (
player.x <= (0)) {
player.health -= 1;
console.log('health decreasing');
}
}
if (
player.y <= (0)) {
player.health -= 1;
console.log('health decreasing');
};
// Are they touching?
if (
player.x <= (enemy.x + 32) &&
enemy.x <= (player.x + 32) &&
player.y <= (enemy.y + 32) &&
enemy.y <= (player.y + 32)
) {
++enemysCaught;
reset();
}
// Draw everything
var render = function() {
if (bgReady) {
context.drawImage(bgImage, 0, 0);
}
if (computerReady) {
context.drawImage(computerImage, computer.x, computer.y);
}
if (hpBoxReady) {
context.drawImage(hpBoxImage, hpBox.x, hpBox.y);
}
if (playerReady) {
context.drawImage(playerImage, player.x, player.y);
}
if (enemyReady) {
context.drawImage(enemyImage, enemy.x, enemy.y);
}
// Score
};
function dieEvent() {
player.health = 100;
}
function updateHealth() {
context.fillStyle = "white";
context.textAlign = "left";
context.fillText("Health: " + player.health, 30, 32);
context.fillStyle="#FF0000";
context.fillRect(10,10,(player.health/100)*140,25);
context.stroke();
}
function updateHackerSkill(){
context.fillStyle = "green";
context.textAlign = "left";
context.fillText("Health: " + player.hackerSkill, 30, 32);
context.fillStyle="#FF0000";
context.fillRect(10,10,(player.hackerSkill/100)*1,45);
context.stroke();
}
function isNearComputer() {
if (player.y <= (computer.y + enemy.viewDistance + 23) &&
player.y >= (computer.y - enemy.viewDistance) &&
player.x <= (computer.x + enemy.viewDistance + 32) &&
player.x >= (computer.x - enemy.viewDistance)) {
console.log("near computer");
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
context.stroke();
context.fillStyle = "green";
context.font = "24px Helvetica";
context.textAlign = "left";
context.textBaseline = "top";
context.fillText("Welcome to uOS v1.0 " , 20 ,10);
window.setTimeout(500);
context.fillText("user$> " , 20 ,35);
}
}
function isNearHPBox() {
if (
player.y <= (hpBox.y + enemy.viewDistance + 64) &&
player.y >= (hpBox.y - enemy.viewDistance - 64) &&
player.x <= (hpBox.x + enemy.viewDistance + 64) &&
player.x >= (hpBox.x - enemy.viewDistance - 64)) {
console.log("healing!");
if (player.health <= 100) {
hpBox.restoreHealth = player.health - 100;
player.health += hpBox.restoreHealth;
}
}
}
function moveEnemy() {
if (
player.y <= (enemy.y + enemy.viewDistance + 64) &&
player.y >= (enemy.y - enemy.viewDistance - 64) &&
player.x <= (enemy.x + enemy.viewDistance + 64) &&
player.x >= (enemy.x - enemy.viewDistance - 64)) {
console.log("seen on enemys Y");
var audio = new Audio('sounds/theWanderer_Scream.m4a');
audio.play();
if (player.x >= (enemy.x)) {
enemy.x -= enemy.speed;
}
if (player.x >= (enemy.x)) {
enemy.x -= enemy.speed;
}
}
}
function checkWallCollision() {
if (player.y <= 0) {
console.log("y")
player.y += 64;
}
if (player.x <= 0) {
console.log("x")
player.x += 64;
}
if (enemy.y <= 0) {
console.log("y")
enemy.y += 64;
}
if (enemy.x <= 0) {
console.log("x")
enemy.x += 64;
}
}
// function updateMouseCoords(){
// document.onmousemove = function(e){
// cursorX = e.pageX;
// cursorY = e.pageY;
// context.fillStyle = "green";
// context.font = "24px Helvetica";
// context.textAlign = "left";
// context.textBaseline = "top";
// context.fillText("x" + cursorX + "y" + cursorY , 20 ,10);
//
// }
// }
// function drawViewLine(){
// var cursorX;
// var cursorY;
// context.beginPath();
// context.moveTo(player.x,player.y);
// context.lineTo(cursorX,cursorY);
// context.stroke();
// console.log("drawing line")
// }
function reducedSpeed() {
player.speed = player.weakSpeed;
}
// The main game loop
var main = function() {
var now = Date.now();
var delta = now - then;
update(delta / 1000);
context.clearRect(0, 0, canvas.width, canvas.height);
render();
updateHealth();
moveEnemy();
if (player.health <= 20) {
reducedSpeed();
} else {
player.speed = 300;
}
if (player.health <= 0) {
dieEvent();
}
checkWallCollision();
isNearHPBox();
isNearComputer();
//updateMouseCoords();
//drawViewLine();
then = now;
// Request to do this again ASAP
requestAnimationFrame(main);
};
// Cross-browser support for requestAnimationFrame
var w = window;
requestAnimationFrame = w.requestAnimationFrame || w.webkitRequestAnimationFrame || w.msRequestAnimationFrame || w.mozRequestAnimationFrame;
// Let's play this game!
var then = Date.now();
reset();
main();
</script>
<style>
body {
margin: 0;
padding: 0;
}
</style>
there are multiple ways to deal with this issue, here are the main 2 i can think of right now:
1.- Move the world instead of your character: what you do is that your character actually stays in place, and you move the rest of the world instead, this is mainly useful for runner games, where movement is constant or one-dimensional (only left-right or up-down but never both in the same level)
2.- I haven't tested this, but what if your canvas is actually the size of the level, but it is partially hidden by a parent DIV with a specific width and height? this way you can move your character around the canvas, and move the canvas around the div to keep the character centered.
These solutions may or may not translate properly to your specific game though, since you gave literally no details about how movement is being dealt with at the moment.