Related
I am a beginner in JS canvas. I wanna make a game but when rendering too many (Bricks) the game becomes unplayable. Most of the lag comes from draw function the part where bricks are drawn, From ctx.fill() and ctx.rect() function. I observed it with the chrome's performance devtool.
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var ballRadius = 10;
var x = canvas.width / 2;
var y = canvas.height - ballRadius;
var bulletX = x;
var bulletY = y;
var bullet2X = x;
var bullet2Y = y;
var dx = 2;
var dy = -2;
var paddleHeight = 0;
var paddleWidth = canvas.width;
var paddleX = (canvas.width - paddleWidth) / 2;
var rightPressed = false;
var leftPressed = false;
var brickRowCount = 50;
var brickColumnCount = 96;
var brickWidth = 5;
var brickHeight = 5;
var brickPadding = 0;
var brickOffsetTop = 0;
var brickOffsetLeft = 0;
var score = 0;
var damageDealth = 0;
var brickHealth = 1000;
var scoreEarned = 0;
window.bricks = [];
if (bricks.length == 0) {
bricks = [];
for (var c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for (var r = 0; r < brickRowCount; r++) {
bricks[c][r] = {
x: 0,
y: 0,
status: brickHealth
};
}
}
}
var resetLevel =
setInterval(() => {
var brickStatus = 0;
for (var row = 0; row < bricks.length; row++) {
bricks[row].forEach((status) => {
if (status.status > 1) {
brickStatus += 1;
}
})
}
if (brickStatus < 1) {
for (var row = 0; row < bricks.length; row++) {
for (var brick = 0; brick < bricks[row].length; brick++) {
if (bricks[row][brick].status < 1) {
brickHealth += 1;
bricks[row][brick].status = 1000
allBullets = [];
childBullets = [];
AOEBullets = [];
}
}
}
}
console.log(brickStatus);
console.log("BrickHealth: " + brickHealth)
}, 1000);
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
function keyDownHandler(e) {
if (e.key == "Right" || e.key == "ArrowRight" || e.key == "d") {
rightPressed = true;
} else if (e.key == "Left" || e.key == "ArrowLeft" || e.key == "a") {
leftPressed = true;
}
}
function keyUpHandler(e) {
if (e.key == "Right" || e.key == "ArrowRight" || e.key == "d") {
rightPressed = false;
} else if (e.key == "Left" || e.key == "ArrowLeft" || e.key == "a") {
leftPressed = false;
}
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
class MakeBullet {
constructor(x, y, radius, color, speedX, speedY, pierce, lifespan, damage) {
this.x = x;
this.y = y;
this.speedX = speedX;
this.speedY = speedY;
this.radius = radius;
this.color = color;
this.pierce = pierce;
this.lifespan = lifespan;
this.damage = damage;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
update() {
this.draw();
this.x = this.x + this.speedX;
this.y = this.y + this.speedY;
}
}
window.allBullets = [];
window.childBullets = [];
window.AOEBullets = [];
document.addEventListener("mousemove", (event) => {
const angle = Math.atan2(event.clientY - y, event.clientX - x);
window.shootAngle = {
x: Math.cos(angle),
y: Math.sin(angle)
}
})
setInterval(() => {
if (allBullets.length < 101) {
allBullets.push(new MakeBullet(x, y, 3, "rgba(0, 149, 221, 0.9)", shootAngle.x + shootAngle.x * 12, shootAngle.y + shootAngle.y * 12, 8, 2, 25))
/* allBullets.push(new MakeBullet(x - 5, y, 3, "rgba(0, 149, 221, 0.9)", shootAngle.x + shootAngle.x * 12, shootAngle.y + shootAngle.y * 12, 8, 8, 50))
allBullets.push(new MakeBullet(x + 5, y, 3, "rgba(0, 149, 221, 0.9)", shootAngle.x + shootAngle.x * 12, shootAngle.y + shootAngle.y * 12, 8, 8, 25)) */
}
}, 75)
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function drawBricks() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
if (bricks[c][r].status > 0) {
var brickX = (c * (brickWidth + brickPadding)) + brickOffsetLeft;
var brickY = (r * (brickHeight + brickPadding)) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
}
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBricks();
drawBall();
drawPaddle();
childBullets.forEach((childBullet) => {
if (childBullet.lifespan > 0) {
childBullet.update();
childBullet.draw();
if (childBullet.x + childBullet.speedX > canvas.width - childBullet.radius || childBullet.x + childBullet.speedX < childBullet.radius) {
childBullet.speedX = -childBullet.speedX;
}
if (childBullet.y + childBullet.speedY > canvas.width - childBullet.radius || childBullet.y + childBullet.speedY < childBullet.radius) {
childBullet.speedY = -childBullet.speedY;
} else if (childBullet.y + childBullet.speedY > canvas.height - childBullet.radius) {
if (childBullet.x > paddleX && childBullet.x < paddleX + paddleWidth) {
if (childBullet.y = childBullet.y - paddleHeight) {
childBullet.speedY = -childBullet.speedY;
}
}
}
}
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
var b = bricks[c][r];
if (b.status > 0) {
if (childBullet.x > b.x && childBullet.x < b.x + brickWidth && childBullet.y > b.y && childBullet.y < b.y + brickHeight) {
b.status -= childBullet.damage;
childBullet.pierce -= 1
var randomScore = Math.floor(Math.random() * 5);
score += randomScore;
scoreEarned += randomScore;
damageDealth += childBullet.damage;
}
}
}
}
})
AOEBullets.forEach((AOEBullet) => {
if (AOEBullet.lifespan > 0) {
AOEBullet.update();
AOEBullet.draw();
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
var b = bricks[c][r];
if (b.status > 0) {
if (AOEBullet.x + AOEBullet.radius > b.x + brickWidth && AOEBullet.x - AOEBullet.radius < b.x + brickWidth && AOEBullet.y + AOEBullet.radius > b.y + brickHeight && AOEBullet.y - AOEBullet.radius < b.y + brickHeight) {
b.status -= AOEBullet.damage;
AOEBullet.pierce -= 1
var randomScore = Math.floor(Math.random() * 10);
score += randomScore;
scoreEarned += randomScore;
damageDealth += AOEBullet.damage;
}
}
}
}
}
})
if (shootAngle !== undefined) {
allBullets.forEach((bullet) => {
if (bullet.lifespan > 0) {
if (bullet.pierce > 0) {
bullet.update();
bullet.draw();
window.BulletX = bullet.x;
window.BulletY = bullet.y;
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
var b = bricks[c][r];
if (b.status > 0) {
if (bullet.x > b.x && bullet.x < b.x + brickWidth && bullet.y > b.y && bullet.y < b.y + brickHeight) {
b.status -= bullet.damage;
bullet.pierce -= 1;
var randomScore = Math.floor(Math.random() * 100);
score += randomScore;
scoreEarned += randomScore;
AOEBullets.push(new MakeBullet(bullet.x, bullet.y, 25, "rgba(0,149,221, 0.024)", 0, 0, 5, 0.4, 1.4))
AOEBullets.push(new MakeBullet(bullet.x, bullet.y, 50, "rgba(0,149,221, 0.018)", 0, 0, 5, 0.3, 0.7))
AOEBullets.push(new MakeBullet(bullet.x, bullet.y, 75, "rgba(0,149,221, 0.016)", 0, 0, 5, 0.2, 0.5))
AOEBullets.push(new MakeBullet(bullet.x, bullet.y, 100, "rgba(0,149,221, 0.012)", 0, 0, 5, 0.1, 0.2))
damageDealth += bullet.damage;
}
if (bullet.x > b.x && bullet.x < b.x + brickWidth && bullet.y > b.y && bullet.y < b.y + brickHeight) {
b.status -= bullet.damage;
}
if (bullet.x > b.x && bullet.x < b.x + brickWidth && bullet.y > b.y && bullet.y < b.y + brickHeight) {
b.status -= bullet.damage;
}
if (bullet.x > b.x && bullet.x < b.x + brickWidth && bullet.y > b.y && bullet.y < b.y + brickHeight) {
b.status -= bullet.damage;
}
}
}
}
if (bullet.x + bullet.speedX > canvas.width - bullet.radius || bullet.x + bullet.speedX < bullet.radius) {
bullet.speedX = -bullet.speedX;
}
if (bullet.y + bullet.speedY > canvas.width - bullet.radius || bullet.y + bullet.speedY < bullet.radius) {
bullet.speedY = -bullet.speedY;
} else if (bullet.y + bullet.speedY > canvas.height - bullet.radius) {
if (bullet.x > paddleX && bullet.x < paddleX + paddleWidth) {
if (bullet.y = bullet.y - paddleHeight) {
bullet.speedY = -bullet.speedY;
}
}
}
}
}
})
}
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if (y + dy < ballRadius) {
dy = -dy;
} else if (y + dy > canvas.height - ballRadius) {
if (x > paddleX && x < paddleX + paddleWidth) {
if (y = y - paddleHeight) {
dy = -dy;
}
} else {
document.location.reload();
clearInterval(interval); // Needed for Chrome to end game
}
}
if (rightPressed && x < canvas.width - ballRadius) {
x += 7;
} else if (leftPressed && x > 0 + ballRadius) {
x -= 7;
}
// x += dx;
//y += dy;
}
var interval = setInterval(draw, 10);
setInterval(() => {
allBullets.forEach((bullet) => {
for (var i = 0; i < 1; i++) {
var angle = 2 * Math.PI * Math.random();
var randomx = bullet.speedX * Math.cos(angle);
var randomy = bullet.speedY * Math.sin(angle);
if (bullet.lifespan < 1) {
AOEBullets.push(new MakeBullet(bullet.x, bullet.y, 25, "rgba(0,149,221, 0.024)", 0, 0, 5, 0.4, 0.24))
AOEBullets.push(new MakeBullet(bullet.x, bullet.y, 50, "rgba(0,149,221, 0.018)", 0, 0, 5, 0.3, 0.16))
AOEBullets.push(new MakeBullet(bullet.x, bullet.y, 75, "rgba(0,149,221, 0.016)", 0, 0, 5, 0.2, 0.08))
AOEBullets.push(new MakeBullet(bullet.x, bullet.y, 100, "rgba(0,149,221, 0.012)", 0, 0, 5, 0.1, 0.04))
if (childBullets.length < 51) {
childBullets.push(new MakeBullet(bullet.x, bullet.y, 2, "#0095DD", randomx + randomx * Math.floor(Math.random() * 2), randomy + randomy * Math.floor(Math.random() * 2), 5, 5, 1))
}
}
if (bullet.pierce < 1) {
if (childBullets.length < 51) {
childBullets.push(new MakeBullet(bullet.x, bullet.y, 2, "#0095DD", randomx + randomx * Math.floor(Math.random() * 5), randomy + randomy * Math.floor(Math.random() * 5), 5, 5, 1))
}
}
}
})
}, 1)
var lifeSpan = setInterval(() => {
allBullets.forEach((bullet) => {
bullet.lifespan -= 0.1;
})
childBullets.forEach((bullet) => {
bullet.lifespan -= 0.1;
})
AOEBullets.forEach((bullet) => {
bullet.lifespan -= 0.1;
})
}, 100)
setInterval(() => {
for (var i = 0; i < allBullets.length; i++) {
if (allBullets[i].pierce < 1 || allBullets[i].lifespan < 1) {
allBullets.splice(allBullets.indexOf[i], 1)
}
}
}, 50)
setInterval(() => {
for (var i = 0; i < childBullets.length; i++) {
if (childBullets[i].pierce < 1 || childBullets[i].lifespan < 0) {
childBullets.splice(childBullets.indexOf[i], 1)
}
}
}, 5000)
setInterval(() => {
for (var i = 0; i < AOEBullets.length; i++) {
if (AOEBullets[i].pierce < 1 || AOEBullets[i].lifespan < 0) {
AOEBullets.splice(AOEBullets.indexOf[i], 1)
}
}
}, 5000)
setInterval(() => {
for (var i = 0; i < 8; i++) {
allBullets.push(new MakeBullet(x, y, 10, "#0095DD", shootAngle.x + shootAngle.x * 12, shootAngle.y + shootAngle.y * 12, 10, 3, 5))
allBullets.push(new MakeBullet(x + 10, y, 10, "#0095DD", shootAngle.x + shootAngle.x * 12, shootAngle.y + shootAngle.y * 12, 10, 3, 5))
allBullets.push(new MakeBullet(x - 10, y, 10, "#0095DD", shootAngle.x + shootAngle.x * 12, shootAngle.y + shootAngle.y * 12, 10, 3, 5))
}
}, 30000)
setInterval(() => {
document.getElementsByClassName("scrore")[0].innerText = "Score: " + score;
}, 100)
setInterval(() => {
document.getElementsByClassName("scoreEarned")[0].innerText = "Score Earned: " + scoreEarned;
scoreEarned = 0;
}, 1000)
var damage = setInterval(() => {
document.getElementsByClassName("damageDealth")[0].innerText = "Damage: " + damageDealth.toFixed(1);
damageDealth = 0;
}, 1000)
<canvas id="myCanvas" height="320" width="480" style="background-color: #eee"></canvas>
<p class="scrore" style="color: rgba(0, 149, 221, 0.9); font-family: 'Hammersmith One';">Score: 0</p>
<p class="scoreEarned" style="color: rgba(0, 149, 221, 0.9); font-family: 'Hammersmith One';">Score Earned: 0</p>
<p class="damageDealth" style="color: rgba(0, 149, 221, 0.9); font-family: 'Hammersmith One';">Damage: 0</p>
One idea to speed up the drawing is to prepare the next frame to be shown on an invisible canvas. Then blip the invisible canvas to the canvas shown to the player in one go.
// drawing surface
var visibleCanvas_; // (HTML5 canvas) serves as playfield, visible for user
var visibleCtx2d_; // (HTML5 canvas 2D context) context of visible canvas
// optimization: if drawing with fillRect(), draw on hidden canvas
var hiddenCanvas_; // (HTML5 canvas) invisible canvas on which shapes are drawn each step
var hiddenCtx2d_; // (HTML5 canvas 2D context) context of invisible canvas
function createHiddenCanvas_(mainCanvas) {
hiddenCanvas_= document.createElement("canvas");
hiddenCanvas_.width = mainCanvas.width;
hiddenCanvas_.height = mainCanvas.height;
hiddenCtx2d_= hiddenCanvas_.getContext("2d");
// other settings that do not change while drawing, e.g. brick color
// hiddenCtx2d_.strokeStyle = ...;
// hiddenCtx2d_.fillStyle = ...;
}
// must be called once at startup of the game
function init_() {
visibleCanvas_ = document.getElementById('myCanvas');
visibleCtx2d_= visibleCanvas_.getContext('2d');
createHiddenCanvas_(visibleCanvas_);
}
// to be called before we start building the next frame
function startDraw_() {
// clear hidden canvas
hiddenCtx2d_.clearRect(0, 0, hiddenCanvas_.width, hiddenCanvas_.height);
}
// draws single brick for next frame on hidden canvas
function drawBrick_(brickX, brickY, brickWidth, brickHeight) {
hiddenCtx2d_.fillRect(brickX, brickY, brickWidth, brickHeight);
}
// to be called after we finish building the next frame and want to display it
function finishDraw_() {
// clear visible canvas
visibleCtx2d_.clearRect(0, 0, visibleCanvas_.width, visibleCanvas_.height);
// blip hidden canvas on visible canvas
visibleCtx2d_.drawImage(hiddenCanvas_, 0, 0);
}
Draw all shapes for the next frame between startDraw_ and finishDraw_.
You should also look into window.requestAnimationFrame to manage when the next frame is drawn.
I created a RaceTrack game, where you simply drive a car, collect yellow bonuses and avoid black obstacles.
The problem is that the animation of my car is not smooth ( When u try to go left or right etc. )
Can someone help me understand how can I do a smooth animation when steering a car ?
How could I add a spontanious curves instead of straight road?
Ps. I know my ColissionChecker() functions aren't perfect, but that is a problem for another day.
I don't know why in snippet's Full Page the canvas is very small and you can't see anything.
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var LineWidth = 10;
var LineHeight = 80;
var boundaryTopOffset = 5;
var boundaryLeftOffset = 2;
var boundaryPadding = 50;
var boundaryMiddleOffset = 2;
var speed = 50;
let executedTimer = false;
let dateDiff;
let currentScore = 0;
var leftBoundary = [];
var rightBoundary = [];
var middleBoundary = [];
var bonuses = [];
var obstacles = [];
var car = {
x: 1200,
y: 800
}
document.addEventListener('keydown', function(event) {
let key = event.which
if(key === 37) {
car.x -= speed;
} else if(key === 39) {
car.x += speed;
} else if(key === 38) {
car.y -= speed;
} else if(key === 40) {
car.y += speed;
}
})
for (x = 0; x < 8; x++) {
leftBoundary[x] =
{
offset: boundaryLeftOffset + 400,
topOffset: 0,
width: LineWidth,
height: LineHeight,
color: "red"
};
}
for (x = 0; x < 8; x++) {
middleBoundary[x] =
{
offset: boundaryMiddleOffset + 890,
topOffset: 0,
width: LineWidth,
height: LineHeight,
color: "white"
};
}
for (x = 0; x < 8; x++) {
rightBoundary[x] =
{
offset: boundaryLeftOffset + 1400,
topOffset: 0,
width: LineWidth,
height: LineHeight,
color: "red"
};
}
var cycle = 0,
totalCycle = LineHeight + boundaryPadding;
window.requestAnimationFrame(draw);
function draw() {
if(executedTimer == false) {
obstacles.push({x: Math.floor((Math.random() * 1000) + 450), y: 10});
timerStart();
}
drawCanvas(boundaryLeftOffset-2, 0, canvas.width, canvas.height, 'grey');
cycle = (cycle + 4) % totalCycle;
for (boundary of [leftBoundary, rightBoundary, middleBoundary]) {
for (i = 0; i < boundary.length; i++) {
boundary[i].topOffset = cycle + (i-1) * totalCycle;
drawBoundary(boundary[i], boundary[i].color);
}
}
if(dateDiff >= 1000) {
obstacles.push({x: Math.floor((Math.random() * 900) + 490), y: 10});
bonuses.push({x: Math.floor((Math.random() * 900) + 490), y: 10})
}
drawScore();
drawObstacle();
drawBonus();
drawCar();
obstacleColissionChecker();
bonusColissionChecker();
timerCheck();
window.requestAnimationFrame(draw);
}
function drawBoundary(x, elementColor) {
c.fillStyle = elementColor;
c.fillRect(x.offset+100, x.topOffset, x.width, x.height);
}
function drawCanvas(posX, posY, width, height, elementColor) {
c.fillStyle = elementColor;
c.fillRect(posX, posY, width, height);
}
function drawCar() {
c.fillStyle = "blue";
c.fillRect(car.x, car.y, 100, 150);
c.fillStyle = "black";
for(var i = 0; i < 101; i+=100){
c.beginPath();
c.ellipse(car.x + i, car.y + 10, 10, 15, Math.PI, 0, 2 * Math.PI);
c.ellipse(car.x + i, car.y + 140, 10, 15, Math.PI, 0, 2 * Math.PI);
c.fill();
c.closePath();
}
}
function timerStart() {
date1 = new Date();
executedTimer = true;
}
function timerCheck() {
var date2 = new Date();
dateDiff = Math.abs(date1 - date2);
if(dateDiff >= 1000)date1 = date2;
}
function drawScore() {
c.font='25px Verdana';
c.fillStyle = 'hsl('+ 0 +', 100%, 50%)';
c.fillText('Score : ' + currentScore, 100, 80);
}
function drawObstacle() {
c.fillStyle = "#080D23";
for(obstacle of [obstacles]) {
for (i = 0; i < obstacles.length; i++) {
c.fillRect(obstacle[i].x, obstacle[i].y+= 5, 80, 50);
}
}
}
function drawBonus() {
c.fillStyle = "#F2C14A";
for(bonus of [bonuses]) {
for (i = 0; i < bonuses.length; i++) {
c.beginPath();
c.arc(bonuses[i].x, bonuses[i].y+= 5, 20, 0, Math.PI * 2, false);
c.fill();
c.closePath();
}
}
}
function obstacleColissionChecker() {
for (i = 0; i < obstacles.length; i++) {
if(car.y + 20 - obstacles[i]?.y + 20 > 0 && car.y - 20 - obstacles[i]?.y + 20 < 100
&& car.x + 100 - obstacles[i]?.x + 20 > 0 && car.x - 100 - obstacles[i]?.x - 20 < 200) {
currentScore--;
}
}
}
function bonusColissionChecker() {
for (i = 0; i < bonuses.length; i++) {
if(car.y + 20 - bonuses[i]?.y + 20 > 0 && car.y - 20 - bonuses[i]?.y + 20 < 100
&& car.x + 100 - bonuses[i]?.x + 20 > 0 && car.x - 100 - bonuses[i]?.x - 20 < 200) {
currentScore++;
}
}
}
canvas {
border: 1px solid black;
margin: 0 !important;
padding: 0 !important;
}
body {
margin: 0;
}
<canvas></canvas>
In your code the speed is constant.
The car is either moving at that speed or is not moving.
This is the problem : you need to introduce acceleration.
You should car.x += speed on every frame and alter the speed in the key press handler. It would be a good start for you.
I've been making a simple game just with the HTML5 canvas and plain JavaScript. Most recently I tried adding a feature to shoot bullets which would then destroy obstacles but I've come upon a problem. Whenever you shoot a bullet it doesn't move until another is shot. Even when it starts moving and collides with one of the obstacles nothing happens. I've done some troubleshooting and I can't seem to get to the bottom of it. Any help and/or suggestions would be greatly appreciated.
Collision handling code:
for(var i = 0; i < this.bullets.length - 1; i++) { // -1 is present because whenever I remove it I get an error. I have a feeling this may be related to the order of how everything updates, will be fixed
this.bullets[i].x += this.bullets[i].speed;
if(this.bullets[i].x > cvs.width) {
this.bullets.splice(i, 1);
}
for(var j = 0; j < obstacles.length - 1; j++) { // Same here
if(rectanglesColliding(this.bullets[i], obstacles[j])) {
console.log("Collision");
}
}
}
Full player object:
var player = {
score: 0,
x: 50,
y: 150,
radius: 10,
jumping: false,
bullets: [],
velocityX: 0,
velocityY: 1,
angle: 90,
update: function() {
this.draw();
this.score++;
if(this.y + this.radius + 2 > cvs.height - 50 && this.jumping === false || this.y - this.radius + 2 < 0 + 50 && this.jumping === true) {
this.velocityY = 0;
} else if(this.jumping === false){
this.velocityY = 4;
} else {
this.velocityY = -4;
}
obstacles.forEach((obstacle) => {
if(colliding(this, obstacle)) {
gameEnded = true;
}
});
this.x += this.velocityX;
this.y += this.velocityY;
for(var i = 0; i < this.bullets.length - 1; i++) { // -1 is present because whenever I remove it I get an error. I have a feeling this may be related to the order of how everything updates, will be fixed
this.bullets[i].x += this.bullets[i].speed;
if(this.bullets[i].x > cvs.width) {
this.bullets.splice(i, 1);
}
for(var j = 0; j < obstacles.length - 1; j++) { // Same here
if(rectanglesColliding(this.bullets[i], obstacles[j])) {
console.log("Collision");
}
}
}
},
draw: function() {
this.bullets.forEach((bullet) => {
ctx.fillStyle = 'green';
ctx.fillRect(bullet.x, bullet.y, 7, 2);
});
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
}
};
Collision Detection Function:
function rectanglesColliding(rect1, rect2) {
if(rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x && // Found on Mozilla.com: https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y) return true;
else return false;
}
Shoot Function:
function shoot() {
player.bullets.push({ x: player.x + 10, y: player.y, speed: 6 });
}
Object of the game:
Survive as long as you can by avoiding the obstacles.
Controls:
Space: Go up
W: Shoot Bullet
Full code snippet:
var cvs = document.getElementById('canvas');
var ctx = cvs.getContext("2d");
var gameEnded = false;
var player = {
score: 0,
x: 50,
y: 150,
radius: 10,
jumping: false,
bullets: [],
velocityX: 0,
velocityY: 1,
angle: 90,
update: function() {
this.draw();
this.score++;
if(this.y + this.radius + 2 > cvs.height - 50 && this.jumping === false || this.y - this.radius + 2 < 0 + 50 && this.jumping === true) {
this.velocityY = 0;
} else if(this.jumping === false){
this.velocityY = 4;
} else {
this.velocityY = -4;
}
obstacles.forEach((obstacle) => {
if(colliding(this, obstacle)) {
gameEnded = true;
}
});
this.x += this.velocityX;
this.y += this.velocityY;
for(var i = 0; i < this.bullets.length - 1; i++) {
this.bullets[i].x += this.bullets[i].speed;
if(this.bullets[i].x > cvs.width) {
this.bullets.splice(i, 1);
}
for(var j = 0; j < obstacles.length - 1; j++) {
if(rectanglesColliding(this.bullets[i], obstacles[j])) {
console.log("Collision");
// obstacles.splice(i, 1);
}
}
}
},
draw: function() {
this.bullets.forEach((bullet) => {
ctx.fillStyle = 'green';
ctx.fillRect(bullet.x, bullet.y, 7, 2);
});
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
}
};
var obstacles = [{ x: 150, y: 60, width: 10, height: 20 }];
document.addEventListener('keydown', (event) => {
if(event.key === ' ') {
player.jumping = true;
}
if(event.key === 'w') {
shoot();
}
});
document.addEventListener('keyup', (event) => {
if(event.key === ' ') {
player.jumping = false;
}
});
function randomIntFromRange(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function getWidth() {
return Math.max(
document.body.scrollWidth,
document.documentElement.scrollWidth,
document.body.offsetWidth,
document.documentElement.offsetWidth,
document.documentElement.clientWidth
);
}
function getHeight() {
return Math.max(
document.body.scrollHeight,
document.documentElement.scrollHeight,
document.body.offsetHeight,
document.documentElement.offsetHeight,
document.documentElement.clientHeight
);
}
function colliding(circle, rect) {
var distX = Math.abs(circle.x - rect.x - rect.width / 2);
var distY = Math.abs(circle.y - rect.y - rect.height / 2);
if(distX > (rect.width / 2 + circle.radius)) return false;
if(distY > (rect.height / 2 + circle.radius)) return false;
if(distX <= (rect.width / 2)) return true;
if(distY <= (rect.height / 2)) return true;
}
function rectanglesColliding(rect1, rect2) {
if(rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y) {
return true;
}
}
function distance(x1, y1, x2, y2) {
const xDist = x2 - x1;
const yDist = y2 - y1;
return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2));
}
function shoot() {
player.bullets.push({ x: player.x + 10, y: player.y, speed: 6 });
}
function generateObstacle() {
var obstacle = {
x: obstacles[0].x + randomIntFromRange(cvs.width - 100, cvs.width + 70),
y: 0 + randomIntFromRange(20, 550),
width: randomIntFromRange(25, 35),
height: randomIntFromRange(10, 25)
};
for(var i = 0; i < obstacles.length; i++) {
if(distance(obstacle.x, obstacle.y, obstacles[i].x, obstacles[i].y) < 65) {
obstacle = {
x: obstacles[0].x + randomIntFromRange(cvs.width - 100, cvs.width + 70),
y: 0 + randomIntFromRange(20, 550),
width: randomIntFromRange(25, 35),
height: randomIntFromRange(10, 25)
};
i = -1;
}
}
obstacles.push({ x: obstacle.x, y: obstacle.y, width: obstacle.width, height: obstacle.height });
}
function removeBadObstacles() {
for(var i = 0; i < obstacles.length; i++) {
if(obstacles[i].x < 0) {
obstacles.splice(i, 1);
}
}
}
function updateObstacles() {
if(obstacles[obstacles.length - 1].x < cvs.width + 5) {
generateObstacle();
generateObstacle();
}
obstacles.forEach((obstacle) => {
obstacle.x -= 10;
ctx.fillStyle = 'red';
ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
});
}
function init() {
cvs.width = getWidth();
cvs.height = getHeight();
update();
}
function update() {
if(gameEnded) {
ctx.font = '50px Verdana';
return ctx.fillText('Game Over', 110, cvs.height / 2);
}
requestAnimationFrame(update);
removeBadObstacles();
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, cvs.width, cvs.height);
updateObstacles();
player.update();
ctx.font = '30px Impact';
ctx.fillText("Score: " + player.score, 20, 40);
}
init();
<!DOCTYPE html>
<html>
<head>
<title>Game</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="text/javascript" src="./JS/main.js"></script>
</body>
</html>
when iterate through a array, the last item is at length-1 (which mean you should use < length, not < length-1)
the remove code should be obstacles.splice(j, 1); (i is the index for bullet)
you do not assign size to bullet (and you may also want to use this size when draw)
var cvs = document.getElementById('canvas');
var ctx = cvs.getContext("2d");
var gameEnded = false;
var player = {
score: 0,
x: 50,
y: 150,
radius: 10,
jumping: false,
bullets: [],
velocityX: 0,
velocityY: 1,
angle: 90,
update: function() {
this.draw();
this.score++;
if(this.y + this.radius + 2 > cvs.height - 50 && this.jumping === false || this.y - this.radius + 2 < 0 + 50 && this.jumping === true) {
this.velocityY = 0;
} else if(this.jumping === false){
this.velocityY = 4;
} else {
this.velocityY = -4;
}
obstacles.forEach((obstacle) => {
if(colliding(this, obstacle)) {
gameEnded = true;
}
});
this.x += this.velocityX;
this.y += this.velocityY;
for(var i = 0; i < this.bullets.length; i++) {//<==============
this.bullets[i].x += this.bullets[i].speed;
if(this.bullets[i].x > cvs.width) {
this.bullets.splice(i, 1);
}
for(var j = 0; j < obstacles.length; j++) {//<==============
if(rectanglesColliding(this.bullets[i], obstacles[j])) {
console.log("Collision");
obstacles.splice(j, 1);//<==============
}
}
}
},
draw: function() {
this.bullets.forEach((bullet) => {
ctx.fillStyle = 'green';
ctx.fillRect(bullet.x, bullet.y, 7, 2);
});
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
}
};
var obstacles = [{ x: 150, y: 60, width: 10, height: 20 }];
document.addEventListener('keydown', (event) => {
if(event.key === ' ') {
player.jumping = true;
}
if(event.key === 'w') {
shoot();
}
});
document.addEventListener('keyup', (event) => {
if(event.key === ' ') {
player.jumping = false;
}
});
function randomIntFromRange(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function getWidth() {
return Math.max(
document.body.scrollWidth,
document.documentElement.scrollWidth,
document.body.offsetWidth,
document.documentElement.offsetWidth,
document.documentElement.clientWidth
);
}
function getHeight() {
return Math.max(
document.body.scrollHeight,
document.documentElement.scrollHeight,
document.body.offsetHeight,
document.documentElement.offsetHeight,
document.documentElement.clientHeight
);
}
function colliding(circle, rect) {
var distX = Math.abs(circle.x - rect.x - rect.width / 2);
var distY = Math.abs(circle.y - rect.y - rect.height / 2);
if(distX > (rect.width / 2 + circle.radius)) return false;
if(distY > (rect.height / 2 + circle.radius)) return false;
if(distX <= (rect.width / 2)) return true;
if(distY <= (rect.height / 2)) return true;
}
function rectanglesColliding(rect1, rect2) {
if(rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y) {
return true;
}
}
function distance(x1, y1, x2, y2) {
const xDist = x2 - x1;
const yDist = y2 - y1;
return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2));
}
function shoot() {
player.bullets.push({ x: player.x + 10, y: player.y, speed: 6, width: 7, height: 6 }); //<====================
}
function generateObstacle() {
var obstacle = {
x: obstacles[0].x + randomIntFromRange(cvs.width - 100, cvs.width + 70),
y: 0 + randomIntFromRange(20, 550),
width: randomIntFromRange(25, 35),
height: randomIntFromRange(10, 25)
};
for(var i = 0; i < obstacles.length; i++) {
if(distance(obstacle.x, obstacle.y, obstacles[i].x, obstacles[i].y) < 65) {
obstacle = {
x: obstacles[0].x + randomIntFromRange(cvs.width - 100, cvs.width + 70),
y: 0 + randomIntFromRange(20, 550),
width: randomIntFromRange(25, 35),
height: randomIntFromRange(10, 25)
};
i = -1;
}
}
obstacles.push({ x: obstacle.x, y: obstacle.y, width: obstacle.width, height: obstacle.height });
}
function removeBadObstacles() {
for(var i = 0; i < obstacles.length; i++) {
if(obstacles[i].x < 0) {
obstacles.splice(i, 1);
}
}
}
function updateObstacles() {
if(obstacles[obstacles.length - 1].x < cvs.width + 5) {
generateObstacle();
generateObstacle();
}
obstacles.forEach((obstacle) => {
obstacle.x -= 10;
ctx.fillStyle = 'red';
ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
});
}
function init() {
cvs.width = getWidth();
cvs.height = getHeight();
update();
}
function update() {
if(gameEnded) {
ctx.font = '50px Verdana';
return ctx.fillText('Game Over', 110, cvs.height / 2);
}
requestAnimationFrame(update);
removeBadObstacles();
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, cvs.width, cvs.height);
updateObstacles();
player.update();
ctx.font = '30px Impact';
ctx.fillText("Score: " + player.score, 20, 40);
}
init();
<!DOCTYPE html>
<html>
<head>
<title>Game</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="text/javascript" src="./JS/main.js"></script>
</body>
</html>
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.