I have a snake game based on created with JavaScript and canvas.
The problem is the collision works in blocks but it works only when the head has moved to the outermost cell. How this can be solved so that this block of the head does not fall into the frame?
I tried to create an empty block of a new head, but it did not work.
const playground = document.querySelector('canvas');
const ctx = playground.getContext('2d');
const scoreBox = document.querySelector('#scoreBox');
document.addEventListener("keydown", moveSnake);
playground.width = 500;
playground.height = 500;
const gridSize = 20;
const snakeColor = "darkslategray";
const foodColor = "indianred";
const spaceGrid = 2;
let tileCount = playground.width / gridSize;
let snakeVelocityX = 0;
let snakeVelocityY = 0;
let start = true;
let canDeath = false;
let drawSnakeTail = true;
const SnakeLen = [];
const gameFruits = [];
let snakeTail = 3;
function sleep(ms) {
ms += new Date().getTime();
while (new Date() < ms) {}
}
function getRandCoord() {
return Math.floor(Math.random() * tileCount);
}
let snakeX = playground.width / 2 - gridSize / 2;
let snakeY = playground.height / 2 - gridSize / 2;
console.log(snakeX);
function drawGameScene() {
ctx.fillStyle = "snow";
ctx.fillRect(0, 0, playground.width, playground.height);
}
function moveSnake(ev) {
const oldSnakeSpeedX = snakeVelocityX;
const oldSnakeSpeedY = snakeVelocityY;
switch (ev.keyCode) {
case 37:
snakeVelocityX = -1;
snakeVelocityY = 0;
break;
case 38:
snakeVelocityX = 0;
snakeVelocityY = -1;
break;
case 39:
snakeVelocityX = 1;
snakeVelocityY = 0;
break;
case 40:
snakeVelocityX = 0;
snakeVelocityY = 1;
break;
}
canDeath = true;
if ((snakeVelocityX !== 0 && oldSnakeSpeedX !== 0 && snakeVelocityX !== oldSnakeSpeedX) ||
(snakeVelocityY !== 0 && oldSnakeSpeedY !== 0 && snakeVelocityY !== oldSnakeSpeedY)) {
snakeVelocityX = oldSnakeSpeedX;
snakeVelocityY = oldSnakeSpeedY;
}
}
function drawFruits() {
for (let i = 0; i < gameFruits.length; i++) {
const fruit = gameFruits[i];
ctx.fillStyle = fruit.color;
ctx.fillRect(fruit.x * gridSize, fruit.y * gridSize, gridSize - 2, gridSize - 2);
}
}
function SnakeCollisionFoodHandler() {
for (let i = 0; i < gameFruits.length; i++) {
const fruit = gameFruits[i];
if (snakeX === fruit.x && snakeY === fruit.y) {
snakeTail += fruit.points;
gameFruits.splice(i, 1);
spawnFruitTile();
}
}
}
function containsObject(obj, list) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i]["x"] == obj["x"] || list[i]["y"] == obj["y"]) {
return false;
}
}
return true;
}
function spawnFruitTile() {
let x = getRandCoord();
y = getRandCoord();
coors = {
x,
y
};
if (containsObject(coors, SnakeLen)) {
if (x === gridSize + 4 || y === gridSize + 4 || x === 0 || y === 0) {
spawnFruitTile();
} else {
gameFruits.push({
x: x,
y: y,
points: 1,
color: foodColor
});
}
} else {
spawnFruitTile();
}
}
function onGameOver() {
drawSnakeTail = false;
death_score = snakeTail - 3;
ctx.font = "20px Arial";
ctx.fillStyle = '#20201d';
window.pauseAll = true;
ctx.fillText("Game over\nScore:" + death_score, (playground.width - 200) / 2, playground.height / 2);
// alert();
// location.reload();
}
function drawSnake() {
console.log(SnakeLen);
if (drawSnakeTail == true) {
if (start == true) {
snakeVelocityY = 1;
snakeVelocityX = 1;
}
snakeX += snakeVelocityX;
snakeY += snakeVelocityY;
if (snakeX < 0) {
snakeX = tileCount - 1;
}
if (snakeX > tileCount - 1) {
snakeX = 0;
}
if (snakeY < 0) {
snakeY = tileCount - 1;
}
if (snakeY > tileCount - 1) {
snakeY = 0;
}
drawGameScene();
ctx.fillStyle = snakeColor;
for (let i = 0; i < SnakeLen.length; i++) {
const {
x,
y
} = SnakeLen[i];
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
if (start == true) {
snakeVelocityY = 0;
snakeVelocityX = 0;
setTimeout(start = false, 100);
}
if (start == false) {
if (canDeath == true) {
if ((x === snakeX && y === snakeY) && (snakeX !== 0 || snakeY !== 0)) {
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
setTimeout(onGameOver, 100);
}
} //playground.width
// console.log(snakeX , playground.width - tileCount, gridSize + spaceGrid, x);
if (snakeX === gridSize + 4 || snakeY === gridSize + 4 || snakeX === 0 || snakeY === 0) { //
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
setTimeout(onGameOver, 100);
}
}
}
}
SnakeLen.push({
x: snakeX,
y: snakeY
});
while (SnakeLen.length > snakeTail) {
SnakeLen.shift();
}
}
function drawBorder() {
ctx.globalCompositeOperation = "source-over";
ctx.lineWidth = 40;
ctx.strokeStyle = "#076826";
ctx.strokeRect(0, 0, playground.width, playground.height);
}
function drawScore() {
ctx.font = "20px Arial";
ctx.fillStyle = '#20201d';
ctx.fillText("Score:" + (snakeTail - 3), 10, 20);
}
function onGameFrame() {
drawSnake();
drawFruits();
SnakeCollisionFoodHandler();
drawBorder();
drawScore();
}
(function onGameInit() {
spawnFruitTile();
setInterval(onGameFrame, 100);
}());
canvas {
border: 0.5px solid black;
color: black;
display: flex;
position: absolute;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%)
}
<div class="container">
<canvas width="500px" height="500px"></canvas>
</div>
If you won't set a timeout around the onGameOver call, the game will end in the current position. I guess this happens because javascript is able to run another cycle so another frame is drawn before the event runs:
if (snakeX === gridSize + 4 || snakeY === gridSize + 4 || snakeX === 0 || snakeY === 0) { //
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
onGameOver(); // <-- removed the setTimeout wrap
}
const playground = document.querySelector('canvas');
const ctx = playground.getContext('2d');
const scoreBox = document.querySelector('#scoreBox');
document.addEventListener("keydown", moveSnake);
playground.width = 500;
playground.height = 500;
const gridSize = 20;
const snakeColor = "darkslategray";
const foodColor = "indianred";
const spaceGrid = 2;
let tileCount = playground.width / gridSize;
let snakeVelocityX = 0;
let snakeVelocityY = 0;
let start = true;
let canDeath = false;
let drawSnakeTail = true;
const SnakeLen = [];
const gameFruits = [];
let snakeTail = 3;
function sleep(ms) {
ms += new Date().getTime();
while (new Date() < ms) {}
}
function getRandCoord() {
return Math.floor(Math.random() * tileCount);
}
let snakeX = playground.width / 2 - gridSize / 2;
let snakeY = playground.height / 2 - gridSize / 2;
console.log(snakeX);
function drawGameScene() {
ctx.fillStyle = "snow";
ctx.fillRect(0, 0, playground.width, playground.height);
}
function moveSnake(ev) {
const oldSnakeSpeedX = snakeVelocityX;
const oldSnakeSpeedY = snakeVelocityY;
switch (ev.keyCode) {
case 37:
snakeVelocityX = -1;
snakeVelocityY = 0;
break;
case 38:
snakeVelocityX = 0;
snakeVelocityY = -1;
break;
case 39:
snakeVelocityX = 1;
snakeVelocityY = 0;
break;
case 40:
snakeVelocityX = 0;
snakeVelocityY = 1;
break;
}
canDeath = true;
if ((snakeVelocityX !== 0 && oldSnakeSpeedX !== 0 && snakeVelocityX !== oldSnakeSpeedX) ||
(snakeVelocityY !== 0 && oldSnakeSpeedY !== 0 && snakeVelocityY !== oldSnakeSpeedY)) {
snakeVelocityX = oldSnakeSpeedX;
snakeVelocityY = oldSnakeSpeedY;
}
}
function drawFruits() {
for (let i = 0; i < gameFruits.length; i++) {
const fruit = gameFruits[i];
ctx.fillStyle = fruit.color;
ctx.fillRect(fruit.x * gridSize, fruit.y * gridSize, gridSize - 2, gridSize - 2);
}
}
function SnakeCollisionFoodHandler() {
for (let i = 0; i < gameFruits.length; i++) {
const fruit = gameFruits[i];
if (snakeX === fruit.x && snakeY === fruit.y) {
snakeTail += fruit.points;
gameFruits.splice(i, 1);
spawnFruitTile();
}
}
}
function containsObject(obj, list) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i]["x"] == obj["x"] || list[i]["y"] == obj["y"]) {
return false;
}
}
return true;
}
function spawnFruitTile() {
let x = getRandCoord();
y = getRandCoord();
coors = {
x,
y
};
if (containsObject(coors, SnakeLen)) {
if (x === gridSize + 4 || y === gridSize + 4 || x === 0 || y === 0) {
spawnFruitTile();
} else {
gameFruits.push({
x: x,
y: y,
points: 1,
color: foodColor
});
}
} else {
spawnFruitTile();
}
}
function onGameOver() {
drawSnakeTail = false;
death_score = snakeTail - 3;
ctx.font = "20px Arial";
ctx.fillStyle = '#20201d';
window.pauseAll = true;
ctx.fillText("Game over\nScore:" + death_score, (playground.width - 200) / 2, playground.height / 2);
// alert();
// location.reload();
}
function drawSnake() {
console.log(SnakeLen);
if (drawSnakeTail == true) {
if (start == true) {
snakeVelocityY = 1;
snakeVelocityX = 1;
}
snakeX += snakeVelocityX;
snakeY += snakeVelocityY;
if (snakeX < 0) {
snakeX = tileCount - 1;
}
if (snakeX > tileCount - 1) {
snakeX = 0;
}
if (snakeY < 0) {
snakeY = tileCount - 1;
}
if (snakeY > tileCount - 1) {
snakeY = 0;
}
drawGameScene();
ctx.fillStyle = snakeColor;
for (let i = 0; i < SnakeLen.length; i++) {
const {
x,
y
} = SnakeLen[i];
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
if (start == true) {
snakeVelocityY = 0;
snakeVelocityX = 0;
setTimeout(start = false, 100);
}
if (start == false) {
if (canDeath == true) {
if ((x === snakeX && y === snakeY) && (snakeX !== 0 || snakeY !== 0)) {
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
setTimeout(onGameOver, 100);
}
} //playground.width
// console.log(snakeX , playground.width - tileCount, gridSize + spaceGrid, x);
if (snakeX === gridSize + 4 || snakeY === gridSize + 4 || snakeX === 0 || snakeY === 0) { //
ctx.fillRect(x * gridSize, y * gridSize, gridSize - spaceGrid, gridSize - spaceGrid);
onGameOver();
}
}
}
}
SnakeLen.push({
x: snakeX,
y: snakeY
});
while (SnakeLen.length > snakeTail) {
SnakeLen.shift();
}
}
function drawBorder() {
ctx.globalCompositeOperation = "source-over";
ctx.lineWidth = 40;
ctx.strokeStyle = "#076826";
ctx.strokeRect(0, 0, playground.width, playground.height);
}
function drawScore() {
ctx.font = "20px Arial";
ctx.fillStyle = '#20201d';
ctx.fillText("Score:" + (snakeTail - 3), 10, 20);
}
function onGameFrame() {
drawSnake();
drawFruits();
SnakeCollisionFoodHandler();
drawBorder();
drawScore();
}
(function onGameInit() {
spawnFruitTile();
setInterval(onGameFrame, 100);
}());
canvas {
border: 0.5px solid black;
color: black;
display: flex;
position: absolute;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%)
}
<div class="container">
<canvas width="500px" height="500px"></canvas>
</div>
Related
I have been making a snake game for a few days to improve my js skills.
And I released the first beta test of my mini-game. And you actually know that there are always a lot of bugs in beta tests. And I noticed them. First I notice that if the snake.x position < 0, then the snake appears from a different corner, but in front of one block, and not from the zero coordinate. This also applies to snake.y<0.
here is the code:
var canvas = document.getElementById("game");
var ctx = canvas.getContext("2d");
var box = 10;
//Snake
var snake = [];
var dir = "right";
var maxCell = 10;
var can = canvas.getBoundingClientRect();
var px = Math.floor(canvas.width / 2 / 10) * 10;
var py = Math.floor(canvas.height / 2 / 10) * 10;
//Apple
var ax = Math.floor((Math.random() * ~~(canvas.width / box)) / 10) * 10;
var ay = Math.floor((Math.random() * ~~(canvas.height / box)) / 10) * 10;
var loop = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
Elems();
}, 1000 / 40);
document.addEventListener("keydown", function (e) {
if (e.keyCode === 37 && dir !== "right") {
dir = "left";
//console.log('left')
} else if (e.keyCode === 38 && dir !== "down") {
dir = "up";
//console.log('up')
} else if (e.keyCode === 39 && dir !== "left") {
dir = "right";
//console.log('right')
} else if (e.keyCode === 40 && dir !== "up") {
dir = "down";
//console.log('down')
}
});
function direction() {
if (dir == "right") {
px += box;
} else if (dir == "left") {
px -= box;
} else if (dir == "up") {
py -= box;
} else if (dir == "down") {
py += box;
}
}
//Closure )))
function Elems() {
function Apple() {
ctx.fillStyle = "green";
ctx.fillRect(ax, ay, box, box);
}
//! Spawn snake
function Snake() {
direction();
var head = {
x: px,
y: py,
};
snake.unshift(head);
//-------------BUG---------------
if (px >= canvas.width) {
px = 0;
}
if (px + box < 0) {
px = canvas.width;
}
if (py >= canvas.height) {
py = 0;
}
if (py + box < 0) {
py = canvas.height;
}
//-------------------------------
snake.forEach(function (elem, index) {
ctx.fillStyle = `red`;
ctx.fillRect(elem.x, elem.y, box, box);
});
if (head.x == ax && head.y == ay) {
maxCell += 1;
ax = Math.floor(Math.random() * ~~(canvas.width / box)) * 10;
ay = Math.floor(Math.random() * ~~(canvas.height / box)) * 10;
}
for (let i = 1; i < snake.length; i++) {
if (head.x === snake[i].x && head.y === snake[i].y) {
maxCell = 10;
px = Math.floor(canvas.width / 2 / 10) * 10;
py = Math.floor(canvas.height / 2 / 10) * 10;
clearInterval(loop);
alert("Game Over:(")
}
}
if (snake.length < maxCell) {
snake.push({ x: px, y: py });
}
snake.pop();
}
Snake();
Apple();
}
I tried changing the canvas dimensions and box value. But it didn't help x((((
You'd have to rearrange your code a good bit to get it working right.
The Snake function should not happen in a loop, the loop related stuff inside of Snake should be inside the Elems function instead. And the loop being set should happen last.
The following is a basic rework of your code with an added method, and I've included the grid that angel.bonev suggested so it works like a traditional snake game now and with the apple getting eaten to increase the snake size. The testForPointInArea method makes it such that the snake does not have to be exactly over the apple spot, but just overlapping.
Also the opposite direction restrictions in the keydown listener were removed;
&& dir !== "right" && dir !== "down" && dir !== "left" && dir !== "up"
If the snake is a small size or not big enough to bank corners,
then game over would not happen with the above backwards restrictions.
var canvas = document.getElementById("game");
var ctx = canvas.getContext("2d");
var box = 10;
//Snake body Array
var snake = [];
var dir = "right";
var maxCell = 2;
var can = canvas.getBoundingClientRect();
var grid = {
x: Math.floor(canvas.width / box),
y: Math.floor(canvas.height / box)
};
var px = Math.floor(grid.x / 2) * 10;
var py = Math.floor(grid.y / 2) * 10;
//Apple cords
var ax = Math.floor( Math.random() * grid.y ) * box;
var ay = Math.floor( Math.random() * grid.x ) * box;
var theSnake;
document.addEventListener("keydown", function(e) {
e.preventDefault();
if (e.keyCode === 37 ) {
dir = "left";
//console.log('left')
} else if (e.keyCode === 38 ) {
dir = "up";
//console.log('up')
} else if (e.keyCode === 39) {
dir = "right";
//console.log('right')
} else if (e.keyCode === 40 ) {
dir = "down";
//console.log('down')
}
});
function direction() {
if (dir == "right") {
px += 2;
} else if (dir == "left") {
px -= 2;
} else if (dir == "up") {
py -= 2;
} else if (dir == "down") {
py += 2;
}
}
function Apple() {
ctx.fillStyle = "green";
ctx.fillRect(ax, ay, box, box);
}
function testForPointInArea(p, left,top,right,bottom) {
return Boolean(!(p.x < left || p.x > right || p.y < top || p.y > bottom));
}
function Elems() {
direction();
theSnake.head.x = px; theSnake.head.y = py;
snake.unshift({x:theSnake.head.x, y:theSnake.head.y});
for (let i = 1; i < snake.length; i++) {
if (theSnake.head.x === snake[i].x && theSnake.head.y === snake[i].y) {
maxCell = 2;
px = Math.floor(grid.x / 2) * 10;
py = Math.floor(grid.y / 2) * 10;
snake = [];
//clearInterval(loop);
alert("Game Over :(");
return;
}
}
if (snake.length < maxCell) {
snake.push({ x: px, y: py });
}
if (px >= canvas.width) {
px = 0;
}
if (px + box < 0) {
px = canvas.width;
}
if (py >= canvas.height) {
py = 0;
}
if (py + box < 0) {
py = canvas.height;
}
theSnake.drawSnake();
Apple();
snake.pop();
if ( testForPointInArea(theSnake.head, ax - 3, ay - 3, ax + box + 3, ay + box + 3) ) {
maxCell += 1;
ax = Math.floor( Math.random() * grid.y ) * box;
ay = Math.floor( Math.random() * grid.x ) * box;
Apple();
}
}
//Snake Class
function Snake() {
this.head = {
x: px,
y: py,
};
this.drawSnake = function() {
snake.forEach(function (elem, index) {
ctx.fillStyle = "red";
ctx.fillRect(elem.x, elem.y, box, box);
});
};
}
theSnake = new Snake();
var loop = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
Elems();
}, 1000 / 40);
<canvas id="game" width="150px" height="150px"> </canvas>
I was making a snake game. I have a basic game but I wanted to move the snake more smoothly and specially I wanted to turn the direction of the snake more smoothly.
The problem until now is that I have been removing the tail of the snake and adding it to the head for the movement part.
Moving the snake smoothly is rather easy by reducing the interval for calling the draw function and incrementing the x and y values by small amounts but what I am having problem is with turning the snake smoothly,it is exactly where I am lost/confused as to how to do it like done here (only the turning of snake part and not the changing of head when snake gets blocked)
can anyone help out please? here is my code:
let cvs = "";
let ctx = "";
let size = [];
var direction = 'b';
var speed = 200;
//moving lines on arrow click
window.addEventListener("keydown", moveSomething, false);
function moveSomething(e) {
switch (e.keyCode) {
case 37:
//left
if (direction != 'r') {
direction = 'l';
}
break;
case 38:
//up
if (direction != 'b') {
direction = 't';
}
break;
case 39:
//right
if (direction != 'l') {
direction = 'r';
}
break;
case 40:
//bottom
if (direction != 't') {
direction = 'b';
}
break;
}
}
let food = {
x: Math.floor(Math.random() * 220),
y: Math.floor(Math.random() * 400),
s: 20,
draw: function() {
//this if block rounds off the x and y values
if ((this.x % 20) != 0 || (this.y % 20) != 0) {
if ((this.x % 20) != 0) {
let e = this.x % 20;
e = 20 - e;
this.x = this.x + e;
}
if ((this.y % 20) != 0) {
let e = this.y % 20;
e = 20 - e;
this.y = this.y + e;
}
};
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, this.s, this.s);
},
newfood: function() {
this.x = Math.floor(Math.random() * 220);
this.y = Math.floor(Math.random() * 400);
this.draw();
}
}
const snake = {
s: 20,
draw: function(x, y) {
ctx.fillStyle = "green";
ctx.fillRect(this.s * x, this.s * y, this.s, this.s);
ctx.strokeStyle = "black";
ctx.strokeRect(this.s * x, this.s * y, this.s, this.s);
},
snakeInit: function() {
for (let i = 8; i >= 4; i--) {
size.push({
x: i,
y: 6
})
}
},
callDraw: function() {
for (let i = 0; i < size.length; i++) {
this.draw(size[i].x, size[i].y);
}
},
move: function() {
var snakeX = size[0].x;
var snakeY = size[0].y;
if (direction == 'r') {
snakeX++;
} else if (direction == 'l') {
snakeX--;
} else if (direction == 't') {
snakeY--;
} else if (direction == 'b') {
snakeY++;
}
//console.log("Inside move1", speed);
if (snakeX == -1 || snakeX == cvs.width / this.s || snakeY == -1 || snakeY == Math.floor(cvs.height / this.s) || this.checkSelfCollision(snakeX, snakeY, size)) {
ctx.clearRect(0, 0, cvs.width, cvs.height);
gameloop = clearInterval(gameloop);
return;
} else {
ctx.clearRect(0, 0, cvs.width, cvs.height);
food.draw();
}
if (snakeX * 20 == food.x && snakeY * 20 == food.y) {
var tail = {
x: snakeX,
y: snakeY
};
food.eaten();
speed += 200;
food.newfood();
} else {
var tail = size.pop();
tail.x = snakeX;
tail.y = snakeY;
}
size.unshift(tail);
for (let i = 0; i < size.length; i++) {
this.draw(size[i].x, size[i].y);
}
},
checkSelfCollision: function(x, y, arr) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].x == x && arr[i].y == y) {
return true;
}
}
return false;
}
}
function loop() {
const cvsL = document.getElementById("snakes");
const ctxL = cvsL.getContext('2d');
cvs = cvsL;
ctx = ctxL;
snake.snakeInit();
snake.callDraw();
food.draw();
gameloop = setInterval(function() {
snake.move();
}, speed);
}
<body onload="loop();">
<canvas id="snakes" width=240px height=420px style="border: 1px solid black;display: block;"></canvas>
</body>
I need help to make points every time the snake eats a piece of food. I am coding in Brackets.
I've watched The Coding Train and I was thinking the problem was that he was using p5.js when I was using Brackets. Could somebody please answer my question. Thank you! The link for the video that I have watched is: https://www.youtube.com/watch?v=AaGK-fj-BAM.
function Snake() { <- Javascript
this.x = 0;
this.y = 0;
this.xspeed = 1;
this.yspeed = 0;
this.total = 0;
this.tail = [];
this.eat = function(pos) {
var d = dist(this.x, this.y, pos.x, pos.y);
if (d < 1) {
this.total++;
return true;
} else {
return false;
}
}
this.dir = function(x, y) {
this.xspeed = x;
this.yspeed = y;
}
this.death = function() {
for (var i = 0; i < this.tail.length; i++) {
var pos = this.tail[i];
var d = dist(this.x, this.y, pos.x, pos.y);
if (d < 1) {
alert(' BAD LUCK ');
this.total = 0;
this.tail = [];
point.counterReset;
}
}
}
this.update = function() {
for (var i = 0; i < this.tail.length - 1; i++) {
this.tail[i] = this.tail[i + 1];
}
if (this.total >= 1) {
this.tail[this.total - 1] = createVector(this.x, this.y);
}
this.x = this.x + this.xspeed * scl;
this.y = this.y + this.yspeed * scl;
this.x = constrain(this.x, 0, width - scl);
this.y = constrain(this.y, 0, height - scl);
}
this.show = function() {
fill(89, 152, 47);
for (var i = 0; i < this.tail.length; i++) {
rect(this.tail[i].x, this.tail[i].y, scl, scl);
}
rect(this.x, this.y, scl, scl);
}
}
var s;
var point = 1;
var scl = 20;
var food;
var fool;
function setup() {
createCanvas(600,600);
s = new Snake();
frameRate(10);
pickLocation();
}
function pickLocation() {
var cols = floor(width / scl);
var rows = floor(height / scl);
food = createVector(floor(random(cols)), floor(random(rows)));
food.mult(scl);
fool = createVector(floor(random(cols)), floor(random(rows)));
fool.mult(scl);
}
function draw() {
background(120,6,6);
if (s.eat(food)) {
pickLocation();
point.total++;
if (Math.random() * 100 < 45) {
let name = prompt("What is 2 ÷ 4?");
alert ("Yes it is, " + name);
}
else if (Math.random() * 100 < 45) {
let name = prompt("What is the intergration of X²?");
alert ("Yes it is, " + name);
}
else if (Math.random() * 100 < 50) {
let name = prompt("What is Mass x Acceleration?");
alert ("Yes it is, " + name);
}
else if (Math.random() * 100 < 50) {
let name = prompt("How many elements are in the Periodic Table?");
alert ("Yes it is, " + name);
}
else if (Math.random() * 100 < 50 ) {
let name = prompt("What is 336 ÷ 84?");
alert ("Yes it is, " + name);
}
else if (Math.random() * 100 < 50 ) {
let name = prompt("(VERY RARE)Who was the captain of The First Fleet?");
alert ("Yes it is, " + name);
}
}
if (s.eat(fool)) {
pickLocation();
point.total++;
if (Math.random() * 100 < 45) {
let name = prompt("What is 2 ÷ 4?");
alert ("Yes it is, " + name);
}
else if (Math.random() * 100 < 45) {
let name = prompt("What is the intergration of X²?");
alert ("Yes it is, " + name);
}
else if (Math.random() * 100 < 50) {
let name = prompt("What is Mass x Acceleration?");
alert ("Yes it is, " + name);
}
else if (Math.random() * 100 < 50 ) {
let name = prompt("What is 336 ÷ 84?");
alert ("Yes it is, " + name);
}
else if (Math.random() * 100 < 50) {
let name = prompt("How many elements are in the Periodic Table?");
alert ("Yes it is, " + name);
}
else if (Math.random() * 100 < 50 ) {
let name = prompt("(VERY RARE)Who was the captain of The First Fleet?");
alert ("Yes it is, " + name);
}
}
s.death();
s.update();
s.show();
fill(255, 0, 100);
rect(food.x, food.y, scl, scl);
fill(255, 0, 100);
rect(fool.x, fool.y, scl, scl);
}
document.write("Score: " + point);
point.update();
point.show();
function keyPressed() {
if (keyCode === UP_ARROW) {
s.dir(0, -1);
} else if (keyCode === DOWN_ARROW) {
s.dir(0, 1);
} else if (keyCode === RIGHT_ARROW) {
s.dir(1, 0);
} else if (keyCode === LEFT_ARROW) {
s.dir(-1, 0)
}
}
html, body { <- css
margin: 0;
padding: 0;
background-color: #40E0D0;
}
canvas {
display: block;
}
<!DOCTYPE html> <-html
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
<title>Snake Game</title>
</head>
<body>
<script src="sketch.js"></script>
<script src="snake.js"></script>
<script src="skin.js"></script>
</body>
</html>
I am counting points when snake eats apple with that code
if (cell.x === apple.x && cell.y === apple.y) snake.maxCells++;
And my snake-game:
var canvas = document.getElementById('game');
var context = canvas.getContext('2d');
var grid = 16;
var count = 0;
var snake = {
x: 160,
y: 160,
dx: grid,
dy: 0,
cells: [], //tail
maxCells: 4
};
var apple = {
x: 320,
y: 320
};
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
function loop() {
requestAnimationFrame(loop);
if (++count < 1) {
return;
}
count = -15; // speed
context.clearRect(0,0,canvas.width,canvas.height);
snake.x += snake.dx;
snake.y += snake.dy;
if (snake.x < 0) {
snake.x = canvas.width - grid;
}
else if (snake.x >= canvas.width) {
snake.x = 0;
}
if (snake.y < 0) {
snake.y = canvas.height - grid;
}
else if (snake.y >= canvas.height) {
snake.y = 0;
}
snake.cells.unshift({x: snake.x, y: snake.y});
if (snake.cells.length > snake.maxCells) {
snake.cells.pop();
}
context.fillStyle = 'red';
context.fillRect(apple.x, apple.y, grid-1, grid-1);
context.fillStyle = 'green';
snake.cells.forEach(function(cell, index) {
context.fillRect(cell.x, cell.y, grid-1, grid-1);
if (cell.x === apple.x && cell.y === apple.y) { // if our snake eat red apple, so you count points
// and adding one green cell to snake
snake.maxCells++;
// Drawing new apple
apple.x = getRandomInt(0, 25) * grid;
apple.y = getRandomInt(0, 25) * grid;
}
for (var i = index + 1; i < snake.cells.length; i++) {
if (cell.x === snake.cells[i].x && cell.y === snake.cells[i].y) {
snake.x = 160;
snake.y = 160;
snake.cells = [];
snake.maxCells = 4;
snake.dx = grid;
snake.dy = 0;
apple.x = getRandomInt(0, 25) * grid;
apple.y = getRandomInt(0, 25) * grid;
}
}
});
}
document.addEventListener('keydown', function(e) {
if (e.which === 37 && snake.dx === 0) {
snake.dx = -grid;
snake.dy = 0;
}
else if (e.which === 38 && snake.dy === 0) {
snake.dy = -grid;
snake.dx = 0;
}
else if (e.which === 39 && snake.dx === 0) {
snake.dx = grid;
snake.dy = 0;
}
else if (e.which === 40 && snake.dy === 0) {
snake.dy = grid;
snake.dx = 0;
}
});
requestAnimationFrame(loop);
html, body {
height: 100%;
margin: 0;
}
body {
background: black;
display: flex;
align-items: center;
justify-content: center;
}
canvas {
border: 1px solid white;
}
<!DOCTYPE html>
<html>
<body>
<canvas width="400" height="400" id="game"></canvas>
</body>
</html>
My raycaster's sprites keep blinking, and I think it has to do with ZBuffer.
Using this for reference: https://lodev.org/cgtutor/index.html
I have done plenty of research and can't find any JS raycasters that answer my problem. By sprites, I mean billboarded images, and when you move they blink like an atari game that is being pushed to its limits.
Here is the project on JSFiddle: https://jsfiddle.net/Vakore/bsvx1m26/
Here is where I am using ZBuffer:
for (var stripe = drawStartX; stripe < drawEndX; stripe++) {
var texX =
floor((256 * (stripe - (-spriteWidth / 2 + spriteScreenX)) * texWidth) / spriteWidth) / 256;
// the conditions in the if are:
// 1) it's in front of camera plane so you don't see things behind you
// 2) it's on the screen (left)
// 3) it's on the screen (right)
// 4) ZBuffer, with perpendicular distance
var ruffer = 0;
if (sprite[i][2] == "barrel") {
ruffer = 576 - 64;
}
if (sprite[i][2] == "pillar") {
ruffer = 576;
}
if (sprite[i][2] == "greenlight") {
ruffer = 576 + 64;
}
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[stripe]
) {
can.drawImage(
document.getElementById(sprite[i][2]),
texX + ruffer, 0, 1, texHeight, stripe, drawStartY, 1, drawEndY - drawStartY
);
}
}
ZBuffer was declared and assigned an array value earlier in the code.
var keys = {
"a": false,
"s": false,
"d": false,
"A": false,
"S": false,
"D": false,
"W": false,
"Z": false,
"X": false,
"C": false,
"ENTER": false
};
document.addEventListener("keydown", function(e) {
e.preventDefault();
if (e.keyCode === 16)
keys["SHIFT"] = true;
if (e.keyCode === 37)
keys["a"] = true;
if (e.keyCode === 39)
keys["d"] = true;
if (e.keyCode === 65)
keys["A"] = true;
if (e.keyCode === 68)
keys["D"] = true;
if (e.keyCode === 83)
keys["S"] = true;
if (e.keyCode === 87)
keys["W"] = true;
if (e.keyCode === 40)
keys["S"] = true;
if (e.keyCode === 38)
keys["W"] = true;
if (e.keyCode === 67)
keys["C"] = true;
if (e.keyCode === 88)
keys["X"] = true;
if (e.keyCode === 90)
keys["Z"] = true;
if (e.keyCode === 13)
keys["ENTER"] = true;
})
document.addEventListener("keyup", function(e) {
e.preventDefault();
if (e.keyCode === 16)
keys["SHIFT"] = false;
if (e.keyCode === 37)
keys["a"] = false;
if (e.keyCode === 39)
keys["d"] = false;
if (e.keyCode === 65)
keys["A"] = false;
if (e.keyCode === 68)
keys["D"] = false;
if (e.keyCode === 83)
keys["S"] = false;
if (e.keyCode === 87)
keys["W"] = false;
if (e.keyCode === 40)
keys["S"] = false;
if (e.keyCode === 38)
keys["W"] = false;
if (e.keyCode === 67)
keys["C"] = false;
if (e.keyCode === 88)
keys["X"] = false;
if (e.keyCode === 90)
keys["Z"] = false;
})
/*
Basic functions(ease of access)
*/
var canvas = document.getElementById("canva");
var can = canvas.getContext("2d");
canvas.requestPointerLock = canvas.requestPointerLock || canvas.mozRequestPointerLock;
document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock;
canvas.onclick = function() {
canvas.requestPointerLock();
clicked = true;
};
var mouseX = 0;
var mouseMove = function(e) {
mouseX += e.movementX;
};
var rect = function(x, y, w, h) {
can.fillRect(x, y, w, h);
};
var arc = function(x, y, r, start, stop) {
can.beginPath();
can.arc(x, y, r, start, stop);
can.fill();
};
var circle = function(x, y, r) {
arc(x, y, r, 0, 360);
};
var fill = function(r, g, b, a) {
if (a === undefined) {
a = 1;
}
can.fillStyle = "rgb(" + r + "," + g + "," + b + "," + a + ")";
};
var font = function(siz) {
can.font = siz;
};
var textAlign = function(ali) {
can.textAlign = ali;
};
var text = function(txt, x, y, w, h) {
can.fillText(txt, x, y, w, h);
};
var quad = function(x1, y1, x2, y2, x3, y3, x4, y4) {
can.beginPath();
can.moveTo(x1, y1);
can.lineTo(x2, y2);
can.lineTo(x3, y3);
can.lineTo(x4, y4);
can.closePath();
can.fill();
};
var random = function(min, max) {
return round(Math.random() * (max - min)) + min;
};
var translate = function(x, y) {
can.save();
can.translate(x, y);
};
var scale = function(w, h) {
can.scale(w, h);
}
var trestore = function() {
can.restore();
};
var floor = function(num) {
return Math.floor(num);
};
var sqrt = function(num) {
return Math.sqrt(num);
};
var abs = function(num) {
return Math.abs(num);
};
var color = function(r, g, b, a) {
return [r, g, b, a];
};
var dist = function() {
return true;
};
var cos = function(num) {
return Math.cos(num);
}
var sin = function(num) {
return Math.sin(num);
}
var dist = function(x1, y1, x2, y2) {
dx = x1 - x2;
dy = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
};
//Begin the raycaster
var scaler = 3; //This reduces the resolution so that it can display large rooms without lagging
var w = 640 / scaler;
var h = 480 / scaler;
var texWidth = 64;
var texHeight = 64;
var rotSpeed = 0.1;
var moveSpeed = 0.1;
var worldMap = [
"1111111111111111111111111111111111111111",
"1001000001234567800000000000000000000001",
"1001000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1001000000000000000000000000000000000001",
"1001000000000000000000000000222200000001",
"1001000000000000000000000000200200000001",
"1111000000000000000000000000200200000001",
"1000000000000000000000000033300333000001",
"8008000000000000000000000000000000000001",
"8000800000000000000000000033300333000001",
"8000080000000000000000000000300300000001",
"8000008000000000000000000000333300000001",
"8888888000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1666006660000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1666666660000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000040444444444444400000000000000001",
"1000000040000000000000400000000000000001",
"1000000040000000000000400000000000000001",
"1000000040000000000000400000000000000001",
"1000000044444444444444400000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000007775777000000000000000001",
"1000000000000007000007000000000000000001",
"1000000000000005000005000000000000000001",
"1000000000000007000007000000000000000001",
"1000000000000000000007000000000000000001",
"1111111111111117775777111111111111111111",
];
try {
var ZBuffer = [];
var sprite = [
[5, 5, "barrel", 0], //Y and X are reversed
[6.5, 2.5, "pillar", 1],
[6.5, 1.5, "pillar", 1.5],
[8, 6, "greenlight", 2],
];
var posX = 2,
posY = 2,
dirX = -1,
dirY = 0,
planeX = 0,
planeY = 0.66; //The 2d raycaster version of the camera plane
} catch (e) {
alert(e);
}
var castRays = function() {
for (var x = 0; x < w; x++) {
var cameraX = 2 * x / (w) - 1; //x-coordinate in camera space
var rayDirX = dirX + planeX * cameraX;
var rayDirY = dirY + planeY * cameraX;
//which box of the map we're in
var mapX = floor(posX);
var mapY = floor(posY);
//length of ray from current position to next x or y-side
var sideDistX;
var sideDistY;
//length of ray from one x or y-side to next x or y-side
var deltaDistX = sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX));
var deltaDistY = sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
var perpWallDist = 0;
//what direction to step in x or y-direction (either +1 or -1)
var stepX = 0;
var stepY = 0;
var hit = 0; //was there a wall hit?
var side = 0; //was a NS or a EW wall hit?
//calculate step and initial sideDist
if (rayDirX < 0) {
stepX = -1;
sideDistX = (posX - mapX) * deltaDistX;
} else {
stepX = 1;
sideDistX = (mapX + 1.0 - posX) * deltaDistX;
}
if (rayDirY < 0) {
stepY = -1;
sideDistY = (posY - mapY) * deltaDistY;
} else {
stepY = 1;
sideDistY = (mapY + 1.0 - posY) * deltaDistY;
}
while (hit == 0) {
//jump to next map square, OR in x-direction, OR in y-direction
if (sideDistX < sideDistY) {
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
} else {
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
//Check if ray has hit a wall
if (worldMap[mapX][mapY] == 0) {}
if (worldMap[mapX][mapY] != 0) {
hit = 1;
}
} //end of while loop
//Calculate distance of perpendicular ray (Euclidean distance will give fisheye effect!)
if (side == 0) {
perpWallDist = (mapX - posX + (1 - stepX) / 2) / rayDirX;
} else {
perpWallDist = (mapY - posY + (1 - stepY) / 2) / rayDirY;
}
//Calculate height of line to draw on screen
var lineHeight = abs(floor(h / perpWallDist));
//calculate lowest and highest pixel to fill in current stripe
var drawStart = -lineHeight / 2 + h / 2;
//if(drawStart < 0) {drawStart = 0;}
var drawEnd = lineHeight / 2 + h / 2;
//if(drawEnd >= h) {drawEnd = h - 1;}
//texturing calculations
//var texNum = worldMap[mapX][mapY] - 1; //1 subtracted from it so that texture 0 can be used!
//calculate value of wallX
var wallX = 0; //where exactly the wall was hit
if (side == 0) {
wallX = posY + perpWallDist * rayDirY;
} else {
wallX = posX + perpWallDist * rayDirX;
}
wallX -= floor((wallX));
//x coordinate on the texture
var texX = floor(wallX * (texWidth));
if (side == 0 && rayDirX > 0) {
texX = texWidth - texX - 1;
}
if (side == 1 && rayDirY < 0) {
texX = texWidth - texX - 1;
}
//Untextured variant
/*var currentColor = color(255, 255, 255);
if (worldMap[mapX][mapY] == "1") {currentColor = color(150, 150, 150);}
if (worldMap[mapX][mapY] == "2") {currentColor = color(255, 255, 255);}
if (worldMap[mapX][mapY] == "3") {currentColor = color(255,0,0);}
if (worldMap[mapX][mapY] == "4") {currentColor = color(0,0,255);}
if (worldMap[mapX][mapY] == "5") {currentColor = color(0,255,0);}
if (worldMap[mapX][mapY] == "6") {currentColor = color(255,0,255);}
if (worldMap[mapX][mapY]=="7") {currentColor = color(0,255,255);}
if (side === 1) {currentColor = [currentColor[0] - 40, currentColor[1] - 40, currentColor[2] - 40];}
//if (dist(mapX, mapY, px, pz) > 10) {continue;}
fill(currentColor[0],currentColor[1],currentColor[2]);
rect(x, drawStart, 1, drawEnd - drawStart);*/
var currentImg = "bluestone";
if (worldMap[mapX][mapY] == "1") {
currentImg = "bluestone";
}
if (worldMap[mapX][mapY] == "2") {
currentImg = "greystone";
}
if (worldMap[mapX][mapY] == "3") {
currentImg = "wood";
}
if (worldMap[mapX][mapY] == "4") {
currentImg = "colorstone";
}
if (worldMap[mapX][mapY] == "5") {
currentImg = "eagle";
}
if (worldMap[mapX][mapY] == "6") {
currentImg = "mossy";
}
if (worldMap[mapX][mapY] == "7") {
currentImg = "redbrick";
}
if (worldMap[mapX][mapY] == "8") {
currentImg = "purplestone";
}
//if (worldMap[mapX][mapY] =="a") {currentImg = "barrel";}
//if (dist(mapX, mapY, posX, posY) > 20) {continue;}
can.drawImage(document.getElementById(currentImg), texX + ((worldMap[mapX][mapY] - 1) * 64), 0, 1, texHeight, x, drawStart, 1, drawEnd - drawStart);
//fill(255, 0, 0);rect(x, drawStart, 1, drawEnd - drawStart);
if (side === 1) {
fill(0, 0, 0, 0.3);
rect(x, drawStart, 1, drawEnd - drawStart);
}
//Set the ZBuffer
ZBuffer[x] = perpWallDist;
//FLOOR CASTING(not using till figure out how to do without lag)
var floorXWall = 0,
floorYWall = 0;
if (side == 0 && rayDirX > 0) {
floorXWall = mapX;
floorYWall = mapY + wallX;
} else if (side == 0 && rayDirX < 0) {
floorXWall = mapX + 1.0;
floorYWall = mapY + wallX;
} else if (side == 1 && rayDirY > 0) {
floorXWall = mapX + wallX;
floorYWall = mapY;
} else {
floorXWall = mapX + wallX;
floorYWall = mapY + 1.0;
}
var distWall, distPlayer, currentDist;
distWall = perpWallDist;
distPlayer = 0.0;
for (var y = drawEnd + 1; y < h; y++) {
//if (dist(mapX, mapY, posX, posY) > 5) {continue;}
currentDist = h / (2.0 * y - h); //you could make a small lookup table for this instead
var weight = (currentDist - distPlayer) / (distWall - distPlayer);
var currentFloorX = weight * floorXWall + (1.0 - weight) * posX;
var currentFloorY = weight * floorYWall + (1.0 - weight) * posY;
var floorTexX, floorTexY;
floorTexX = floor(currentFloorX * texWidth) % texWidth;
floorTexY = floor(currentFloorY * texHeight) % texHeight;
//OPTIMIZE FOR LESS LAG!!!!!!!!!! (search
//FLOOR
//can.drawImage(document.getElementById("greystone"), floorTexX, floorTexY, texWidth, texHeight, x, y, texWidth, texHeight);
//CEILING
//can.drawImage(document.getElementById("wood"), floorTexX, floorTexY, texWidth, 1, x, h - y, texWidth, 1);
} //End of the 'y' loop
} //end the loop
//SPRITE CASTING
//Sort sprites
sprite.sort(function(a, b) {
return b[3] - a[3];
}); //Sort the depth of each sprite
//Draw sprites
for (var i = 0; i < sprite.length; i++) {
var spriteX = sprite[i][0] - posX;
var spriteY = sprite[i][1] - posY;
sprite[i][3] = abs((posX - sprite[i][0]) - (posY - sprite[i][1]));
var invDet = 1.0 / (planeX * dirY - dirX * planeY);
var transformX = invDet * (dirY * spriteX - dirX * spriteY);
var transformY = invDet * (-planeY * spriteX + planeX * spriteY); //this is actually the depth inside the screen, that what Z is in 3D(Thanks lodev.org for explanation!)
var spriteScreenX = floor((w / 2) * (1 + transformX / transformY));
//calculate height of the sprite on screen
var spriteHeight = abs(floor(h / (transformY))); //using "transformY" instead of the real distance prevents fisheye
//calculate lowest and highest pixel to fill in current stripe
var drawStartY = -spriteHeight / 2 + h / 2;
//if (drawStartY < 0) {drawStartY = 0;} Don't need THIS
var drawEndY = spriteHeight / 2 + h / 2;
//if (drawEndY >= h) {drawEndY = h - 1;} Or THIS
//calculate width of the sprite
var spriteWidth = abs(floor(h / (transformY)));
var drawStartX = -spriteWidth / 2 + spriteScreenX;
//if (drawStartX < 0) {drawStartX = 0;} Nor this
var drawEndX = spriteWidth / 2 + spriteScreenX;
//if (drawEndX >= w) {drawEndX = w - 1;} Nor dis
//loop through every vertical stripe of the sprite on screen
for (var stripe = drawStartX; stripe < drawEndX; stripe++) {
var texX = floor(256 * (stripe - (-spriteWidth / 2 + spriteScreenX)) * texWidth / spriteWidth) / 256;
//the conditions in the if are:
//1) it's in front of camera plane so you don't see things behind you
//2) it's on the screen (left)
//3) it's on the screen (right)
//4) ZBuffer, with perpendicular distance
var ruffer = 0;
if (sprite[i][2] == "barrel") {
ruffer = 576 - 64;
}
if (sprite[i][2] == "pillar") {
ruffer = 576;
}
if (sprite[i][2] == "greenlight") {
ruffer = 576 + 64;
}
if (transformY > 0 && stripe > 0 && stripe < w && transformY < ZBuffer[stripe]) {
can.drawImage(document.getElementById(sprite[i][2]), texX + ruffer, 0, 1, texHeight, stripe, drawStartY, 1, drawEndY - drawStartY);
}
}
} //End of 'i' loop
//Bottom of dat
if (keys["W"]) {
if (worldMap[floor(posX + dirX * moveSpeed)][floor(posY)] == false) {
posX += dirX * moveSpeed
};
if (worldMap[floor(posX)][floor(posY + dirY * moveSpeed)] == false) {
posY += dirY * moveSpeed
};
}
//move backwards if no wall behind you
if (keys["S"]) {
if (worldMap[floor(posX - dirX * moveSpeed)][floor(posY)] == false) {
posX -= dirX * moveSpeed;
}
if (worldMap[floor(posX)][floor(posY - dirY * moveSpeed)] == false) {
posY -= dirY * moveSpeed;
}
}
if (keys["D"]) {
if (worldMap[floor(posX + planeX * moveSpeed)][floor(posY)] == false) {
posX += planeX * moveSpeed
};
if (worldMap[floor(posX)][floor(posY + planeY * moveSpeed)] == false) {
posY += planeY * moveSpeed
};
}
if (keys["A"]) {
if (worldMap[floor(posX - planeX * moveSpeed)][floor(posY)] == false) {
posX -= planeX * moveSpeed;
}
if (worldMap[floor(posX)][floor(posY - planeY * moveSpeed)] == false) {
posY -= planeY * moveSpeed;
}
}
//rotate to the right
mouseX = -mouseX / 75;
var oldDirX = dirX;
dirX = dirX * cos(mouseX) - dirY * sin(mouseX);
dirY = oldDirX * sin(mouseX) + dirY * cos(mouseX);
var oldPlaneX = planeX;
planeX = planeX * cos(mouseX) - planeY * sin(mouseX);
planeY = oldPlaneX * sin(mouseX) + planeY * cos(mouseX);
mouseX = 0;
if (keys["d"]) {
//both camera direction and camera plane must be rotated
var oldDirX = dirX;
dirX = dirX * cos(-rotSpeed) - dirY * sin(-rotSpeed);
dirY = oldDirX * sin(-rotSpeed) + dirY * cos(-rotSpeed);
var oldPlaneX = planeX;
planeX = planeX * cos(-rotSpeed) - planeY * sin(-rotSpeed);
planeY = oldPlaneX * sin(-rotSpeed) + planeY * cos(-rotSpeed);
}
//rotate to the left
if (keys["a"]) {
//both camera direction and camera plane must be rotated
var oldDirX = dirX;
dirX = dirX * cos(rotSpeed) - dirY * sin(rotSpeed);
dirY = oldDirX * sin(rotSpeed) + dirY * cos(rotSpeed);
var oldPlaneX = planeX;
planeX = planeX * cos(rotSpeed) - planeY * sin(rotSpeed);
planeY = oldPlaneX * sin(rotSpeed) + planeY * cos(rotSpeed);
}
};
var run = function() {
try {
if (document.pointerLockElement === canvas || document.mozPointerLockElement === canvas) {
document.addEventListener("mousemove", mouseMove, false);
} else {
document.removeEventListener("mousemove", mouseMove, false);
} //Mouse stuff
translate(0, 0);
scale(scaler, scaler);
fill(40, 40, 40);
rect(0, 0, w, h / 2);
fill(80, 80, 80);
rect(0, h / 2, w, h / 2);
if (keys["C"]) {
rotSpeed = 0.04;
} else {
rotSpeed = 0.1;
}
/*if (keys["W"]) {fill(255, 0, 0);}
if (keys["A"]) {fill(0, 255, 0);}
if (keys["S"]) {fill(0, 0, 255);}
if (keys["D"]) {fill(255, 0, 255);}
if (keys["X"]) {fill(255, 255, 0);}
if (keys["Z"]) {fill(0, 255, 255);}*/
castRays();
trestore();
requestAnimationFrame(run);
} catch (e) {
alert(e);
}
};
run();
<html>
<title>Jailbreak</title>
<div class="center">
<canvas width="640" height="480" id="canva"></canvas>
<br>
<img id="barrel" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="pillar" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="greenlight" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="bluestone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="greystone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="wood" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="colorstone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="eagle" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="mossy" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="redbrick" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="purplestone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
</div>
</html>
UPDATE: I tried changing
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[stripe]//Pay attention to this line
) {
to:
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY > ZBuffer[stripe]//Now look
) {
Which basically reverses the check to see if it is behind a wall. Surprisingly, the blinking continues.
The answer was pretty simple. It was because stripe is not an even value, and if you were to have an array like this: var thisArray = [1, 2, 3, 4]; and you were to try and grab var money = thisArray[1.5]; the value undefined would be returned.
So I changed this:
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[stripe]
) {
To this:
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[Math.round(stripe)]
) {
Math.floor also works, since it also returns an integer value.
I am working on a college project that requires me to build a 2D game in javascript. A problem that I'm having at the moment is that it cannot read the 'addEventListener'. This error has caused my game to not work completely.
document.getElementById('restart').addEventListener('click', startGame);
Here is the full code that I have used. The error is down the very bottom.
(function()
{
//Define variables
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var player, score, stop, ticker;
var ground = [], water = [], enemies = [], environment = [];
//Platform variables
var platformHeight, platformLength, gapLength;
var platformWidth = 32;
var platformBase = canvas.height - platformWidth;
var platformSpacer = 64;
//Randomly generates a number
function random(low, high)
{
return Math.floor(Math.random() * (high - low + 1) + low);
}
//Bounds a number
function bound(num, low, high)
{
return Math.max(Math.min(num, high), low);
}
//Loads all of the assets
var assetLoader = (function()
{
this.imgs = {
'bg' : 'Images/bg.png',
'sky' : 'Images/sky.png',
'backdrop' : 'Images/backdrop.png',
'backdrop2' : 'Images/backdrop_ground.png',
'grass' : 'Images/grass.png',
'avatar_normal' : 'Images/normal_walk.png',
'water' : 'imgs/water.png',
'grass1' : 'imgs/grassMid1.png',
'grass2' : 'imgs/grassMid2.png',
'bridge' : 'imgs/bridge.png',
'plant' : 'imgs/plant.png',
'bush1' : 'imgs/bush1.png',
'bush2' : 'imgs/bush2.png',
'cliff' : 'imgs/grassCliffRight.png',
'spikes' : 'imgs/spikes.png',
'box' : 'imgs/boxCoin.png',
'slime' : 'imgs/slime.png'
};
var assetsLoaded = 0; //How many assets have been loaded
var numImgs = Object.keys(this.imgs).length; //Total number of image assets
this.totalAssest = numImgs; //Total number of assets
function assetLoaded(dic, name)
{
if(this[dic][name].status !== 'loading')
{
return;
}
this[dic][name].status = 'loaded';
assetsLoaded++;
if(assetsLoaded === this.totalAssest && typeof this.finished === 'function')
{
this.finished();
}
}
this.downloadAll = function()
{
var _this = this;
var src;
for (var img in this.imgs)
{
if (this.imgs.hasOwnProperty(img))
{
src = this.imgs[img];
(function(_this, img)
{
_this.imgs[img] = new Image();
_this.imgs[img].status = 'loading';
_this.imgs[img].name = img;
_this.imgs[img].onload = function() {assetLoaded.call(_this, 'imgs', img)};
_this.imgs[img].src = src;
})(_this, img);
}
}
}
return{
imgs: this.imgs,
totalAssest: this.totalAssest,
downloadAll: this.downloadAll
};
})();
assetLoader.finished = function()
{
startGame();
}
function SpriteSheet(path, frameWidth, frameHeight)
{
this.image = new Image();
this.frameWidth = frameWidth;
this.frameHeight = frameHeight;
var self = this;
this.image.onload = function()
{
self.framesPerRow = Math.floor(self.image.width / self.frameWidth);
};
this.image.src = path;
}
function Animation(spritesheet, frameSpeed, startFrame, endFrame)
{
var animationSequence = [];
var currentFrame = 0;
var counter = 0;
for (var frameNumber = startFrame; frameNumber <= endFrame; frameNumber++)
{
animationSequence.push(frameNumber);
}
this.update = function()
{
if (counter == (frameSpeed - 1))
{
currentFrame = (currentFrame + 1) % animationSequence.length;
}
counter = (counter + 1) % frameSpeed;
};
this.draw = function(x, y)
{
var row = Math.floor(animationSequence[currentFrame] / spritesheet.framesPerRow);
var col = Math.floor(animationSequence[currentFrame] % spritesheet.framesPerRow);
ctx.drawImage
(
spritesheet.image,
col * spritesheet.frameWidth, row * spritesheet.frameHeight,
spritesheet.frameWidth, spritesheet.frameHeight,
x, y,
spritesheet.frameWidth, spritesheet.frameHeight);
};
}
var background = (function()
{
var sky = {};
var backdrop = {};
var backdrop2 = {};
this.draw = function()
{
ctx.drawImage(assetLoader.imgs.bg, 0, 0);
sky.x -= sky.speed;
backdrop.x -= backdrop.speed;
backdrop2.x -= backdrop2.speed;
ctx.drawImage(assetLoader.imgs.sky, sky.x, sky.y);
ctx.drawImage(assetLoader.imgs.sky, sky.x + canvas.width, sky.y);
ctx.drawImage(assetLoader.imgs.backdrop, backdrop.x, backdrop.y);
ctx.drawImage(assetLoader.imgs.backdrop, backdrop.x + canvas.width, backdrop.y);
ctx.drawImage(assetLoader.imgs.backdrop2, backdrop2.x, backdrop2.y);
ctx.drawImage(assetLoader.imgs.backdrop2, backdrop2.x + canvas.width, backdrop2.y);
if (sky.x + assetLoader.imgs.sky.width <= 0)
{
sky.x = 0;
}
if (backdrop.x + assetLoader.imgs.backdrop.width <= 0)
{
backdrop.x = 0;
}
if (backdrop2.x + assetLoader.imgs.backdrop2.width <= 0)
{
backdrop2.x = 0;
}
};
this.reset = function()
{
sky.x = 0;
sky.y = 0;
sky.speed = 0.2;
backdrop.x = 0;
backdrop.y = 0;
backdrop.speed = 0.4;
backdrop2.x = 0;
backdrop2.y = 0;
backdrop2.speed = 0.6;
}
return{
draw: this.draw,
reset: this.reset
};
})();
//A vector for 2D space
function Vector(x, y, dx, dy)
{
// position
this.x = x || 0;
this.y = y || 0;
// direction
this.dx = dx || 0;
this.dy = dy || 0;
}
//Advances the vector's position
Vector.prototype.advance = function()
{
this.x += this.dx;
this.y += this.dy;
};
//Gets the minimum distance between two vectors
Vector.prototype.minDist = function(vec)
{
var minDist = Infinity;
var max = Math.max(Math.abs(this.dx), Math.abs(this.dy),Math.abs(vec.dx), Math.abs(vec.dy));
var slice = 1 / max;
var x, y, distSquared;
// get the middle of each vector
var vec1 = {}, vec2 = {};
vec1.x = this.x + this.width/2;
vec1.y = this.y + this.height/2;
vec2.x = vec.x + vec.width/2;
vec2.y = vec.y + vec.height/2;
for(var percent = 0; percent < 1; percent += slice)
{
x = (vec1.x + this.dx * percent) - (vec2.x + vec.dx * percent);
y = (vec1.y + this.dy * percent) - (vec2.y + vec.dy * percent);
distSquared = x * x + y * y;
minDist = Math.min(minDist, distSquared);
}
return Math.sqrt(minDist);
};
//The player object
var player = (function(player)
{
//Player properties
player.width = 60;
player.height = 96;
player.speed = 6;
//Jumping
player.gravity = 1;
player.dy = 0;
player.jumpDy = -10;
player.isFalling = false;
player.isJumping = false;
//Spritesheets
player.sheet = new SpriteSheet('Images/normal_walk.png', player.width, player.height);
player.walkAnim = new Animation(player.sheet, 4, 0, 15);
player.jumpAnim = new Animation(player.sheet, 4, 15, 15);
player.fallAnim = new Animation(player.sheet, 4, 11, 11);
player.anim = player.walkAnim;
Vector.call(player, 0, 0, 0, player.dy);
var jumpCounter = 0;
player.update = function()
{
//Jump if not currently jumping or falling
if(KEY_STATUS.space && player.dy === 0 && !player.isJumping)
{
player.isJumping = true;
player.dy = player.jumpDy;
jumpCounter = 12;
}
//Jump higher if the spacebar is continually pressed
if(KEY_STATUS.space && jumpCounter)
{
player.dy = player.jumpDy;
}
jumpCounter = Math.max(jumpCounter - 1, 0);
this.advance();
//Gravity
if(player.isFalling || player.isJumping)
{
player.dy += player.gravity;
}
//Falling Animation
if(player.dy > 0)
{
player.anim = player.fallAnim;
}
// change animation is jumping
else if(player.dy < 0)
{
player.anim = player.jumpAnim;
}
else
{
player.anim = player.walkAnim;
}
player.anim.update();
};
//Draw the player's current position
player.draw = function()
{
player.anim.draw(player.x, player.y);
};
//Resets the player's position
player.reset = function()
{
player.x = 64;
player.y = 250;
};
return player;
})(Object.create(Vector.prototype));
//Sprites
function Sprite(x, y, type)
{
this.x = x;
this.y = y;
this.width = platformWidth;
this.height = platformWidth;
this.type = type;
Vector.call(this, x, y, 0, 0);
//Updating the sprites
this.update = function()
{
this.dx = -player.speed;
this.advancer();
}
//Drawing the sprites
this.draw = function()
{
ctx.save();
ctx.translate(0.5, 0.5);
ctx.drawImage(assetLoader.imgs[this.type], this.x, this.y);
ctx.restore();
}
}
Sprite.prototype = Object.create(Vector.prototype);
//Platforms
function getType()
{
var type;
switch(platformHeight)
{
case 0:
case 1:
type = Math.random() > 0.5 ? 'grass1' : 'grass2';
break;
case 2:
type = 'grass';
break;
case 3:
type = 'bridge';
break;
case 4:
type = 'box';
break;
}
if (platformLength === 1 && platformHeight < 3 && rand(0, 3) === 0)
{
type = 'cliff';
}
return type;
}
//Update and draw all ground positions
function updateGround()
{
//Animate ground
player.isFalling = true;
for(var i = 0; i < ground.length; i++)
{
ground[i].update();
ground[i].draw();
//Stop the player going through the platforms when landing
var angle;
if(player.minDist(ground[i]) <= player.height/2 + platformWidth/2 && (angle = Math.atan2(player.y - ground[i].y, player.x - ground[i].x) * 180/Math.PI) > -130 &&angle < -50)
{
player.isJumping = false;
player.isFalling = false;
player.y = ground[i].y - player.height + 5;
player.dy = 0;
}
}
//Remove the ground that has gone off screen
if(ground[0] && ground[0].x < -platformWidth)
{
ground.splice(0, 1);
}
}
//Update and draw all water positions
function updateWater()
{
//Animate water
for(var i = 0; i < water.length; i++)
{
water[i].update();
water[i].draw();
}
//Remove water that has gone off screen
if (water[0] && water[0].x < -platformWidth)
{
var w = water.splice(0, 1)[0];
w.x = water[water.length-1].x + platformWidth;
water.push(w);
}
}
//Update and draw all environment positions
function updateEnvironment()
{
//Animate environment
for(var i = 0; i < environment.length; i++)
{
environment[i].update();
environment[i].draw();
}
//R emove environment that have gone off screen
if(environment[0] && environment[0].x < -platformWidth)
{
environment.splice(0, 1);
}
}
//Update and draw all enemies position. Also check for collision against the player.
function updateEnemies()
{
//Animate enemies
for(var i = 0; i < enemies.length; i++)
{
enemies[i].update();
enemies[i].draw();
//Player ran into enemy
if(player.minDist(enemies[i]) <= player.width - platformWidth/2)
{
gameOver();
}
}
//Remove enemies that have gone off screen
if(enemies[0] && enemies[0].x < -platformWidth)
{
enemies.splice(0, 1);
}
}
//Update and draw the players position
function updatePlayer()
{
player.update();
player.draw();
//Game over
if(player.y + player.height >= canvas.height)
{
gameOver();
}
}
//Spawn new sprites off screen
function spawnSprites()
{
//Increase score
score++;
//First create a gap
if(gapLength > 0)
{
gapLength--;
}
//Then create the ground
else if(platformLength > 0)
{
var type = getType();
ground.push(new Sprite(
canvas.width + platformWidth % player.speed,
platformBase - platformHeight * platformSpacer,
type
));
platformLength--;
//Add random environment sprites
spawnEnvironmentSprites();
//Add random enemies
spawnEnemySprites();
}
//Start over
else
{
//Increase gap length every speed increase of 4
gapLength = rand(player.speed - 2, player.speed);
// only allow a ground to increase by 1
platformHeight = bound(rand(0, platformHeight + rand(0, 2)), 0, 4);
platformLength = rand(Math.floor(player.speed/2), player.speed * 4);
}
}
//Spawn new environment sprites off screen
function spawnEnvironmentSprites()
{
if(score > 40 && rand(0, 20) === 0 && platformHeight < 3)
{
if (Math.random() > 0.5)
{
environment.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer - platformWidth, 'plant'));
}
else if(platformLength > 2)
{
environment.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer - platformWidth, 'bush1'));
environment.push(new Sprite(canvas.width + platformWidth % player.speed + platformWidth, platformBase - platformHeight * platformSpacer - platformWidth, 'bush2'));
}
}
}
//Spawn new enemy sprites off screen
function spawnEnemySprites()
{
if(score > 100 && Math.random() > 0.96 && enemies.length < 3 && platformLength > 5 && (enemies.length ? canvas.width - enemies[enemies.length-1].x >= platformWidth * 3 || canvas.width - enemies[enemies.length-1].x < platformWidth : true))
{
enemies.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer - platformWidth, Math.random() > 0.5 ? 'spikes' : 'slime'));
}
}
//Game Loop
function animate()
{
if(!stop)
{
requestAnimFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
background.draw();
//Update entities
updateWater();
updateEnvironment();
updatePlayer();
updateGround();
updateEnemies();
//Draw the score
ctx.fillText('Score: ' + score + 'm', canvas.width - 140, 30);
//Spawn a new Sprite
if(ticker % Math.floor(platformWidth / player.speed) === 0)
{
spawnSprites();
}
//Increase player's speed only when player is jumping
if(ticker > (Math.floor(platformWidth / player.speed) * player.speed * 20) && player.dy !== 0)
{
player.speed = bound(++player.speed, 0, 15);
player.walkAnim.frameSpeed = Math.floor(platformWidth / player.speed) - 1;
//Reset ticker
ticker = 0;
//Spawn a platform to fill in gap created by increasing player speed
if(gapLength === 0)
{
var type = getType();
ground.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer, type));
platformLength--;
}
}
ticker++;
}
}
//Spacebar events
var KEY_CODES = {
32: 'space'
};
var KEY_STATUS = {};
for(var code in KEY_CODES)
{
if(KEY_CODES.hasOwnProperty(code))
{
KEY_STATUS[KEY_CODES[code]] = false;
}
}
document.onkeydown - function(e)
{
var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
if(KEY_CODES[keyCode])
{
e.preventDefault();
KEY_STATUS[KEY_CODES[keyCode]] = true;
}
};
document.onkeydown - function(e)
{
var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
if(KEY_CODES[keyCode])
{
e.preventDefault();
KEY_STATUS[KEY_CODES[keyCode]] = false;
}
};
//Request Animation Polyfill
var requestAnimFrame = (function()
{
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback, element)
{
window.setTimeout(callback, 1000 / 60);
};
})();
//Start the game and resets all variables and entities, spawn ground and water.
function startGame()
{
document.getElementById('game-over').style.display = 'none';
ground = [];
water = [];
environment = [];
enemies = [];
player.reset();
ticker = 0;
stop = false;
score = 0;
platformHeight = 2;
platformLength = 15;
gapLength = 0;
ctx.font = '16px arial, sans-serif';
for (var i = 0; i < 30; i++)
{
ground.push(new Sprite(i * (platformWidth-3), platformBase - platformHeight * platformSpacer, 'grass'));
}
for (i = 0; i < canvas.width / 32 + 2; i++)
{
water.push(new Sprite(i * platformWidth, platformBase, 'water'));
}
background.reset();
animate();
}
//End the game and restart
function gameOver()
{
stop = true;
document.getElementById('game-over').style.display = 'block';
}
document.getElementById('restart').addEventListener('click', startGame);
assetLoader.downloadAll();
})();