In my code the snake and it's movement consists of an array. In other words the snake's head is the first array number and last number is snake's tail. I try to implement the movement using array.pop and array.unshift. The problem is that the snake gets bigger and bigger as it moves. I think it is some very small detail that I'm missing. Here is the code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cWidth = canvas.width;
var cHeight = canvas.height;
var boxWidth = 10;
var boxHeight = 10;
var boxWH = 10; //One box width and height
var points = 0;
var score = document.getElementById("Score");
var foodImg = new Image();
var k;
foodImg.src = "Pictures/apple2.png";
var snake = [];
snake[0] = {
x: cWidth/2,
y: cHeight/2
};
var food = {
x: Math.floor(Math.random()*60)*boxWH,
y: Math.floor(Math.random()*60)*boxWH
}
document.addEventListener("keydown", direction);
function direction(event){
var key = event.keyCode;
if(key == 65 && k != "right"){
k = "left";
}
else if(key == 68 && k != "left"){
k = "right";
}
else if(key == 87 && k != "down"){
k = "up";
}
else if(key == 83 && k != "up"){
k = "down";
}
}
function SnakeGame(){
ctx.fillStyle = "green";
ctx.fillRect(0, 0, cWidth, cHeight);
var game = setInterval(Draw, 100);
}
function Draw(){
for(var i = 0; i<snake.length; i++){
ctx.fillStyle = (i == 0) ? "red" : "white";
ctx.fillRect(snake[i].x, snake[i].y, boxWH, boxWH);
ctx.strokeStyle = "yellow";
ctx.strokeRect(snake[i].x, snake[i].y, boxWH, boxWH);
}
ctx.drawImage(foodImg, food.x, food.y);
var snakeX = snake[0].x;
var snakeY = snake[0].y;
snake.pop();
if(k == "right") snakeX += boxWH;
if(k == "left") snakeX -= boxWH;
if(k == "up") snakeY -= boxWH;
if(k == "down") snakeY += boxWH;
var nHead = {
x: snakeX,
y: snakeY
}
snake.unshift(nHead);
}
the problem is that the sanke gets bigger and bigger as it moves
The snake has the correct size, what happens is that it is leaving a "trail". To avoid that, you need to clear the canvas each Draw call.
Just move the command to fill the canvas from the SnakeGame function to the beginning of Draw, like this:
function SnakeGame(){
var game = setInterval(Draw, 100);
}
function Draw(){
ctx.fillStyle = "green";
ctx.fillRect(0, 0, cWidth, cHeight);
for(var i = 0; i<snake.length; i++){
ctx.fillStyle = (i == 0) ? "red" : "white";
ctx.fillRect(snake[i].x, snake[i].y, boxWH, boxWH);
//(...)
The problem has to do with the fact that you draw the snake onto the canvas without resetting it every time. The new snake tiles are drawn, but the old ones are also still there.
You can fix it by moving these lines to the beginning of the Draw function, so they are executed every time you are about to redraw the snake:
ctx.fillStyle = "green";
ctx.fillRect(0, 0, cWidth, cHeight);
Related
I'm developing a snake game using canvas but am having trouble with displaying the player's score in the upper right corner. This should be handled in the drawScore function but the text doesn't appear. I'm not getting any errors in the console, nor can I find any problem with the code itself.
Any advice would be appreciated!
class SnakePart {
constructor(x,y) {
this.x = x;
this.y = y;
}
}
const body = document.body;
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
let speed = 7; // base speed variable of snake
const snakeParts = [];
let tailLength = 2;
let tileCount = 20;//number of tiles on screen
let headX = 10;//snake width
let headY = 10;//snake height
let tileSize = canvas.width / tileCount - 2;//size of tiles where apple spawns
let xv = 0;// x axis velocity
let yv = 0;//y axis velocity
let ax = 5;//apple position x axis
let ay = 5;//apple position y axis
let score = 0;
//draws game loop
function drawGame(){
//game loop
clearScreen();
changeSnakePosition();
checkAppleCollision();
drawApple();
drawSnake();
drawScore();
setTimeout(drawGame, 1000/speed);
}
function drawScore(){
ctx.fillStyle = 'white';
ctx.font = '12px Helvetica';
ctx.fillText = ("Score: " + score, canvas.width-50, 10);
}
function clearScreen(){
ctx.fillStyle = 'black';
ctx.fillRect(0,0,canvas.width,canvas.height);
}
function drawSnake(){
ctx.fillStyle = 'orange';
ctx.fillRect(headX * tileCount, headY * tileCount, tileSize,tileSize);
ctx.fillStyle = 'green'
for(let i = 0; i<snakeParts.length; i++){
let part = snakeParts[i];
ctx.fillRect(part.x *tileCount,part.y*tileCount,tileSize,tileSize)
}
snakeParts.push(new SnakePart(headX,headY)); //put a segment at the end of the snake next to the head
while(snakeParts.length > tailLength){
snakeParts.shift(); //remove the furthest item from the snake parts if we have more than our tailSize.
}
}
body.addEventListener('keydown', keyDown);
function keyDown(event){
switch(event.keyCode) {
case 37:
if(xv == 1)
return;
yv = 0;
xv = -1
break;
case 38:
if(yv == 1)
return;
yv= -1;
xv = 0;
break;
case 39:
if(xv == -1)
return;
yv = 0;
xv = 1
break;
case 40:
if(yv == -1)
return;
yv = 1;
xv = 0;
}
}
function changeSnakePosition(){
headX = headX + xv;
headY = headY + yv;
}
function drawApple(){
ctx.fillStyle = 'red';
ctx.fillRect(ax*tileCount,ay*tileCount,tileSize,tileSize)
}
function checkAppleCollision(){
if(ax === headX && ay == headY){
ax = Math.floor(Math.random()*tileCount);
ay = Math.floor(Math.random()*tileCount);
tailLength++;
score++;
speed++;
}
}
drawGame();
<canvas id="canvas"></canvas>
The problem is this line :
ctx.fillText = ("Score: " + score, canvas.width-50, 10);
Which should be :
ctx.fillText("Score: " + score, canvas.width-50, 10);
Here's a stripped down example :
const body = document.body;
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
let score = 0;
function drawGame() {
clearScreen();
drawScore();
}
function drawScore() {
ctx.fillStyle = 'white';
ctx.font = '12px Helvetica';
ctx.fillText("Score: " + score, canvas.width-50, 10);
}
function clearScreen() {
ctx.fillStyle = 'black';
ctx.fillRect(0,0,canvas.width,canvas.height);
}
drawGame();
<canvas id="canvas"></canvas>
There are two issues, I can't control the snake, and now it just resets constantly without giving the snake a chance to move. I figured that if i kept coding the problem would go away but sadly its not a perfect world.
I tried different key codes for controlling the snake but it didn't work.
window.onload = function() {
var cvs = document.getElementById("canvas");
var ctx = cvs.getContext("2d");
var cvsW = cvs.width;
var cvsH = cvs.height;
var snekW = 10;
var snekH = 10;
//score
var score = 0;
//default direction
var direction = "right";
//read users directions
document.addEventListener("keydown",getDirection);
//To my knowledge this function should control the snake with the
keyboard
function getDirection(e)
{
if(e.keyCode == 37 && direction != "right"){
direction = "left";
}else if(e.keyCode == 38 && direction != "down"){
direction = "up";
}else if(e.keyCode == 39 && direction != "left"){
direction = "right";
}else if(e.keycode == 40 && direction != "up"){
direction = "down";
}
}
function drawSnek(x,y)
{
ctx.fillStyle = "Lime";
ctx.fillRect(x*snekW,y*snekH,snekW,snekH);
ctx.fillStyle = "#000";
ctx.strokeRect(x*snekW,y*snekH,snekW,snekH);
}
var len = 4; //default length of the snake
var snek = []; //the snake is an array
for(var i = len-1; i>=0; i--)
{
snek.push({x:i,y:0});
}
//exceptions for direction of snake
if(direction == "left") snekx--;
else if( direction == "up") sneky--;
else if( direction == "right") snekx++;
else if( direction == "down") sneky++;
//Functions for creating snake food have been removed.
var newHead = { x: snekx, y: sneky};
snek.unshift(newHead);
drawScore(score);
}
setInterval(draw, 10); //I do not fully understand this function
}
There were several issues with your code:
You needed a draw function that could be called in an interval. In this draw function you could draw the individual parts of the snake.
You were unable to control the snake correctly as you had a typo in the down part of the callback function.
You have to either introduce x / y variables for the head of the snake or use the first element of the array to retrieve it.
Unshifting the new position using snek.unshift will cause the snake to grow indefinitely. Removing array elements using
snek.splice(len,snek.length - len);
solves this problem.
If you only draw new elements to the screen the old parts you drew will still exist in new frames. A good idea would be to clear the canvas using
ctx.clearRect(0, 0, cvsW, cvsH);
window.onload = function () {
var cvs = document.getElementById("canvas");
var ctx = cvs.getContext("2d");
var cvsW = cvs.width;
var cvsH = cvs.height;
var snekW = 10;
var snekH = 10;
//score
var score = 0;
//default direction
var direction = "right";
//read users directions
document.addEventListener("keydown", getDirection);
//To my knowledge this function should control the snake with the keyboard
function getDirection(e) {
if (e.keyCode == 37 && direction != "right") {
direction = "left";
} else if (e.keyCode == 38 && direction != "down") {
direction = "up";
} else if (e.keyCode == 39 && direction != "left") {
direction = "right";
} else if (e.keyCode == 40 && direction != "up") {
direction = "down";
}
}
function drawSnek(x, y) {
ctx.fillStyle = "Lime";
ctx.fillRect(x * snekW, y * snekH, snekW, snekH);
ctx.fillStyle = "#000";
ctx.strokeRect(x * snekW, y * snekH, snekW, snekH);
}
let snekx = 0;
let sneky = 0;
var len = 4; //default length of the snake
var snek = []; //the snake is an array
for (var i = len - 1; i >= 0; i--) {
snek.push({ x: i, y: 0 });
}
function draw() {
ctx.clearRect(0, 0, cvsW, cvsH);
//exceptions for direction of snake
if (direction == "left") snekx--;
else if (direction == "up") sneky--;
else if (direction == "right") snekx++;
else if (direction == "down") sneky++;
//Functions for creating snake food have been removed.
var newHead = { x: snekx, y: sneky };
snek.unshift(newHead);
for(let i = 0; i < snek.length; i++) {
drawSnek(snek[i].x, snek[i].y)
}
snek.splice(len,snek.length - len);
//drawScore(score);
}
setInterval(draw, 50); //I do not fully understand this function
}
CodePen with fixed code:
https://codepen.io/lukasmoeller/pen/PvVzqq?editors=1111
(boundaries are not checked - the snake will leave the canvas)
Explanation of setInterval(fn, interval)
setInterval calls the function specified using fn every interval ms until it is cleared. The interval can be cleared by using clearInterval, passing it the return value of setInterval. If you want to delay function execution at the beginning you could add setTimeout, add a callback that sets the interval:
setTimeout(function(){
setInterval(draw, 50);
}, 1000)
This would only start drawing the game every 50 ms after 1000ms have passed.
I am trying to create a simple snake game. My problem is, most of the times when the snake meets the food, the position of the snake is not how it should be.
For a better understanding, please look at this screenshot where the snake (white) meets the food (green):
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
x = 0,
y = 0,
speed = 3;
x_move = speed,
y_move = 0,
food_position_x = Math.floor(Math.random() * canvas.width) - 20;
food_position_y = Math.floor(Math.random() * canvas.height) - 20;
// Drawing
function draw() {
requestAnimationFrame(function() {
draw();
});
// Draw the snake
ctx.beginPath();
ctx.rect(x, y, 20, 20);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.closePath();
// Draw the food
ctx.beginPath();
ctx.rect(food_position_x, food_position_y, 20, 20);
ctx.fillStyle = "lightgreen";
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:red }
<canvas id="canvas" width="600" height="400"></canvas>
So it seems like I would need a simple grid system but how would I go ahead on this to fix this issue?
I can't see the code for your collision. But I think you have a mistake in your origin. The origin of your squares is not in the middle, it is on the top left corner.
I am making snake game through javascript and I have come to 2 problems.
First problem: I set condition for "killing" snake when his X and Y hits the border of canvas and I can not get why it is not working.
Second problem: My method "draw" of "snake" is working fine how it is written but if I put instead of "var i = snake.dlzka" "var i = snake.snake_body.length" it types an error: Cannot read property 'x' of undefined.
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 650;
var ctx = canvas.getContext("2d");
var dotW = 10;
var dotH = 10;
var direction = "right"; //smer pohybu hadika
var snake = {
dlzka: 5,
snake_body: [],
arraySpawn: function(){
for(var i = snake.dlzka; i >= 0; i--){
this.snake_body.push({
x: i*dotW,
y: 0
});
}
},
draw: function(){
ctx.clearRect(0,0, canvas.width, canvas.height);
for(var i = snake.dlzka; i>0; i--){
//alert(snake.snake_body.length);
ctx.fillStyle = "blue";
ctx.fillRect(snake.snake_body[i].x, snake.snake_body[i].y, dotW, dotH);
}
snake.move();
},
move: function(){
var first = snake.snake_body[0];
var first_x = first.x;
var first_y = first.y;
switch(direction){
case "right":
first_x += 10;
break;
case "left":
first_x -= 10;
break;
case "up":
first_y -= 10;
break;
case "down":
first_y += 10;
break;
}
this.snake_body.pop();
this.snake_body.unshift({
x: first_x,
y: first_y
});
if((first_x == canvas.width || 0) || (first_y == canvas.height || 0)){
var restart = confirm("Do you want to play again?");
if(restart){
clearInterval(intervalID);
snake.snake_body = [];
snake.arraySpawn();
intervalID = setInterval(function(){snake.draw()}, 33);
}
}
},
update_direction: function(e){
var key = e.keyCode;
if(key == 37 && direction !== "right"){
direction = "left";
}else if(key == 38 && direction !== "down"){
direction = "up";
}else if(key == 39 && direction !== "left"){
direction = "right";
}else if(key == 40 && direction !== "up"){
direction = "down";
}
}
};
snake.arraySpawn();
var intervalID = setInterval(function(){snake.draw()}, 33);
window.onkeydown = snake.update_direction;
canvas{
border: 1px solid black;
}
<canvas id="canvas"></canvas>
fiddle: http://jsfiddle.net/scarface/pdz4k8dj/20/
The first problem arises from the code snippet (first_x == canvas.width || 0) Based on operator precedence rules and left to right evaluation, this evaluates to
((first_x == canvas.width) || 0)
which is not what you wanted. Try
(first_x == canvas.width || first_x == 0)
instead but please try and read the rules to make you sure you understand why. Note the test requires the canvas to be an exact multiple of the x (or y) amount the snake is moved by, and it arguably could be better written as
(first_x >= canvas.width || first_x <= 0)
Don't forget to change the y value test because it has exactly the same problem.
The second issue is due to not taking into account that array indices are zero based. So
for(var i = snake.snake_body.length; i>0; i--)
iterates from past the end of the array (generating the error) back to the second element. Changing this to
for(var i = snake.snake_body.length - 1; i >= 0; i--)
worked in the fiddle.
I'm trying to achieve multiple animations in a game that I am creating using Canvas (it is a simple ping-pong game). This is my first game and I am new to canvas but have created a few experiments before so I have a good knowledge about how canvas work.
First, take a look at the game here.
The problem is, when the ball hits the paddle, I want a burst of n particles at the point of contact but that doesn't came right. Even if I set the particles number to 1, they just keep coming from the point of contact and then hides automatically after some time.
Also, I want to have the burst on every collision but it occurs on first collision only. I am pasting the code here:
//Initialize canvas
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
W = window.innerWidth,
H = window.innerHeight,
particles = [],
ball = {},
paddles = [2],
mouse = {},
points = 0,
fps = 60,
particlesCount = 50,
flag = 0,
particlePos = {};
canvas.addEventListener("mousemove", trackPosition, true);
//Set it's height and width to full screen
canvas.width = W;
canvas.height = H;
//Function to paint canvas
function paintCanvas() {
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "black";
ctx.fillRect(0, 0, W, H);
}
//Create two paddles
function createPaddle(pos) {
//Height and width
this.h = 10;
this.w = 100;
this.x = W/2 - this.w/2;
this.y = (pos == "top") ? 0 : H - this.h;
}
//Push two paddles into the paddles array
paddles.push(new createPaddle("bottom"));
paddles.push(new createPaddle("top"));
//Setting up the parameters of ball
ball = {
x: 2,
y: 2,
r: 5,
c: "white",
vx: 4,
vy: 8,
draw: function() {
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.arc(this.x, this.y, this.r, 0, Math.PI*2, false);
ctx.fill();
}
};
//Function for creating particles
function createParticles(x, y) {
this.x = x || 0;
this.y = y || 0;
this.radius = 0.8;
this.vx = -1.5 + Math.random()*3;
this.vy = -1.5 + Math.random()*3;
}
//Draw everything on canvas
function draw() {
paintCanvas();
for(var i = 0; i < paddles.length; i++) {
p = paddles[i];
ctx.fillStyle = "white";
ctx.fillRect(p.x, p.y, p.w, p.h);
}
ball.draw();
update();
}
//Mouse Position track
function trackPosition(e) {
mouse.x = e.pageX;
mouse.y = e.pageY;
}
//function to increase speed after every 5 points
function increaseSpd() {
if(points % 4 == 0) {
ball.vx += (ball.vx < 0) ? -1 : 1;
ball.vy += (ball.vy < 0) ? -2 : 2;
}
}
//function to update positions
function update() {
//Move the paddles on mouse move
if(mouse.x && mouse.y) {
for(var i = 1; i < paddles.length; i++) {
p = paddles[i];
p.x = mouse.x - p.w/2;
}
}
//Move the ball
ball.x += ball.vx;
ball.y += ball.vy;
//Collision with paddles
p1 = paddles[1];
p2 = paddles[2];
if(ball.y >= p1.y - p1.h) {
if(ball.x >= p1.x && ball.x <= (p1.x - 2) + (p1.w + 2)){
ball.vy = -ball.vy;
points++;
increaseSpd();
particlePos.x = ball.x,
particlePos.y = ball.y;
flag = 1;
}
}
else if(ball.y <= p2.y + 2*p2.h) {
if(ball.x >= p2.x && ball.x <= (p2.x - 2) + (p2.w + 2)){
ball.vy = -ball.vy;
points++;
increaseSpd();
particlePos.x = ball.x,
particlePos.y = ball.y;
flag = 1;
}
}
//Collide with walls
if(ball.x >= W || ball.x <= 0)
ball.vx = -ball.vx;
if(ball.y > H || ball.y < 0) {
clearInterval(int);
}
if(flag == 1) {
setInterval(emitParticles(particlePos.x, particlePos.y), 1000/fps);
}
}
function emitParticles(x, y) {
for(var k = 0; k < particlesCount; k++) {
particles.push(new createParticles(x, y));
}
counter = particles.length;
for(var j = 0; j < particles.length; j++) {
par = particles[j];
ctx.beginPath();
ctx.fillStyle = "white";
ctx.arc(par.x, par.y, par.radius, 0, Math.PI*2, false);
ctx.fill();
par.x += par.vx;
par.y += par.vy;
par.radius -= 0.02;
if(par.radius < 0) {
counter--;
if(counter < 0) particles = [];
}
}
}
var int = setInterval(draw, 1000/fps);
Now, my function for emitting particles is on line 156, and I have called this function on line 151. The problem here can be because of I am not resetting the flag variable but I tried doing that and got more weird results. You can check that out here.
By resetting the flag variable, the problem of infinite particles gets resolved but now they only animate and appear when the ball collides with the paddles. So, I am now out of any solution.
I can see 2 problems here.
your main short term problem is your use of setinterval is incorrect, its first parameter is a function.
setInterval(emitParticles(particlePos.x, particlePos.y), 1000/fps);
should be
setInterval(function() {
emitParticles(particlePos.x, particlePos.y);
}, 1000/fps);
Second to this, once you start an interval it runs forever - you don't want every collision event to leave a background timer running like this.
Have an array of particles to be updated, and update this list once per frame. When you make new particles, push additional ones into it.