Related
I am trying to make a Snake game by canvas in JavaScript. I have completed almost all the settings but the collision check that the game will crash when the snake hit itself or the wall as it cannot insert the checkCollision method which I have defined.
<script>
//Create canvas
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let width = canvas.width;
let height = canvas.height;
let blockSize = 10;
let widthInBlocks = width / blockSize;
let heightInBlocks = height / blockSize;
let drawBorder = function () {
ctx.fillStyle = 'Black';
ctx.fillRect(0, 0, width, blockSize);
ctx.fillRect(0, height - blockSize, width, blockSize);
ctx.fillRect(0, 0, blockSize, height);
ctx.fillRect(width - blockSize, 0, blockSize, height);
};
drawBorder();
//Create score
var score = 0;
let drawScore = function () {
ctx.clearRect(10, 10, width - 20, 40);
ctx.fillStyle = 'Black';
ctx.textBaseLine = 'top';
ctx.textAlign = 'left';
ctx.font = '24px Arial';
ctx.fillText('Score : ' + score, 15, 45);
};
//Block constrcutor
const Block = function (col, row) {
this.col = col;
this.row = row;
};
Block.prototype.drawSquare = function (color) {
let x = this.col * blockSize;
let y = this.row * blockSize;
ctx.fillStyle = color;
ctx.fillRect(x, y, blockSize, blockSize);
};
//Create food
const circle = function (x, y, radius, color, fill) {
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
if (fill) {
ctx.fill();
} else {
ctx.stroke();
}
};
Block.prototype.drawCircle = function (color) {
let x = this.col * blockSize + blockSize / 2;
let y = this.row * blockSize + blockSize / 2;
ctx.fillStyle = color;
circle(x, y, blockSize / 2, color, true);
};
let Apple = function () {
this.position = new Block(10, 10);
};
Apple.prototype.draw = function () {
this.position.drawCircle(colorList[Math.floor(Math.random() * 7)]);
};
Apple.prototype.move = function () {
let randomCol = Math.floor(Math.random() * (widthInBlocks - 2)) + 1;
let randomRow = Math.floor(Math.random() * (heightInBlocks - 2)) + 1;
if (randomCol !== this.segments && randomRow !== this.segments) {
this.position = new Block(randomCol, randomRow);
}
};
//Setting keycode
const directions = {
37:'left',
38:'up',
39:'right',
40:'down'
};
$('body').keydown(function (event) {
let newDirection = directions[event.keyCode];
if (newDirection !== undefined) {
snake.setDirection(newDirection);
}
});
//Create snake
var colorList = ['Blue','Green','Red','Gold','Silver','Purple','Cyan']
var Snake = function () {
this.segments = [
new Block(7,5),
new Block(6,5),
new Block(5,5)
];
this.direction = 'right';
this.nextDirection = 'right';
};
Snake.prototype.draw = function () {
for (let i = 0; i < this.segments.length; i ++) {
this.segments[i].drawSquare(colorList[Math.floor(Math.random() * 7)]);
};
};
//Setting moving directions
Snake.prototype.move = function () {
let head = this.segments[0];
let newHead;
this.direction = this.nextDirection;
if (this.direction === 'right'){
newHead = new Block(head.col + 1, head.row);
} else if (this.direction === 'left') {
newHead = new Block(head.col - 1, head.row);
} else if (this.direction === 'up') {
newHead = new Block(head.col, head.row - 1);
} else if (this.direction === 'down') {
newHead = new Block(head.col, head.row + 1)
}
if (this.checkCollision(newHead)) {
gameOver();
return;
}
this.segments.unshift(newHead);
if (newHead.equal(apple.position)) {
score ++;
apple.move();
aniTime -= 1;
} else {
this.segments.pop();
}
};
//Define collision
Block.prototype.equal = function (otherBlock) {
return this.col === otherBlock.col && this.row === otherBlock.row;
};
Snake.prototype.checkCollision = function (head) {
var leftCollision = (head.col === 0);
var topCollision = (head.row === 0);
var rightCollision = (head.col === widthInBlocks - 1);
var bottomCollision = (head.row === heightInBlocks - 1);
var wallCollision = leftCollision || topCollision ||
rightCollision || bottomCollision;
var selfCollision = false;
for (let i = 0; i < this.segments.length; i ++) {
if (head.equal(this.segments[i])) {
selfCollision = true;
}
}
return wallCollision || selfCollision
};
Snake.prototype.setDirection = function (newDirection) {
if (this.direction === 'up' && newDirection === 'down') {
return;
} else if (this.direction === 'right' && newDirection ==='left') {
return;
} else if (this.direction === 'down' && newDirection ==='up') {
return;
} else if (this.direction === 'left' && newDirection ==='right') {
return;
}
this.nextDirection = newDirection;
};
//run the game
let snake = new Snake();
let apple = new Apple();
var aniTime = 100;
function core () {
ctx.clearRect(0, 0, width, height);
drawScore();
snake.move();
snake.draw();
apple.draw();
drawBorder();
timeOutId = setTimeout(core, aniTime);
if (snake.checkCollision() === true) { //**the PROBLEM
clearTimeout(timeOutId);
gameOver();
};
};
core();
//Game over condition
var gameOver = function () {
clearTimeout(timeOutId);
ctx.font = '60px Arial';
ctx.fillStyle = 'Black';
ctx.textAlign = 'center';
ctx.textBaseLine = 'middle';
ctx.fillText('Game Over', width / 2, height / 2);
};
</script>
When the snake hit itself or the wall, the error message are as below:
Uncaught TypeError: Cannot read properties of undefined (reading 'col')
at Snake.checkCollision (snake.html:148:43)
at core (snake.html:191:27)
at snake.html:196:13
Uncaught TypeError: Cannot read properties of undefined (reading 'col')
at Snake.checkCollision (snake.html:148:43)
at core (snake.html:191:27)
snake.html:129
Uncaught TypeError: gameOver is not a function
at Snake.move (snake.html:129:21)
at core (snake.html:186:23)
There seem to be several minor mistakes that are luckily easy to fix:
gameOver is declared as a var after calling core(). Declare it as a function to make sure its definition gets hoisted to the top. (or call core() below var gameOver = ...)
timeOutId is used by both the core and gameOver functions. Declare it outside of those functions to make sure both have access.
Snake.prototype.checkCollision expects head as an argument, but core calls it without one.
The self collision loop starts at i = 0, which means it will check wether the head collides with itself, which is always the case.
Here's an example that has all the issues fixed:
//Create canvas
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let width = canvas.width;
let height = canvas.height;
let blockSize = 15;
let widthInBlocks = width / blockSize;
let heightInBlocks = height / blockSize;
let drawBorder = function () {
ctx.fillStyle = 'Black';
ctx.fillRect(0, 0, width, blockSize);
ctx.fillRect(0, height - blockSize, width, blockSize);
ctx.fillRect(0, 0, blockSize, height);
ctx.fillRect(width - blockSize, 0, blockSize, height);
};
drawBorder();
//Create score
var score = 0;
let drawScore = function () {
ctx.clearRect(10, 10, width - 20, 40);
ctx.fillStyle = 'Black';
ctx.textBaseLine = 'top';
ctx.textAlign = 'left';
ctx.font = '24px Arial';
ctx.fillText('Score : ' + score, 15, 45);
};
//Block constrcutor
const Block = function (col, row) {
this.col = col;
this.row = row;
};
Block.prototype.drawSquare = function (color) {
let x = this.col * blockSize;
let y = this.row * blockSize;
ctx.fillStyle = color;
ctx.fillRect(x, y, blockSize, blockSize);
};
//Create food
const circle = function (x, y, radius, color, fill) {
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
if (fill) {
ctx.fill();
} else {
ctx.stroke();
}
};
Block.prototype.drawCircle = function (color) {
let x = this.col * blockSize + blockSize / 2;
let y = this.row * blockSize + blockSize / 2;
ctx.fillStyle = color;
circle(x, y, blockSize / 2, color, true);
};
let Apple = function () {
this.position = new Block(10, 10);
};
Apple.prototype.draw = function () {
this.position.drawCircle(colorList[Math.floor(Math.random() * 7)]);
};
Apple.prototype.move = function () {
let availableCells = [];
for (let r = 1; r < heightInBlocks - 2; r += 1) {
for (let c = 1; c < widthInBlocks - 2; c += 1) {
if (snake.segments.some(s => s.row === r && s.col === c)) continue;
availableCells.push([r, c]);
};
};
if (availableCells.length === 0) {
console.log("You won!");
return;
}
const [ randomRow, randomCol ] = availableCells[Math.floor(Math.random() * availableCells.length)];
this.position = new Block(randomCol, randomRow);
};
//Setting keycode
const directions = {
37:'left',
38:'up',
39:'right',
40:'down'
};
$('body').keydown(function (event) {
let newDirection = directions[event.keyCode];
if (newDirection !== undefined) {
snake.setDirection(newDirection);
}
});
//Create snake
var colorList = ['Blue','Green','Red','Gold','Silver','Purple','Cyan']
var Snake = function () {
this.segments = [
new Block(7,5),
new Block(6,5),
new Block(5,5)
];
this.direction = 'right';
this.nextDirection = 'right';
};
Snake.prototype.draw = function () {
for (let i = 0; i < this.segments.length; i ++) {
this.segments[i].drawSquare(colorList[Math.floor(Math.random() * 7)]);
};
};
//Setting moving directions
Snake.prototype.move = function () {
let head = this.segments[0];
let newHead;
this.direction = this.nextDirection;
if (this.direction === 'right'){
newHead = new Block(head.col + 1, head.row);
} else if (this.direction === 'left') {
newHead = new Block(head.col - 1, head.row);
} else if (this.direction === 'up') {
newHead = new Block(head.col, head.row - 1);
} else if (this.direction === 'down') {
newHead = new Block(head.col, head.row + 1)
}
if (this.checkCollision(newHead)) {
gameOver();
return;
}
this.segments.unshift(newHead);
if (newHead.equal(apple.position)) {
score ++;
apple.move();
aniTime -= 1;
} else {
this.segments.pop();
}
};
//Define collision
Block.prototype.equal = function (otherBlock) {
return this.col === otherBlock.col && this.row === otherBlock.row;
};
Snake.prototype.checkCollision = function () {
// FIX 3: make sure head is defined
var head = this.segments[0];
var leftCollision = (head.col === 0);
var topCollision = (head.row === 0);
var rightCollision = (head.col === widthInBlocks - 1);
var bottomCollision = (head.row === heightInBlocks - 1);
var wallCollision = leftCollision || topCollision ||
rightCollision || bottomCollision;
var selfCollision = false;
// Fix 4: start at 1 so head does not self collide
for (let i = 1; i < this.segments.length; i ++) {
if (head.equal(this.segments[i])) {
selfCollision = true;
}
}
return wallCollision || selfCollision
};
Snake.prototype.setDirection = function (newDirection) {
if (this.direction === 'up' && newDirection === 'down') {
return;
} else if (this.direction === 'right' && newDirection ==='left') {
return;
} else if (this.direction === 'down' && newDirection ==='up') {
return;
} else if (this.direction === 'left' && newDirection ==='right') {
return;
}
this.nextDirection = newDirection;
};
//run the game
let snake = new Snake();
let apple = new Apple();
var aniTime = 100;
// FIX 2: declare at top level
var timeOutId;
function core () {
ctx.clearRect(0, 0, width, height);
drawScore();
snake.move();
snake.draw();
apple.draw();
drawBorder();
timeOutId = setTimeout(core, aniTime);
if (snake.checkCollision() === true) {
clearTimeout(timeOutId);
gameOver();
};
};
core();
//Game over condition
// FIX 1: declare as function
function gameOver() {
clearTimeout(timeOutId);
ctx.font = '60px Arial';
ctx.fillStyle = 'Black';
ctx.textAlign = 'center';
ctx.textBaseLine = 'middle';
ctx.fillText('Game Over', width / 2, height / 2);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas" width="300" height="300"></canvas>
This is a part of code for make an Snake Game in JS, the problem is that I want to set oldDirection the actual direction to make an animation with the sprites in the U-turns but this.direction is having new value but oldDirection is always undefined.
class SnakeBody {
constructor(x, y, ctx) {
this.x = x
this.y = y
this.oldX
this.oldY
this.ctx = ctx
this.direction
this.oldDirection
this.sprite = new Image()
}
// Draws Players Body taking direction from head to set the direction of sprite
draw(headDirection) {
this.direction = headDirection
this.sprite.onload = () => {
this.ctx.drawImage(this.sprite, this.x, this.y, 20, 20)
}
}
// Update Player Body to new X and Y, and this must receive direction to change sprite
update(newX, newY, newDirection) {
// Problem here OldDirection doesn't get the address value
this.oldDirection = this.direction
this.direction = newDirection
this.ctx.clearRect(this.x, this.y, 20, 20)
this.oldX = this.x
this.oldY = this.y
this.x = newX;
this.y = newY;
this.sprite.onload = () => {
this.ctx.drawImage(this.sprite, this.x, this.y, 20, 20)
}
}
setDirection(nDirection) {
this.oldDirection = this.direction
this.direction = nDirection
}
getDirection() {
return this.direction
}
// Sets the sprite for the snake body
setSnakeSpriteDirection(newDirection) {
switch (true) {
case this.direction == 'DOWN' && newDirection == 'LEFT':
this.sprite.src = 'src/assets/img/snakeBody_rotateDownLeft.png'
break
case this.direction == 'UP' || this.direction == 'UP' &&
newDirection == 'DOWN' || this.direction == 'DOWN':
this.sprite.src = 'src/assets/img/snakeBody_down.png'
break;
case this.direction == 'RIGHT' || this.direction == 'RIGHT' &&
newDirection == 'LEFT' || this.direction == 'LEFT':
this.sprite.src = 'src/assets/img/snakeBody_left.png'
break;
default:
this.sprite.src = 'src/assets/img/snakeBody_left.png'
break;
}
this.draw()
}
}
EDIT:
SnakeBody:
class SnakeBody {
constructor(x, y, ctx) {
this.x = x
this.y = y
this.oldX
this.oldY
this.ctx = ctx
this.direction
this.oldDirection
this.sprite = new Image()
}
draw(headDirection) {
this.direction = headDirection
this.sprite.onload = () => {
this.ctx.drawImage(this.sprite, this.x, this.y, 20, 20)
}
}
update(newX, newY, newDirection) {
this.oldDirection = this.direction
this.direction = newDirection
this.ctx.clearRect(this.x, this.y, 20, 20)
console.log('This.Direction:', this.direction, 'This.oldDirection:', this.oldDirection);
this.oldX = this.x
this.oldY = this.y
this.x = newX;
this.y = newY;
this.sprite.onload = () => {
this.ctx.drawImage(this.sprite, this.x, this.y, 20, 20)
}
}
setDirection(nDirection) {
this.oldDirection = this.direction
this.direction = nDirection
}
getDirection() {
return this.direction
}
setSnakeSpriteDirection(newDirection) {
switch(true) {
case this.direction == 'DOWN' && newDirection == 'LEFT':
this.sprite.src = 'src/assets/img/snakeBody_rotateDownLeft.png'
break
case this.direction == 'UP' || this.direction == 'UP'
&& newDirection == 'DOWN' || this.direction == 'DOWN':
this.sprite.src = 'src/assets/img/snakeBody_down.png'
break;
case this.direction == 'RIGHT' || this.direction == 'RIGHT'
&& newDirection == 'LEFT' || this.direction == 'LEFT':
this.sprite.src = 'src/assets/img/snakeBody_left.png'
break;
default:
this.sprite.src = 'src/assets/img/snakeBody_left.png'
break;
}
this.draw()
}
}
Player Class:
class Player {
constructor(x, y, name, ctx) {
this.x = x;
this.y = y;
this.oldX;
this.oldY;
this.name = name;
this.ctx = ctx;
this.direction
this.oldDirection
this.body = []
this.sprite = new Image()
this.counter = 0;
}
draw() {
switch (this.getDirection()) {
case 'UP':
this.sprite.src = 'src/assets/img/snakeHead.png'
break;
case 'DOWN':
this.sprite.src = 'src/assets/img/snakeHead_down.png'
break;
case 'RIGHT':
this.sprite.src = 'src/assets/img/snakeHead_right.png'
break;
case 'LEFT':
this.sprite.src = 'src/assets/img/snakeHead_left.png'
break;
default:
this.sprite.src = 'src/assets/img/snakeHead.png'
break;
}
this.sprite.onload = () => {
this.ctx.drawImage(this.sprite, this.x, this.y, 20, 20)
}
}
update(newX, newY) {
this.ctx.clearRect(this.x, this.y, 20, 20)
this.x = newX;
this.y = newY;
}
updateBody() {
if (this.body.length > 0) {
this.body.forEach((bodyPart, index) => {
if( index == 0 ) {
bodyPart.update(this.x, this.y, this.oldDirection)
bodyPart.setSnakeSpriteDirection(this.direction)
} else {
bodyPart.update(
this.body[index - 1].oldX,
this.body[index - 1].oldY,
this.body[index - 1].oldDirection
)
bodyPart.setSnakeSpriteDirection(this.body[index - 1].getDirection())
}
})
}
}
movement() {
this.updateBody()
this.oldY = this.y
this.oldX = this.x
switch (this.direction) {
case 'DOWN':
this.y != 380
? this.update(this.x, this.y + 20)
: this.update(this.x, 0)
break;
case 'UP':
this.y == 0
? this.update(this.x, 380)
: this.update(this.x, this.y - 20)
break;
case 'LEFT':
this.x == 0
? this.update(380, this.y)
: this.update(this.x - 20, this.y)
break;
case 'RIGHT':
this.x != 380
? this.update(this.x + 20, this.y)
: this.update(0, this.y)
break;
default:
break;
}
this.oldDirection = this.direction
}
setDirection(direction) {
this.oldDirection = this.direction
this.direction = direction
}
getDirection() {
return this.direction
}
checkCollision(block) {
if (
this.x < block.x + 20 &&
this.x + 20 > block.x &&
this.y < block.y + 20 &&
this.y + 20 > block.y
) {
let newBody = new SnakeBody(this.x, this.y, this.ctx)
newBody.setDirection()
this.body.push(newBody)
}
}
}
Game: (Where I set the Player direction):
document.addEventListener('DOMContentLoaded', (e) => {
const canvasGame = document.querySelector('canvas')
const ctx = canvasGame.getContext('2d')
const
cHeight = canvasGame.height = 400,
cWidth = canvasGame.width = 400;
let cells = 20;
const drawGrid = () => {
ctx.lineWidth = 1.1
ctx.strokeStyle = '#232332'
ctx.shadowBlur = 0
for (let i = 0; i < cells; i++) {
let f = (cHeight / cells) * i
ctx.beginPath()
ctx.moveTo(f, 0);
ctx.lineTo(f, cHeight)
ctx.stroke()
ctx.beginPath();
ctx.moveTo(0, f)
ctx.lineTo(cHeight, f)
ctx.stroke()
ctx.closePath()
}
}
const drawPlayer = () => {
let playerX = Math.floor(Math.random() * 19 - 0 + 1) * 20
let foodX = Math.floor(Math.random() * 19 - 0 + 1) * 20
let foodY = Math.floor(Math.random() * 19 - 0 + 1) * 20
let playerY = Math.floor(Math.random() * 19 - 0 + 1) * 20
let food = new Food(foodX, foodY, 'red', ctx)
let player = new Player(playerX, playerY, 'Moises', ctx)
player.draw()
food.draw()
document.addEventListener('keydown', (e) => {
switch (e.keyCode) {
case 37:
player.setDirection('LEFT')
break;
case 39:
player.setDirection('RIGHT')
break
case 38:
player.setDirection('UP')
break
case 40:
player.setDirection('DOWN')
break;
}
})
setInterval(() => {
player.movement()
player.checkCollision(food)
food.collision(player)
player.draw()
food.draw()
drawGrid()
}, 450)
}
drawGrid()
drawPlayer()
})
<canvas></canvas>
Food Class:
class Food {
constructor(x, y, color, ctx) {
this.x = x
this.y = y
this.color = color
this.ctx = ctx
}
draw() {
let img = new Image()
img.src = 'src/assets/img/apple.png'
img.onload = () => {
this.ctx.drawImage(img, this.x, this.y, 20, 20);
// this.ctx.fillStyle = this.color
// this.ctx.fillRect(this.x, this.y, 20, 20)
}
}
update(newX, newY) {
this.ctx.clearRect(this.x, this.y, 20, 20)
this.x = newX;
this.y = newY;
// this.ctx.fillRect(newX, newY, 20, 20)
}
collision(player) {
let newX = (Math.floor(Math.random() * 19 - 0 + 1)) * 20;
let newY = (Math.floor(Math.random() * 19 - 0 + 1)) * 20;
if (
player.x < this.x + 20 &&
player.x + 20 > this.x &&
player.y < this.y + 20 &&
player.y + 20 > this.y
) {
this.update(newX, newY)
}
}
}
Seems to work fine for me.
class SnakeBody {
constructor() {
this.direction
this.oldDirection
}
test( newDirection ) {
this.oldDirection = this.direction;
this.direction = newDirection;
}
}
const snake = new SnakeBody();
snake.test( 1 );
snake.test( 2 );
console.log( snake.oldDirection );
EDIT:
i'm guessing your problem is here, in your player class:
index == 0
? (
bodyPart.update(this.x, this.y, this.oldDirection),
bodyPart.setSnakeSpriteDirection(this.direction)
)
: (
bodyPart.update(
this.body[index - 1].oldX,
this.body[index - 1].oldY,
this.body[index - 1].oldDirection
),
bodyPart.setSnakeSpriteDirection(this.body[index - 1].getDirection())
)
Does this code not give you errors? it feels like you are using a different language here. you are putting commas in between lines of code. you are using a ternary operator as if it were just an if statement, or something like that. you're putting parenthesis around lines of code. i'm surprised its not giving errors in the console.
Edit 2:
Try replacing this strange ternary operator with this code instead:
if( index == 0 ) {
bodyPart.update(this.x, this.y, this.oldDirection)
bodyPart.setSnakeSpriteDirection(this.direction)
} else {
bodyPart.update(
this.body[index - 1].oldX,
this.body[index - 1].oldY,
this.body[index - 1].oldDirection
)
bodyPart.setSnakeSpriteDirection(this.body[index - 1].getDirection())
}
This question already has answers here:
How do I rotate a single object on an html 5 canvas?
(8 answers)
Closed 2 years ago.
I want to simulate car rotation and movement in the new direction in a game I am designing.
According to the following answer, within the HTML5 canvas element you cannot rotate individual elements.
The movement itself is happening in this function, I expect the vehicle to move but the entire canvas moves (try pressing left and right). I couldn't figure out how to rotate the car.
Game._rotate = function(dirx, direction) {
this.ctx.translate(200,200);
}
According to this tutorial, I can only rotate the car itself by rotating the canvas. I'd like the rotation and movement to emulate the following: https://oseiskar.github.io/js-car/.
The code itself here: https://plnkr.co/edit/K5X8fMhUlRLhdeki.
You need to render the car relative to ITS position. Also, you can do the rotation inside its RENDER.
The changes below will handle rotation for the car, but you have bigger problems. I would take time and invest in learning how vectors work. The position, speed and acceleration should all be 2D vectors that provide vector math like adding and multiplication.
Also, provide an initial angle to your car so it is initially rendered the right way. It also appears that your acceleration and deceleration are mixed up.
function Car(map, x, y) {
this.map = map;
this.x = x;
this.y = y;
this.angle = 0; // IMPORTANT
this.width = map.tsize;
this.height = map.tsize;
this.image = Loader.getImage('car');
}
Car.speed = 0;
Car.acceleration = 0;
Car.friction = 5;
Car.moveAngle = 0;
Car.maxSpeed = 500;
Car.forward = 'FORWARD';
Car.backward = 'BACKWARD';
Car.left = 'LEFT';
Car.right = 'RIGHT';
// Render relative to car...
Car.prototype.render = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.drawImage(this.image, -this.width / 2, -this.height / 2);
ctx.restore();
};
Game.update = function (delta) {
var dirx = 0;
var diry = 0;
if (Keyboard.isDown(Keyboard.LEFT)) {
this._rotate(dirx, Car.left)
}
else if (Keyboard.isDown(Keyboard.RIGHT)) {
this._rotate(dirx, Car.right)
}
else if (Keyboard.isDown(Keyboard.UP)) {
this._rotate(dirx, Car.up)
diry = accelerate(diry, Car.forward);
}
else if (Keyboard.isDown(Keyboard.DOWN)) {
this._rotate(dirx, Car.down)
diry = accelerate(diry, Car.backward);
}
else {
decelerate();
}
this.car.move(delta, dirx, diry);
this.camera.update();
};
Game._rotate = function(dirx, direction) {
let angleInDegrees = 0;
switch (direction) {
case 'UP':
angleInDegrees = 0;
break;
case 'RIGHT':
angleInDegrees = 90;
break;
case 'DOWN':
angleInDegrees = 180;
break;
case 'LEFT':
angleInDegrees = 270;
break;
}
this.car.angle = angleInDegrees * (Math.PI / 180);
}
Game.render = function () {
// draw map background layer
this._drawLayer(0);
this.car.render(this.ctx);
// draw map top layer
this._drawLayer(1);
};
Vectors
Here is an example using vectors.
loadImage('https://i.stack.imgur.com/JY7ai.png')
.then(function(img) {
main(img);
})
function main(img) {
let game = new Game({
canvas: document.querySelector('#viewport')
});
let car = new Car({
img: img,
imgRadiansOffset : -Math.PI / 2,
position: new Victor(game.canvas.width, game.canvas.height).divide(new Victor(2, 2)),
velocity: new Victor(0, -1),
showBorder: true
});
game.addLayer(car);
game.start();
}
class Game {
constructor(options) {
Object.assign(this, Game.defaultOptions, options);
if (this.canvas != null) {
Object.assign(this, {
width: this.canvas.width,
height: this.canvas.height
});
this.addListeners();
}
}
addLayer(layer) {
this.layers.push(layer);
layer.parent = this;
}
start() {
this.id = setInterval(() => {
this.render();
}, 1000 / this.rate); // frames per second
}
render() {
let ctx = this.canvas.getContext('2d');
ctx.clearRect(0, 0, this.width, this.height);
this.layers.forEach(layer => layer.render(ctx));
}
addListeners() {
if (this.canvas != null) {
window.addEventListener('keydown', (e) => {
this.handleKeyDown(e.keyCode);
});
window.addEventListener('keyup', (e) => {
this.handleKeyUp(e.keyCode);
});
}
}
handleKeyDown(keyCode) {
this.layers.forEach(layer => {
layer.update(keyCode !== this.lastKeyCode ? keyCode : null);
});
this.lastKeyCode = keyCode;
}
handleKeyUp(keyCode) {
this.layers.forEach(layer => {
layer.update(this.lastKeyCode); // calls reset...
});
}
}
Game.defaultOptions = {
id: null,
rate: 30,
layers: [],
canvas: null,
width: 0,
height: 0
};
class Car {
constructor(options) {
Object.assign(this, Car.defaultOptions, options);
if (this.img != null) {
Object.assign(this, {
width: this.img.width,
height: this.img.height
});
}
}
render(ctx) {
ctx.save();
ctx.translate(this.position.x, this.position.y);
ctx.rotate(this.velocity.angle() - this.imgRadiansOffset);
ctx.drawImage(this.img, -this.width / 2, -this.height / 2, this.width, this.height);
if (this.showBorder) {
ctx.strokeStyle = '#C00';
ctx.setLineDash([4, 8]);
ctx.lineWidth = 1;
ctx.beginPath();
ctx.rect(-this.width / 2, -this.height / 2, this.width, this.height);
ctx.stroke();
}
ctx.restore();
}
update(keyCode) {
if (keyCode != null) this.changeDirection(keyCode);
this.position.add(this.velocity.add(this.acceleration));
this.detectCollision();
}
detectCollision() {
let xMin = this.width / 2, xMax = this.parent.width - xMin;
let yMin = this.height / 2, yMax = this.parent.height - yMin;
if (this.position.x < xMin) this.position.x = xMin;
if (this.position.x > xMax) this.position.x = xMax;
if (this.position.y < yMin) this.position.y = yMin;
if (this.position.y > yMax) this.position.y = yMax;
}
changeDirection(keyCode) {
switch (keyCode) {
case 37:
this.reset(new Victor(-1, 0)); // LEFT
break;
case 38:
this.reset(new Victor(0, -1)); // UP
break;
case 39:
this.reset(new Victor(1, 0)); // RIGHT
break;
case 40:
this.reset(new Victor(0, 1)); // DOWN
break;
}
}
reset(dirVect) {
this.velocity = new Victor(this.speedFactor, this.speedFactor).multiply(dirVect);
this.acceleration = new Victor(this.accelFactor, this.accelFactor).multiply(dirVect);
}
}
Car.defaultOptions = {
position: new Victor(0, 0),
width: 0,
height: 0,
img: null,
imgRadiansOffset: 0,
velocity: new Victor(0, 0),
acceleration: new Victor(0, 0),
showBorder: false,
speedFactor: 3, // Velocity scalar
accelFactor: 1 // Acceleration scalar
};
function loadImage(url) {
return new Promise(function(resolve, reject) {
var img = new Image;
img.onload = function() {
resolve(this)
};
img.onerror = img.onabort = function() {
reject("Error loading image")
};
img.src = url;
})
}
#viewport {
border: thin solid grey;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/victor/1.1.0/victor.min.js"></script>
<canvas id="viewport" width="400" height="160"></canvas>
I am working on a project on Khan Academy in which I have to create a game with at least 3 levels. I have developed most of the game but when I tried to proceed from one level to next the game somehow stops.
Here is the full project:
Project Link
/**
* Contains 3 levels
*
*
* Changed Ground
* Brown rectangle is replaced with Dirt Block.
*
* Scoring system changed
* Collecting Good sticks gets 1 point.
* Collecting Bad sticks gets -1 point. (i.e. loses point).
* Hitting rocks will lose 1 point.
*
**/
var level = 0;
var nosOfSticks = 5;
var target = 0;
var speed = 1;
var endLevel = false;
var buttonClicked = false;
var levelButtonEnabled = false;
var startButtonEnabled = true;
var Beaver = function(x, y) { // Beaver Constructor
this.x = x;
this.y = y;
this.img = getImage("creatures/Hopper-Happy");
this.sticks = 0;
};
Beaver.prototype.draw = function() { // Draw function to draw beaver
fill(255, 0, 0);
this.x = constrain(this.x, 0, width-40);
this.y = constrain(this.y, 0, height-50);
image(this.img, this.x, this.y, 40, 40);
};
Beaver.prototype.hop = function() { // Hop function to make beaver hop
this.img = getImage("creatures/Hopper-Jumping");
this.y -= speed * 5;
};
Beaver.prototype.hopLeft = function() {
this.img = getImage("creatures/Hopper-Jumping");
this.x -= speed * 5;
};
Beaver.prototype.hopRight = function() {
this.img = getImage("creatures/Hopper-Jumping");
this.x += speed * 5;
};
Beaver.prototype.fall = function() { // fall function makes beaver fall on the ground
this.img = getImage("creatures/Hopper-Happy");
this.y += speed * 5;
};
Beaver.prototype.checkForStickGrab = function(stick) { // function that checks sticks grab
if ((stick.x >= this.x && stick.x <= (this.x + 40)) &&
(stick.y >= this.y && stick.y <= (this.y + 40))) {
stick.y = -400;
this.sticks++;
}
};
Beaver.prototype.checkForBadStickGrab = function(badstick) { // function that checks badsticks grab
if ((badstick.x >= this.x && badstick.x <= (this.x + 40)) &&
(badstick.y >= this.y && badstick.y <= (this.y + 40))) {
badstick.y = -400;
this.sticks--;
}
};
Beaver.prototype.checkForRockHit = function(rock) { // function that checks rocks hit
if ((rock.x >= this.x - 40 && rock.x <= (this.x + 40)) &&
(rock.y >= this.y - 30 && rock.y <= (this.y + 40))) {
rock.x = -400;
this.sticks--;
}
};
// Drawing Sticks
var Stick = function(x, y) { // Stick constructor
this.x = x;
this.y = y;
};
Stick.prototype.draw = function() { // Draw function to draw sticks
fill(0, 0, 0);
rectMode(CENTER);
rect(this.x, this.y, 5, 40);
};
var Badstick = function(x, y) { // Bad Sticks constructor
Stick.call(this, x, y);
};
//Badstick.prototype = Object.create(Stick);
Badstick.prototype.draw = function() { //Draw function to draw badsticks
fill(255, 0, 13);
rectMode(CENTER);
rect(this.x, this.y, 5, 40);
};
// Drawings Rocks
var Rock = function(x, y) { // rocks constructor
this.x = x;
this.y = y;
this.img = getImage("cute/Rock");
};
Rock.prototype.draw = function(x, y) { // function to draw rocks
fill(0, 0, 0);
image(this.img, this.x, this.y, 40, 40);
};
var beaver = new Beaver(200, 300);
var sticks = [];
for (var i = 0; i < nosOfSticks; i++) {
sticks.push(new Stick(i * 100 + 400, random(20, 260)));
}
var badSticks = [];
for (var i = 0; i < nosOfSticks/2; i++) {
badSticks.push(new Badstick(i * 200 + 400, random(20, 270)));
}
var rocks = [];
for ( var i = 0; i < nosOfSticks * 0.375; i++) {
rocks.push(new Rock(random(0, 375), i * random() - (i * 100)));
}
var grassXs = [];
for (var i = 0; i < 25; i++) {
grassXs.push(i*20);
}
var blockXs = [];
for (var i = 0; i < 25; i++) {
blockXs.push(i*20);
}
var Button = function (x, y, w, h, color, text, size, font, textcolor, best) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
this.text = text;
this.size = size;
this.font = font;
this.textcolor = textcolor;
this.best = best;
};
Button.prototype.draw = function() {
rectMode(CORNER);
fill(this.color);
rect(this.x, this.y, this.w, this.h);
fill(this.textcolor);
stroke(this.textcolor);
textFont(this.font, this.size);
text(this.text, this.x + (this.w/2 - this.w/2.5), this.y + (this.h/2 + this.size/2.5));
/*textFont(this.font, this.size / 2);
text("Best : " + this.best, this.x + 10, this.y + 90);*/
};
Button.prototype.clicked = function() {
if(mouseIsPressed && mouseX >= this.x && mouseX <= this.x + this.w && mouseY >= this.y && mouseY <= this.y + this.h ) {
return true;
}
};
var nextButton = new Button(315, 360, 75, 30, color(0, 255, 0), "Next Level", 12, "Aerial Bold", color(0, 0, 0));
var startButton = new Button(315, 360, 75, 30, color(0, 255, 0), "Start Again", 12, "Aerial Bold", color(0, 0, 0));
var playButton = new Button(140, 250, 120, 50, color(0, 0, 0), "PLAY", 40, "Aerial Bold", color(255, 255, 255));
var level1Button = new Button(30, 120, 100, 100, color(0, 0, 0), "Level 1", 25, "Aerial Bold", color(255, 255, 255));
var level2Button = new Button(140, 120, 100, 100, color(0, 0, 0), "Level 2", 25, "Aerial Bold", color(255, 255, 255));
var level3Button = new Button(250, 120, 100, 100, color(0, 0, 0), "Level 3", 25, "Aerial Bold", color(255, 255, 255));
var drawWin = function() {
fill(255, 0, 0);
textSize(36);
text("YOU WIN!!!!", 100, 200);
nextButton.draw();
};
var drawLoss = function() {
fill(255, 0, 0);
textSize(36);
text("YOU LOSE!!!!", 100, 200);
startButton.draw();
};
var movement = function() {
if (keyIsPressed) {
if(keyCode === UP) {
beaver.hop();
} /*else if(keyCode === LEFT) {
beaver.hopLeft();
} else if(keyCode === RIGHT) {
beaver.hopRight();
} */
} else { beaver.fall();}
};
var drawScore = function() {
fill(0, 255, 0);
textSize(18);
text("Score: " + beaver.sticks, 10, 390);
};
var isWin = function() {
if(beaver.sticks >= target) {
drawWin();
speed = 1;
return true;
}
};
var isLoss = function() {
if (beaver.sticks < target ) {
speed = 1;
drawLoss();
return true;
}
};
var drawBackground = function() {
//static
speed = 1;
background(227, 254, 255);
stroke(0, 0, 0);
rectMode(CORNER);
rect(0, height*0.90, width, height*0.10);
for (var i = 0; i < grassXs.length; i++) {
image(getImage("cute/GrassBlock"), grassXs[i], height*0.85, 35, 20);
image(getImage("cute/DirtBlock"), grassXs[i], height*0.85, 35, 60);
grassXs[i] -= speed;
if (grassXs[i] <= - 20) {
grassXs[i] = width;
}
}
};
var drawSticks = function() {
for (var i = 0; i < sticks.length; i++) {
sticks[i].draw();
beaver.checkForStickGrab(sticks[i]);
sticks[i].x -= speed;
}
};
var drawBadSticks = function() {
for (var i = 0; i < badSticks.length; i++) {
badSticks[i].draw();
beaver.checkForBadStickGrab(badSticks[i]);
badSticks[i].x -= speed;
}
};
var drawRocks = function() {
for (var i = 0; i < rocks.length; i++) {
rocks[i].draw();
beaver.checkForRockHit(rocks[i]);
rocks[i].y += speed;
}
};
var drawLevel = function() {
speed = 1;
drawBackground();
if (level === 1) {
target = 1;
drawSticks();
}
if (level === 2) {
target = 1;
drawSticks();
drawBadSticks();
}
if (level === 3) {
target = 1;
drawBadSticks();
drawSticks();
drawRocks();
}
beaver.draw();
movement();
drawScore();
if (sticks[nosOfSticks - 1].x < -5) {
isWin();
isLoss();
}
};
var drawLevels = function() {
level = "l";
background(0, 0, 0);
level1Button.draw();
level2Button.draw();
level3Button.draw();
if (level1Button.clicked() && level === "l") {
level = 1;
drawLevel();
} else if (level2Button.clicked() && level === "l") {
level = 2;
drawLevel();
} else if (level3Button.clicked() && level === "l") {
level = 3;
drawLevel();
}
};
var drawStart = function() {
level = 0;
background(0);
text("Hoppy Beaver", 75, 50);
text("Extreme", 120, 100);
playButton.draw();
if (playButton.clicked() && level === 0) {
levelButtonEnabled = false;
drawLevels();
}
};
//drawStart();
mouseClicked = function() {
if (nextButton.clicked() || startButton.clicked()) {
if (beaver.sticks >= 1) {
if (level === 0) {
level = 1;
sticks = [];
draw();
isWin = false;
}
if (level === 1) {
level = 2;
sticks = [];
draw();
isWin = false;
}
if (level === 2) {
level = 3;
sticks = [];
draw();
isWin = false;
}
if (level === 3) {
level = 1;
sticks = [];
isWin = false;
draw();
}
} else if (beaver.sticks < 1) {
if (level === 1) {
level = 1;
sticks = [];
drawLevel();
isLoss = false;
}
if (level === 2) {
level = 2;
sticks = [];
drawLevel();
isLoss = false;
}
if (level === 3) {
level = 3;
sticks = [];
drawLevel();
isLoss = false;
}
}
}
};
draw = function() {
speed = 1;
if (level === 1) {
drawLevel();
} else if (level === 2) {
drawLevel();
} else if (level === 3) {
drawLevel();
} else if (level === "l") {
drawLevels();
} else { drawStart(); }
};
welcome to stackoverflow. The problem with your code is this bit right here in the drawLevel function.
if (sticks[nosOfSticks - 1].x < -5) {
isWin();
isLoss();
}
At the start of your program you initialize the sticks array with some stick objects in line 124. When level 1 ends and the next button is clicked, you set the sticks array to an empty array sticks=[] in the mouseClicked function.However, you never re-add anything into the sticks array. Thus, when that block of code runs, the element at position nosOfSticks-1 is undefined, leading to your problem.My suggestion is to make a for loop after sticks=[] to refill the sticks array just like in line 124.
Good Luck!
Also, take a look at this guide for debugging help, how to debug small programs.
I'm trying to edit a pacman implementation (source) using htlm5 and js. The problem is when I make the background transparent, the ghosts and pacman images holding the previews frames. This is an example about my problem.
My js code:
Pacman.FPS = 30;
Pacman.Ghost = function (game, map, colour) {
function getColour() {
if (eatable) {
if (secondsAgo(eatable) > 5) {
return game.getTick() % 20 > 10 ? "rgba(0, 0, 0, 0.1)" : "rgba(0, 0, 0, 0.1)";
} else {
return "rgba(0, 0, 0, 0.1)";
}
} else if(eaten) {
return "#222";
}
return colour;
};
function draw(ctx) {
var s = map.blockSize,
top = (position.y/10) * s,
left = (position.x/10) * s;
if (eatable && secondsAgo(eatable) > 8) {
eatable = null;
}
if (eaten && secondsAgo(eaten) > 3) {
eaten = null;
}
var tl = left + s;
var base = top + s - 3;
var inc = s / 10;
var high = game.getTick() % 10 > 5 ? 3 : -3;
var low = game.getTick() % 10 > 5 ? -3 : 3;
ctx.fillStyle = getColour();
ctx.beginPath();
ctx.moveTo(left, base);
ctx.quadraticCurveTo(left, top, left + (s/2), top);
ctx.quadraticCurveTo(left + s, top, left+s, base);
// Wavy things at the bottom
ctx.quadraticCurveTo(tl-(inc*1), base+high, tl - (inc * 2), base);
ctx.quadraticCurveTo(tl-(inc*3), base+low, tl - (inc * 4), base);
ctx.quadraticCurveTo(tl-(inc*5), base+high, tl - (inc * 6), base);
ctx.quadraticCurveTo(tl-(inc*7), base+low, tl - (inc * 8), base);
ctx.quadraticCurveTo(tl-(inc*9), base+high, tl - (inc * 10), base);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#FFF";
ctx.arc(left + 6,top + 6, s / 6, 0, 300, false);
ctx.arc((left + s) - 6,top + 6, s / 6, 0, 300, false);
ctx.closePath();
ctx.fill();
var f = s / 12;
var off = {};
off[RIGHT] = [f, 0];
off[LEFT] = [-f, 0];
off[UP] = [0, -f];
off[DOWN] = [0, f];
ctx.beginPath();
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#000";
ctx.arc(left+6+off[direction][0], top+6+off[direction][3],
s / 15, 0, 300, false);
ctx.arc((left+s)-6+off[direction][0], top+6+off[direction][4],
s / 15, 0, 300, false);
ctx.closePath();
ctx.fill();
};
function pane(pos) {
if (pos.y === 100 && pos.x >= 190 && direction === RIGHT) {
return {"y": 100, "x": -10};
}
if (pos.y === 100 && pos.x <= -10 && direction === LEFT) {
return position = {"y": 100, "x": 190};
}
return false;
};
function move(ctx) {
var oldPos = position,
onGrid = onGridSquare(position),
npos = null;
if (due !== direction) {
npos = getNewCoord(due, position);
if (onGrid &&
map.isFloorSpace({
"y":pointToCoord(nextSquare(npos.y, due)),
"x":pointToCoord(nextSquare(npos.x, due))})) {
direction = due;
} else {
npos = null;
}
}
if (npos === null) {
npos = getNewCoord(direction, position);
}
if (onGrid &&
map.isWallSpace({
"y" : pointToCoord(nextSquare(npos.y, direction)),
"x" : pointToCoord(nextSquare(npos.x, direction))
})) {
due = getRandomDirection();
return move(ctx);
}
position = npos;
var tmp = pane(position);
if (tmp) {
position = tmp;
}
due = getRandomDirection();
return {
"new" : position,
"old" : oldPos
};
};
return {
"eat" : eat,
"isVunerable" : isVunerable,
"isDangerous" : isDangerous,
"makeEatable" : makeEatable,
"reset" : reset,
"move" : move,
"draw" : draw
};
};
Pacman.User = function (game, map) {
var position = null,
direction = null,
eaten = null,
due = null,
lives = null,
score = 5,
keyMap = {};
keyMap[KEY.ARROW_LEFT] = LEFT;
keyMap[KEY.ARROW_UP] = UP;
keyMap[KEY.ARROW_RIGHT] = RIGHT;
keyMap[KEY.ARROW_DOWN] = DOWN;
function addScore(nScore) {
score += nScore;
if (score >= 10000 && score - nScore < 10000) {
lives += 1;
}
};
function theScore() {
return score;
};
function loseLife() {
lives -= 1;
};
function getLives() {
return lives;
};
function initUser() {
score = 0;
lives = 3;
newLevel();
}
function newLevel() {
resetPosition();
eaten = 0;
};
function resetPosition() {
position = {"x": 90, "y": 120};
direction = LEFT;
due = LEFT;
};
function reset() {
initUser();
resetPosition();
};
function keyDown(e) {
if (typeof keyMap[e.keyCode] !== "undefined") {
due = keyMap[e.keyCode];
e.preventDefault();
e.stopPropagation();
return false;
}
return true;
};
function getNewCoord(dir, current) {
return {
"x": current.x + (dir === LEFT && -2 || dir === RIGHT && 2 || 0),
"y": current.y + (dir === DOWN && 2 || dir === UP && -2 || 0)
};
};
function onWholeSquare(x) {
return x % 10 === 0;
};
function pointToCoord(x) {
return Math.round(x/10);
};
function nextSquare(x, dir) {
var rem = x % 10;
if (rem === 0) {
return x;
} else if (dir === RIGHT || dir === DOWN) {
return x + (10 - rem);
} else {
return x - rem;
}
};
function next(pos, dir) {
return {
"y" : pointToCoord(nextSquare(pos.y, dir)),
"x" : pointToCoord(nextSquare(pos.x, dir)),
};
};
function onGridSquare(pos) {
return onWholeSquare(pos.y) && onWholeSquare(pos.x);
};
function isOnSamePlane(due, dir) {
return ((due === LEFT || due === RIGHT) &&
(dir === LEFT || dir === RIGHT)) ||
((due === UP || due === DOWN) &&
(dir === UP || dir === DOWN));
};
function move(ctx) {
var npos = null,
nextWhole = null,
oldPosition = position,
block = null;
if (due !== direction) {
npos = getNewCoord(due, position);
if (isOnSamePlane(due, direction) ||
(onGridSquare(position) &&
map.isFloorSpace(next(npos, due)))) {
direction = due;
} else {
npos = null;
}
}
if (npos === null) {
npos = getNewCoord(direction, position);
}
if (onGridSquare(position) && map.isWallSpace(next(npos, direction))) {
direction = NONE;
}
if (direction === NONE) {
return {"new" : position, "old" : position};
}
if (npos.y === 100 && npos.x >= 190 && direction === RIGHT) {
npos = {"y": 100, "x": -10};
}
if (npos.y === 100 && npos.x <= -12 && direction === LEFT) {
npos = {"y": 100, "x": 190};
}
position = npos;
nextWhole = next(position, direction);
block = map.block(nextWhole);
if ((isMidSquare(position.y) || isMidSquare(position.x)) &&
block === Pacman.BISCUIT || block === Pacman.PILL) {
map.setBlock(nextWhole, Pacman.EMPTY);
addScore((block === Pacman.BISCUIT) ? 10 : 50);
eaten += 1;
if (eaten === 182) {
game.completedLevel();
}
if (block === Pacman.PILL) {
game.eatenPill();
}
}
return {
"new" : position,
"old" : oldPosition
};
};
function isMidSquare(x) {
var rem = x % 10;
return rem > 3 || rem < 7;
};
function calcAngle(dir, pos) {
if (dir == RIGHT && (pos.x % 10 < 5)) {
return {"start":0.25, "end":1.75, "direction": false};
} else if (dir === DOWN && (pos.y % 10 < 5)) {
return {"start":0.75, "end":2.25, "direction": false};
} else if (dir === UP && (pos.y % 10 < 5)) {
return {"start":1.25, "end":1.75, "direction": true};
} else if (dir === LEFT && (pos.x % 10 < 5)) {
return {"start":0.75, "end":1.25, "direction": true};
}
return {"start":0, "end":2, "direction": false};
};
function drawDead(ctx, amount) {
var size = map.blockSize,
half = size / 2;
if (amount >= 1) {
return;
}
ctx.fillStyle = "#FFFF00";
ctx.beginPath();
ctx.moveTo(((position.x/10) * size) + half,
((position.y/10) * size) + half);
ctx.arc(((position.x/10) * size) + half,
((position.y/10) * size) + half,
half, 0, Math.PI * 2 * amount, true);
ctx.fill();
};
function draw(ctx) {
var s = map.blockSize,
angle = calcAngle(direction, position);
ctx.fillStyle = "#FFFF00";
ctx.beginPath();
ctx.moveTo(((position.x/10) * s) + s / 2,
((position.y/10) * s) + s / 2);
ctx.arc(((position.x/10) * s) + s / 2,
((position.y/10) * s) + s / 2,
s / 2, Math.PI * angle.start,
Math.PI * angle.end, angle.direction);
ctx.fill();
};
initUser();
return {
"draw" : draw,
"drawDead" : drawDead,
"loseLife" : loseLife,
"getLives" : getLives,
"score" : score,
"addScore" : addScore,
"theScore" : theScore,
"keyDown" : keyDown,
"move" : move,
"newLevel" : newLevel,
"reset" : reset,
"resetPosition" : resetPosition
};
};
Pacman.Map = function (size) {
var height = null,
width = null,
blockSize = size,
pillSize = 0,
map = null;
function withinBounds(y, x) {
return y >= 0 && y < height && x >= 0 && x < width;
}
function isWall(pos) {
return withinBounds(pos.y, pos.x) && map[pos.y][pos.x] === Pacman.WALL;
}
function isFloorSpace(pos) {
if (!withinBounds(pos.y, pos.x)) {
return false;
}
var peice = map[pos.y][pos.x];
return peice === Pacman.EMPTY ||
peice === Pacman.BISCUIT ||
peice === Pacman.PILL;
}
function drawWall(ctx) {
var i, j, p, line;
ctx.strokeStyle = "#fFF";
ctx.lineWidth = 5;
ctx.lineCap = "round";
for (i = 0; i < Pacman.WALLS.length; i += 1) {
line = Pacman.WALLS[i];
ctx.beginPath();
for (j = 0; j < line.length; j += 1) {
p = line[j];
if (p.move) {
ctx.moveTo(p.move[0] * blockSize, p.move[1] * blockSize);
} else if (p.line) {
ctx.lineTo(p.line[0] * blockSize, p.line[1] * blockSize);
} else if (p.curve) {
ctx.quadraticCurveTo(p.curve[0] * blockSize,
p.curve[1] * blockSize,
p.curve[2] * blockSize,
p.curve[3] * blockSize);
}
}
ctx.stroke();
}
}
function reset() {
map = Pacman.MAP.clone();
height = map.length;
width = map[0].length;
};
function block(pos) {
return map[pos.y][pos.x];
};
function setBlock(pos, type) {
map[pos.y][pos.x] = type;
};
function drawPills(ctx) {
if (++pillSize > 30) {
pillSize = 0;
}
for (i = 0; i < height; i += 1) {
for (j = 0; j < width; j += 1) {
if (map[i][j] === Pacman.PILL) {
ctx.beginPath();
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#000";
ctx.fillRect((j * blockSize), (i * blockSize),
blockSize, blockSize);
ctx.fillStyle = "#FFF";
ctx.arc((j * blockSize) + blockSize / 2,
(i * blockSize) + blockSize / 2,
Math.abs(5 - (pillSize/3)),
0,
Math.PI * 2, false);
ctx.fill();
ctx.closePath();
}
}
}
};
function draw(ctx) {
var i, j, size = blockSize;
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#000";
ctx.fillRect(0, 0, width * size, height * size);
drawWall(ctx);
for (i = 0; i < height; i += 1) {
for (j = 0; j < width; j += 1) {
drawBlock(i, j, ctx);
}
}
};
function drawBlock(y, x, ctx) {
var layout = map[y][x];
if (layout === Pacman.PILL) {
return;
}
ctx.beginPath();
if (layout === Pacman.EMPTY || layout === Pacman.BLOCK ||
layout === Pacman.BISCUIT) {
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#000";
ctx.fillRect((x * blockSize), (y * blockSize),
blockSize, blockSize);
if (layout === Pacman.BISCUIT) {
ctx.fillStyle = "#FFF";
ctx.fillRect((x * blockSize) + (blockSize / 2.5),
(y * blockSize) + (blockSize / 2.5),
blockSize / 6, blockSize / 6);
}
}
ctx.closePath();
};
reset();
return {
"draw" : draw,
"drawBlock" : drawBlock,
"drawPills" : drawPills,
"block" : block,
"setBlock" : setBlock,
"reset" : reset,
"isWallSpace" : isWall,
"isFloorSpace" : isFloorSpace,
"height" : height,
"width" : width,
"blockSize" : blockSize
};
};
Pacman.Audio = function(game) {
var files = [],
endEvents = [],
progressEvents = [],
playing = [];
function load(name, path, cb) {
var f = files[name] = document.createElement("audio");
progressEvents[name] = function(event) { progress(event, name, cb); };
f.addEventListener("canplaythrough", progressEvents[name], true);
f.setAttribute("preload", "true");
f.setAttribute("autobuffer", "true");
f.setAttribute("src", path);
f.pause();
};
function progress(event, name, callback) {
if (event.loaded === event.total && typeof callback === "function") {
callback();
files[name].removeEventListener("canplaythrough",
progressEvents[name], true);
}
};
function disableSound() {
for (var i = 0; i < playing.length; i++) {
files[playing[i]].pause();
files[playing[i]].currentTime = 0;
}
playing = [];
};
function ended(name) {
var i, tmp = [], found = false;
files[name].removeEventListener("ended", endEvents[name], true);
for (i = 0; i < playing.length; i++) {
if (!found && playing[i]) {
found = true;
} else {
tmp.push(playing[i]);
}
}
playing = tmp;
};
return {
"disableSound" : disableSound,
"load" : load,
"play" : play,
"pause" : pause,
"resume" : resume
};
};
var PACMAN = (function () {
var state = WAITING,
audio = null,
ghosts = [],
ghostSpecs = ["#00FFDE", "#FF0000", "#FFB8DE", "#FFB847"],
eatenCount = 0,
level = 0,
tick = 0,
ghostPos, userPos,
stateChanged = true,
timerStart = null,
lastTime = 0,
ctx = null,
timer = null,
map = null,
user = null,
stored = null;
function getTick() {
return tick;
};
function collided(user, ghost) {
return (Math.sqrt(Math.pow(ghost.x - user.x, 2) +
Math.pow(ghost.y - user.y, 2))) < 10;
};
function drawFooter() {
var topLeft = (map.height * map.blockSize),
textBase = topLeft + 17;
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#000000";
ctx.fillRect(0, topLeft, (map.width * map.blockSize), 30);
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#FFFF00";
for (var i = 0, len = user.getLives(); i < len; i++) {
ctx.fillStyle = "rgba(0, 0, 0, 0)";
//ctx.fillStyle = "#FFFF00";
ctx.beginPath();
ctx.moveTo(150 + (25 * i) + map.blockSize / 2,
(topLeft+1) + map.blockSize / 2);
ctx.arc(150 + (25 * i) + map.blockSize / 2,
(topLeft+1) + map.blockSize / 2,
map.blockSize / 2, Math.PI * 0.25, Math.PI * 1.75, false);
ctx.fill();
}
ctx.fillStyle = !soundDisabled() ? "#00FF00" : "#FF0000";
ctx.font = "bold 16px sans-serif";
//ctx.fillText("♪", 10, textBase);
ctx.fillText("s", 10, textBase);
ctx.fillStyle = "#FFF";
ctx.font = "14px BDCartoonShoutRegular";
ctx.fillText("Score: " + user.theScore(), 30, textBase);
ctx.fillText("Level: " + level, 260, textBase);
}
function redrawBlock(pos) {
map.drawBlock(Math.floor(pos.y/10), Math.floor(pos.x/10), ctx);
map.drawBlock(Math.ceil(pos.y/10), Math.ceil(pos.x/10), ctx);
}
function mainDraw() {
var diff, u, i, len, nScore;
ghostPos = [];
for (i = 0, len = ghosts.length; i < len; i += 1) {
ghostPos.push(ghosts[i].move(ctx));
}
u = user.move(ctx);
for (i = 0, len = ghosts.length; i < len; i += 1) {
redrawBlock(ghostPos[i].old);
}
redrawBlock(u.old);
for (i = 0, len = ghosts.length; i < len; i += 1) {
ghosts[i].draw(ctx);
}
user.draw(ctx);
userPos = u["new"];
for (i = 0, len = ghosts.length; i < len; i += 1) {
ctx.fillStyle = "rgba(0, 0, 0, 0)";
if (collided(userPos, ghostPos[i]["new"])) {
if (ghosts[i].isVunerable()) {
audio.play("eatghost");
ghosts[i].eat();
eatenCount += 1;
nScore = eatenCount * 50;
drawScore(nScore, ghostPos[i]);
user.addScore(nScore);
setState(EATEN_PAUSE);
timerStart = tick;
} else if (ghosts[i].isDangerous()) {
audio.play("die");
setState(DYING);
timerStart = tick;
}
}
}
};
function mainLoop() {
var diff;
if (state !== PAUSE) {
++tick;
}
map.drawPills(ctx);
if (state === PLAYING) {
mainDraw();
} else if (state === WAITING && stateChanged) {
stateChanged = false;
map.draw(ctx);
dialog("Press N to start a New game");
} else if (state === EATEN_PAUSE &&
(tick - timerStart) > (Pacman.FPS / 3)) {
map.draw(ctx);
setState(PLAYING);
} else if (state === DYING) {
if (tick - timerStart > (Pacman.FPS * 2)) {
loseLife();
} else {
redrawBlock(userPos);
for (i = 0, len = ghosts.length; i < len; i += 1) {
redrawBlock(ghostPos[i].old);
//ctx.fillStyle = "rgba(0, 0, 0, 0)";
ghostPos.push(ghosts[i].draw(ctx));
}
user.drawDead(ctx, (tick - timerStart) / (Pacman.FPS * 2));
}
} else if (state === COUNTDOWN) {
diff = 5 + Math.floor((timerStart - tick) / Pacman.FPS);
if (diff === 0) {
map.draw(ctx);
setState(PLAYING);
} else {
if (diff !== lastTime) {
lastTime = diff;
map.draw(ctx);
dialog("Starting in: " + diff);
}
}
}
drawFooter();
}
}());
For every game you want to make, your gameLoop should contain game logic like this (and in advance: GameAlchemist is right, everything should be redrawn every frame):
Clear the whole canvas
Draw background elements (don't use expensive calls, simply draw an image. If you have a lot of drawn elements, such as shapes, lines, etc., make sure to buffer this first on f.e. another, hidden canvas)
Draw more static (background) elements if you want to, that don't change position (f.e. walls)
Draw dynamic elements (your Hero, enemies, bullets, etc.)
Think of all these steps as layers (as in f.e. Photoshop) and make sure to do it in the right order.
This cheat sheet is very helpful.
Also, instead of setInterval, start using requestAnimationFrame. See f.e. this link.
PS Please don't beg for code, in the way you do. Experiment, try, fail, and try again. Then you will learn. Not by asking for gift wrapped pieces of code.