javascipt "Cannot read property 'height' of undefined" error - javascript

I new at javascript and I try to develop a new game and I get this message and I didn't understand why.
I glad for your help!(The error occured on the "this.top = (Math.random() * cvs.height) / 3 + 20;" line).
const obstablesArray = [];
class Obstable {
constructor(cvs) {
this.top = (Math.random() * cvs.height) / 3 + 20;
this.bottom = (Math.random() * cvs.height) / 3 + 20;
this.x = cvs.width;
this.width = 20;
this.color = "red";
this.cvs = cvs;
}
draw(ctx) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, 0, this.width, this.top);
ctx.fillRect(
this.x,
this.cvs.height - this.bottom,
this.width,
this.bottom
);
}
update(gameSpeed) {
this.x -= gameSpeed;
this.draw();
}
}
export function handleObstacle(bird, ctx, frame) {
if (frame % 50) obstablesArray.unshift(new Obstable());
for (let i = 0; i < obstablesArray.length; i++) {
obstablesArray[i].update(this.ctx);
}
if (obstablesArray.length > 20)
for (let i = 0; i < 20; i++) {
obstablesArray.pop(obstablesArray[0]);
}
}
I creat the cvs on this class and send it:
import Bird from "/src/bird";
import { handleObstacle } from "./obstacles";
const cvs = document.getElementById("canvas1");
const ctx = cvs.getContext("2d");
cvs.width = 600;
cvs.height = 400;
let frame = 0;
let spacePress = false;
let angle = 0;
const bird = new Bird();
function animate() {
ctx.clearRect(0, 0, cvs.width, cvs.height);
bird.update(cvs, spacePress, angle);
bird.draw(ctx);
//handleParticiles(bird, ctx);
handleObstacle(bird, ctx, frame);

It's because you are not passing cvs to your class constructor here:
export function handleObstacle(bird, ctx, frame) {
if (frame % 50) obstablesArray.unshift(new Obstable()); //<-- here
...
}
And cvs is of course undefined.

Related

.png flickers while animating using drawImage in canvas javascript

I have created a simple game using Canvas in Javascript where the player has to avoid obstacles by jumping over or shrinking. The obstacle images, which are animated moving towards the left, flickers at some sort of interval. The interval increases in parallel with the gameSpeed variable, making me wonder if this might have something to do with either the spawnObstacle() or the update() functions. Another reason why I believe so is because neither the drawn playerImg.png nor the drawn Score/Highscore ever flickers. I have tried several different solutions found online, but none seem to solve my problem.
Here is my index.js and index.html below:
import {registerNewHighscore, makeList} from "./globalHs.js";
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const newGameBtn = document.getElementById("newGame");
const seeInstrBtn = document.getElementById("instrBtn");
const seeHsBtn = document.getElementById("hsBtn");
const goBackBtn = document.getElementById("backBtn");
let score;
let highscore;
let scoreText;
let highscoreText;
let player;
let gravity;
let obstacles = [];
let gameSpeed;
let keyPressed;
let isKeyPressed = false
let active = true;
let rotation = 0;
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
function createImage(path){
let image = new Image();
image.src = path;
return image;
}
const obsImg = createImage("img/chatbubble.png");
const rockImg = createImage("img/rock.png");
const roadblockImg = createImage("img/roadblock.png");
const playerImg = createImage("img/logoPlayer.png");
newGameBtn.addEventListener('click', function() {
document.getElementById("newGame").style.display = "none";
document.getElementById("header").style.display = "none";
document.getElementById("instr").style.display = "none";
document.getElementById("main").style.display = "block";
document.getElementById("instrBtn").style.display = "none";
document.getElementById("hsBtn").style.display = "none";
document.getElementById("hsBoard").style.display = "none";
start();
});
seeInstrBtn.addEventListener('click', function(){
document.getElementById("header").style.display = "none";
document.getElementById("instrBtn").style.display = "none";
document.getElementById("newGame").style.display = "none";
document.getElementById("instr").style.display = "block";
document.getElementById("instr").style.visibility = "visible";
document.getElementById("backBtn").style.display = "block";
document.getElementById("hsBtn").style.display = "none";
document.getElementById("hsBoard").style.display = "none";
document.getElementById("backBtn").style.top = "50%";
});
seeHsBtn.addEventListener('click', function(){
document.getElementById("header").style.display = "none";
document.getElementById("hsBtn").style.display = "none";
document.getElementById("newGame").style.display = "none";
document.getElementById("instrBtn").style.display = "none";
document.getElementById("instr").style.display = "none";
document.getElementById("hsBoard").style.display = "block";
document.getElementById("backBtn").style.display = "block";
document.getElementById("backBtn").style.top = "70%";
makeList();
});
goBackBtn.addEventListener('click', function() {
goBack();
});
function goBack() {
document.getElementById("backBtn").style.display = "none";
document.getElementById("instr").style.display = "none";
document.getElementById("header").style.display = "block";
document.getElementById("newGame").style.display = "block";
document.getElementById("instrBtn").style.display = "block";
document.getElementById("hsBtn").style.display = "block";
document.getElementById("hsBoard").style.display = "none";
};
document.addEventListener('keydown', function(evt) {
if (isKeyPressed) return;
isKeyPressed = true;
keyPressed = evt.code;
});
document.addEventListener('keyup', function(evt) {
if (evt.code !== keyPressed) return; // only respond to the key already pressed
isKeyPressed = false;
keyPressed = null;
});
function randomIntInRange (min, max){
return Math.round(Math.random() * (max - min) + min);
}
class Player{
constructor (x, y, r, w, h, playerImg){
this.playerImg = playerImg;
this.x = x;
this.y = y;
this.r = r;
this.w = r*2;
this.h = r*2;
this.dy = 0;
this.jumpForce = 18;
this.originalRad = r;
this.grounded = false;
this.jumpTimer = 0;
/* this.newRotation = 0; */
}
animate () {
if (['Space', 'KeyW'].includes(keyPressed)) {
this.jump();
} else{
this.jumpTimer = 0;
}
if (['ShiftLeft', 'KeyS'].includes(keyPressed)){
/* this.newRotation = rotation * 2; */
this.r = this.originalRad / 2;
this.w = this.originalRad;
this.h = this.originalRad;
} else {
/* this.newRotation = rotation; */
this.r = this.originalRad;
this.w = this.r * 2;
this.h = this.r * 2;
}
this.y += this.dy;
if (this.y + this.r < canvas.height){
this.dy += gravity;
this.grounded = false;
} else{
this.dy = 0;
this.grounded = true;
this.y = canvas.height - this.r;
}
this.draw();
}
jump () {
if (this.r != this.originalRad) return;
if (this.grounded && this.jumpTimer == 0){
this.jumpTimer = 1.5;
this.dy = -this.jumpForce;
} else if (this.jumpTimer > 0 && this.jumpTimer < 15){
this.jumpTimer++;
this.dy = -this.jumpForce - (this.jumpTimer / 50);
}
}
draw () {
ctx.translate(this.x, this.y);
ctx.rotate(rotation);
ctx.translate(-(this.x), -(this.y));
ctx.drawImage(this.playerImg, (this.x-this.r), (this.y-this.r), this.w, this.h);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
}
class Obstacle {
constructor (x, y, w, h, obsImg){
this.obsImg = obsImg;
this.x = x,
this.y = y,
this.w = w;
this.h = h;
this.dx = -gameSpeed;
obsImg.width = this.w;
obsImg.height = this.h;
}
update (){
this.x += this.dx;
this.dx = -gameSpeed;
}
draw () {
ctx.beginPath();
ctx.fillStyle = "rgba(0, 0, 0, 0)";
ctx.fillRect(this.x, this.y, this.w, this.h,);
ctx.drawImage(this.obsImg, this.x, this.y, this.w*1.1, this.h);
ctx.closePath();
}
/////// CIRCLE
/*draw () {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, (2 * Math.PI), false)
ctx.fillStyle = this.c;
ctx.fill();
ctx.closePath();
}*/
/////// ELLIPSE
/*draw () {
ctx.beginPath();
ctx.ellipse(this.x, this.y, this.radX, this.radY, 0, 0, 2 * Math.PI);
ctx.fillStyle = this.c;
ctx.fill();
ctx.stroke();
}*/
}
class Rock {
constructor (x, y, w, h, rockImg){
this.rockImg = rockImg;
this.x = x,
this.y = y,
this.w = w;
this.h = h;
this.dx = -gameSpeed;
rockImg.width = this.w;
rockImg.height = this.h;
}
update (){
this.x += this.dx;
this.dx = -gameSpeed;
}
draw () {
ctx.beginPath();
ctx.fillStyle = "rgba(0, 0, 0, 0)";
ctx.fillRect(this.x+20, this.y+40, this.w-20, this.h-40);
ctx.drawImage(this.rockImg, this.x-20, this.y, this.w*1.5, this.h*1.5);
ctx.closePath();
}
}
class Roadblock {
constructor (x, y, w, h, roadblockImg){
this.roadblockImg = roadblockImg;
this.x = x,
this.y = y,
this.w = w;
this.h = h;
this.dx = -gameSpeed;
roadblockImg.width = this.w;
roadblockImg.height = this.h;
}
update (){
this.x += this.dx;
this.dx = -gameSpeed;
}
draw () {
ctx.beginPath();
ctx.fillStyle = "rgba(0, 0, 0, 0)";
ctx.fillRect(this.x, this.y+15, this.w, this.h,);
ctx.drawImage(this.roadblockImg, this.x, this.y, this.w, this.h*1.15);
ctx.closePath();
}
}
class Text{
constructor(t, x, y, a, c, s){
this.t = t;
this.x = x;
this.y = y;
this.a = a;
this.c = c;
this.s = s;
}
draw () {
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.font = this.s + "px";
ctx.textAlign = this.a;
ctx.fillText(this.t, this.x, this.y);
ctx.closePath();
}
}
function getDistance(player, obstacle) {
var distX = Math.abs(player.x - (obstacle.x + obstacle.w / 2));
var distY = Math.abs(player.y - (obstacle.y + obstacle.h / 2));
if (distX > (obstacle.w / 2 + player.r)) { return false; }
if (distY > (obstacle.h / 2 + player.r)) { return false; }
if (distX <= (obstacle.w)) { return true; }
if (distY <= (obstacle.h)) { return true; }
var dx = distX - obstacle.w / 2;
var dy = distY - obstacle.h / 2;
return (dx * dx + dy * dy <= (player.r*player.r));
}
let initialSpawnTimer = 200;
let spawnTimer = initialSpawnTimer;
function spawnObstacle (){
let sizeX;
let sizeY;
let type = randomIntInRange(0, 2);
let obstacle = new Obstacle(
canvas.width + sizeX,
canvas.height - sizeX,
sizeX,
sizeY,
obsImg
);
if (type == 0){
sizeX = randomIntInRange(100, 160);
sizeY = sizeX / 2;
obstacle = new Rock(
canvas.width + sizeX,
canvas.height - sizeY,
sizeX,
sizeY,
rockImg
);
} else if (type == 1){
sizeX = randomIntInRange(80, 160);
sizeY = sizeX / 2;
obstacle = new Obstacle(
canvas.width + sizeX,
canvas.height - sizeX,
sizeX,
sizeY,
obsImg
);
obstacle.y -= player.originalRad + randomIntInRange(-50, 150);
} else if (type == 2){
sizeX = 150;
sizeY = sizeX / 2;
obstacle = new Roadblock(
canvas.width + sizeX,
canvas.height - sizeY,
sizeX,
sizeY,
roadblockImg
);
}
obstacles.push(obstacle);
}
function start () {
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
ctx.font = "40px Courier New";
active = true;
gameSpeed = 6;
gravity = 1;
score = 0;
highscore = 0;
if (localStorage.getItem('highscore')){
highscore = localStorage.getItem('highscore');
}
player = new Player(100, 0, 50, 100, 100, playerImg);
scoreText = new Text("Score: " + score, 45, 45, "left", "#212121", "40");
highscoreText = new Text("Highscore: " + highscore, 45,
90, "left", "gold", "40");
window.requestAnimationFrame(update);
}
let lastTime;
function update (time) {
if (lastTime != null) {
const delta = time - lastTime;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
spawnTimer--;
if (spawnTimer <= 0){
spawnObstacle();
spawnTimer = initialSpawnTimer - gameSpeed * 8;
if (spawnTimer < 60){
spawnTimer = randomIntInRange(40, 80);
}
}
for (let i = 0; i < obstacles.length; i++){
let o = obstacles[i];
o.draw();
o.update();
if (o.x + o.y < 0){
obstacles.splice(i, 1);
}
if (getDistance(player, o)) {
active = false;
obstacles = [];
spawnTimer = initialSpawnTimer;
gameSpeed = 6;
window.localStorage.setItem('highscore', highscore);
score -= 1;
highscore -= 1;
if (score >= highscore){
registerNewHighscore(highscore+1);
}
goBack();
}
}
lastTime = time;
if (active){
window.requestAnimationFrame(update);
}
player.animate();
score++;
scoreText.t = "Score: " + score;
scoreText.draw();
if (score > highscore){
highscore = score;
highscoreText.t = "Highscore: " + highscore;
}
highscoreText.draw();
rotation+=Math.PI/180 * 2 + gameSpeed * 0.01;
gameSpeed += 0.002;
}
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kindly Waiting Game V2</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="world">
<div id="header">The Kindly Game</div>
<div id="newGame" onmouseover="this.style.backgroundColor = 'goldenrod'"
onmouseout="this.style.backgroundColor= 'gold'">New Game</div>
<div id="instrBtn" onmouseover="this.style.backgroundColor = 'goldenrod'" onmouseout="this.style.backgroundColor
= 'gold'">Instructions</div>
<div id="hsBtn" onmouseover="this.style.backgroundColor = 'goldenrod'" onmouseout="this.style.backgroundColor
= 'gold'">Highscores</div>
<div id="instr" style="display: none">Avoid the <br>chatbubbles, <br>roadblocks, <br>and rocks!<br><br>
Instructions: <br>Space/W: Jump <br>ShiftLeft/S: Shrink</div>
<div id="hsBoard" style="display: none">Highscores:</div>
<div id="backBtn" onmouseover="this.style.backgroundColor = 'goldenrod'" onmouseout="this.style.backgroundColor
= 'gold'">Back</div>
<div id="main"></div>
<div id="score"></div>
<div id="cloudLarge"></div>
<div id="cloudMedium"></div>
<div id="cloudSmall"></div>
<div id="cloudSmall2"></div>
<div id="ufo"></div>
<div id="airplane"></div>
<div id="ye"></div>
</div>
<canvas id="game" width="640" height="400"></canvas>
<script src="globalHs.js" type="module"></script>
<script src="index.js" type="module"></script>
</body>
</html>
Your issue is caused by this block of code inside your update() function:
if (o.x + o.y < 0){
obstacles.splice(i, 1);
}
Now while I don't know the exact logic why you're checking the sum of the obstacles' horizontal and vertical position, you're actually removing something from an array with the .splice() method. As this is happening inside a for-loop you're actually modifying the length of the array while it might still loop over it.
You can fix this by looping over the array from the last to the first element:
for (let i = obstacles.length-1; i>=0; i--) {
let o = obstacles[i];
o.draw();
o.update();
if (o.x + o.y < 0) {
obstacles.splice(i, 1);
}
if (getDistance(player, o)) {
active = false;
obstacles = [];
spawnTimer = initialSpawnTimer;
gameSpeed = 6;
window.localStorage.setItem('highscore', highscore);
score -= 1;
highscore -= 1;
if (score >= highscore) {
registerNewHighscore(highscore + 1);
}
goBack();
}
}

How to add external javascript in react app

I made a JavaScript particle animation and it works fine on a normal html website.
I tried implementing it into my react app using react helmet/useEffect but both didn't work and I don't know what's going on.
Here is my script:
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = 'absolute';
canvas.style.width = '100%';
canvas.style.height = '100%';
const container = document.querySelector('.particles');
container.appendChild(canvas);
let particleArray;
class Particle {
constructor(x, y, dirX, dirY, size, color) {
this.x = x;
this.y = y;
this.dirX = dirX;
this.dirY = dirY;
this.size = size;
this.color = color;
this.scale = .1;
this.counter = 0;
}
draw() {
var gradient = ctx.createRadialGradient(this.x, this.y, this.size / 8, this.x, this.y, this.size);
gradient.addColorStop(0, 'white');
gradient.addColorStop(1, 'white');
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, false);
ctx.shadowColor = this.color;
ctx.shadowBlur = 10;
ctx.fillStyle = this.color;
ctx.fill();
}
update() {
if (this.x + this.size > canvas.width || this.x - this.size < 0) {
this.dirX = -this.dirX;
}
if (this.y + this.size > canvas.height || this.y - this.size < 0) {
this.dirY = -this.dirY;
}
this.size += this.scale;
this.counter += .1;
if (this.counter > 3) {
this.scale = -this.scale;
this.counter = 0;
}
this.x += this.dirX;
this.y += this.dirY;
this.draw();
}
}
function init() {
particleArray = [];
for (let i = 0; i < 25; i++) {
let size = Math.random() * 3;
let x = Math.random() * (innerWidth - size * 2);
let y = Math.random() * (innerHeight - size * 2);
let dirX = (Math.random() * .4) - .2;
let dirY = (Math.random() * .4) - .2;
let colors = ['#721240']
let color = colors[Math.floor(Math.random() * colors.length)];
particleArray.push(new Particle(x, y, dirX, dirY, size, color));
}
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, innerWidth, innerHeight);
for (let i = 0; i < particleArray.length; i++) {
particleArray[i].update();
}
}
init();
animate();
window.addEventListener('resize', () => {
canvas.width = innerWidth;
canvas.height = innerHeight;
init()
})
And here is how I tried to add it to my react app in App.js:
<div className="particles">
{useScript("./assets/particles.js")}
</div>
useScript is a react function using useEffect:
import { useEffect } from "react";
function useScript(url) {
useEffect(() => {
const script = document.createElement("script");
script.src = url;
script.async = false;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, [url]);
}
export default useScript;
Please help I'm stuck...
Instead of injecting the script, why don't you directly inject the code itself in useEffect. That's worked for me on multiple occassions
This is my proposed code. console.log() things like canvas and particleArray at multiple places to get it right
import { useEffect } from "react";
function useScript(url) {
useEffect(() => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = 'absolute';
canvas.style.width = '100%';
canvas.style.height = '100%';
const container = document.querySelector('.particles');
container.appendChild(canvas);
let particleArray;
class Particle {
constructor(x, y, dirX, dirY, size, color) {
this.x = x;
this.y = y;
this.dirX = dirX;
this.dirY = dirY;
this.size = size;
this.color = color;
this.scale = .1;
this.counter = 0;
}
draw() {
var gradient = ctx.createRadialGradient(this.x, this.y, this.size / 8, this.x, this.y, this.size);
gradient.addColorStop(0, 'white');
gradient.addColorStop(1, 'white');
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, false);
ctx.shadowColor = this.color;
ctx.shadowBlur = 10;
ctx.fillStyle = this.color;
ctx.fill();
}
update() {
if (this.x + this.size > canvas.width || this.x - this.size < 0) {
this.dirX = -this.dirX;
}
if (this.y + this.size > canvas.height || this.y - this.size < 0) {
this.dirY = -this.dirY;
}
this.size += this.scale;
this.counter += .1;
if (this.counter > 3) {
this.scale = -this.scale;
this.counter = 0;
}
this.x += this.dirX;
this.y += this.dirY;
this.draw();
}
}
function init() {
particleArray = [];
for (let i = 0; i < 25; i++) {
let size = Math.random() * 3;
let x = Math.random() * (innerWidth - size * 2);
let y = Math.random() * (innerHeight - size * 2);
let dirX = (Math.random() * .4) - .2;
let dirY = (Math.random() * .4) - .2;
let colors = ['#721240']
let color = colors[Math.floor(Math.random() * colors.length)];
particleArray.push(new Particle(x, y, dirX, dirY, size, color));
}
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, innerWidth, innerHeight);
for (let i = 0; i < particleArray.length; i++) {
particleArray[i].update();
}
}
init();
animate();
window.addEventListener('resize', () => {
canvas.width = innerWidth;
canvas.height = innerHeight;
init()
})
return ()=>{
window.removeEventListener('resize', () => {
canvas.width = innerWidth;
canvas.height = innerHeight;
init()
})
}
}, [url]);
}
export default useScript;

'this' is undefined in class method with requestAnimationFrame

I'm working on a JavaScript canvas API using an Object Oriented approach. My problem is that after using requestAnimationFrame in the animate method I get this as undefined. If I remove the requestAnimationFrame from the animate method the errors goes away - Any idea how to solve this?
Canvas.js
export default class Canvas {
constructor(canvas, ctx) {
this.canvas = canvas;
this.ctx = ctx;
this.x = 100;
this.y = 100;
this.dx = 4;
this.dy = 5;
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
drawCircle(x, y) {
for (let i = 0; i < 6; i++) {
this.ctx.beginPath();
this.ctx.fillStyle = "lightblue";
this.ctx.arc(x, y, 30, 0, Math.PI * 2);
this.ctx.fill();
}
}
animate() {
// Clear rect
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
requestAnimationFrame(this.animate);
this.x += 1;
this.drawCircle(this.x, this.y);
}
}
app.js
import Canvas from "./Canvas";
let c = document.getElementById("canvas");
let ctx = c.getContext("2d");
const obj = new Canvas(c, ctx);
obj.animate();
You might need to bind the methods to use this.
Try the below code for Canvas.js
export default class Canvas {
constructor(canvas, ctx) {
this.canvas = canvas;
this.ctx = ctx;
this.x = 100;
this.y = 100;
this.dx = 4;
this.dy = 5;
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
this.drawCircle = this.drawCircle.bind(this);
this.animate = this.animate.bind(this);
}
drawCircle(x, y) {
for (let i = 0; i < 6; i++) {
this.ctx.beginPath();
this.ctx.fillStyle = "lightblue";
this.ctx.arc(x, y, 30, 0, Math.PI * 2);
this.ctx.fill();
}
}
animate() {
// Clear rect
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
requestAnimationFrame(this.animate);
this.x += 1;
this.drawCircle(this.x, this.y);
}
}

how do i get my balls to move down the canvas after a short period of time?

I am trying to get my balls, in the array, to move directly down the canvas after a short period of time, one after the other to equal exactly 100 balls dropped. I included all of my code to try and show more of what i want the program to do.
Really the balls spawn and a user controls the paddle scoring points for each ball hit. Their are radio buttons that can be checked to make the balls fall faster or slower.
Im just stuck on getting the balls to move down the canvas one at a time.
var posY = 0;
var spawnRateOfDescent = 2;
var spawnRate = 500;
var lastSpawn = -1;
var balls = [100];
var startTime = Date.now();
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
function checkRadio() {
if (document.getElementById("moderate").checked == true) {
spawnRate = 250;
} else if (document.getElementById("hard").checked == true) {
spawnRate = 100;
} else if (document.getElementById("easy").checked == true) {
spawnRate = 500;
}
}
function startGame() {
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
baton = new batonPiece(50, 10, "28E060", 210, 250)
}
function ball() {
this.x = Math.random() * (canvas.width - 30) + 15;
this.y = 0;
this.radius = 10;
this.color = randomColor();
this.draw = function () {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
ctx.fillStyle = randomColor();
ctx.fill();
ctx.closePath();
}
}
var balls = [];
for (var i = 0; i < 100; i++) {
balls[i] = new ball();
balls[i].draw();
}
function batonPiece(width, height, color, x, y) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
function randomColor() {
var letter = "0123456789ABCDEF".split("");
var color = "#";
for (var i = 0; i < 6; i = i + 1) {
color += letter[Math.round(Math.random() * 15)];
}
return color;
}
function moveLeft() {
batonPiece.x -= 1;
}
function moveRight() {
baton.x += 1;
}
function stopMove() {
baton.x = 0;
}

How do I properly introduce a delay after a drawImage function in an html5 canvas game?

I'm making a simple memory game and I have put a short delay after 2 cards have been flipped face up and before they are checked for a match. If they are unmatched they will then flip back face down. The problem I'm having is that the delay comes before the second card is flipped face up even though it comes afterwards in the code, resulting in the second card not showing face up. I'm using the drawImage function with pre-loaded images so the call shouldn't have to wait for an image to load. I've added my code below and commented after the draw face up and delay functions.
An online version: http://dtc-wsuv.org/mscoggins/hiragana/seindex.html
var ROWS = 2;
var COLS = 3;
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
canvas.width = COLS * 80 + (COLS - 1) * 10 + 40;
canvas.height = ROWS * 100 + (ROWS - 1) * 10 + 40;
var Card = function(x, y, img) {
this.x = x;
this.y = y;
this.w = 80;
this.h = 100;
this.r = 10;
this.img = img;
this.match = false;
};
Card.prototype.drawFaceDown = function() {
this.drawCardBG();
ctx.beginPath();
ctx.fillStyle = "red";
ctx.arc(this.w / 2 + this.x, this.h / 2 + this.y, 15, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
this.isFaceUp = false;
};
Card.prototype.drawFaceUp = function() {
this.drawCardBG();
var imgW = 57;
var imgH = 70;
var imgX = this.x + (this.w - imgW) / 2;
var imgY = this.y + (this.h - imgH) / 2;
ctx.drawImage(this.img, imgX, imgY, imgW, imgH);
this.isFaceUp = true;
};
Card.prototype.drawCardBG = function() {
ctx.beginPath();
ctx.fillStyle = "white";
ctx.fillRect(this.x, this.y, this.w, this.h);
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.moveTo(this.x + this.r, this.y);
ctx.lineTo(this.w + this.x - this.r * 2, this.y);
ctx.arcTo(this.w + this.x, this.y, this.w + this.x, this.y + this.r, this.r);
ctx.lineTo(this.w + this.x, this.h + this.y - this.r * 2);
ctx.arcTo(this.w + this.x, this.h + this.y, this.w + this.x - this.r, this.h + this.y, this.r);
ctx.lineTo(this.x + this.r, this.h + this.y);
ctx.arcTo(this.x, this.h + this.y, this.x, this.h + this.y - this.r, this.r);
ctx.lineTo(this.x, this.y + this.r);
ctx.arcTo(this.x, this.y, this.x + this.r, this.y, this.r);
ctx.stroke();
ctx.closePath();
};
Card.prototype.mouseOverCard = function(x, y) {
return x >= this.x && x <= this.x + this.w && y >= this.y && y <= this.y + this.h;
};
var imgLib = [
'img/a.png', 'img/ka.png', 'img/sa.png', 'img/ta.png', 'img/na.png', 'img/ha.png',
'img/ma.png', 'img/ya.png', 'img/ra.png', 'img/wa.png', 'img/i.png', 'img/ki.png',
'img/shi.png', 'img/chi.png', 'img/ni.png', 'img/hi.png', 'img/mi.png', 'img/ri.png',
'img/u.png', 'img/ku.png', 'img/su.png', 'img/tsu.png', 'img/nu.png', 'img/hu.png',
'img/mu.png', 'img/yu.png', 'img/ru.png', 'img/n.png', 'img/e.png', 'img/ke.png',
'img/se.png', 'img/te.png', 'img/ne.png', 'img/he.png', 'img/me.png', 'img/re.png',
'img/o.png', 'img/ko.png', 'img/so.png', 'img/to.png', 'img/no.png', 'img/ho.png',
'img/mo.png', 'img/yo.png', 'img/ro.png', 'img/wo.png'
];
var imgArray = [];
imgArray = imgLib.slice();
var flippedCards = [];
var numTries = 0;
var doneLoading = function() {};
canvas.addEventListener("click", function(e) {
for(var i = 0;i < cards.length;i++) {
var mouseX = e.clientX - e.currentTarget.offsetLeft;
var mouseY = e.clientY - e.currentTarget.offsetTop;
if(cards[i].mouseOverCard(mouseX, mouseY)) {
if(flippedCards.length < 2 && !this.isFaceUp) {
cards[i].drawFaceUp(); //draw card face up
flippedCards.push(cards[i]);
if(flippedCards.length === 2) {
numTries++;
if(flippedCards[0].img.src === flippedCards[1].img.src) {
flippedCards[0].match = true;
flippedCards[1].match = true;
}
delay(600); //delay after image has been drawn
checkMatches();
}
}
}
}
var foundAllMatches = true;
for(var i = 0;i < cards.length;i++) {
foundAllMatches = foundAllMatches && cards[i].match;
}
if(foundAllMatches) {
var winText = "You Win!";
var textWidth = ctx.measureText(winText).width;
ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.font = "40px Arial";
ctx.fillText(winText, canvas.width / 2 - textWidth, canvas.height / 2);
}
}, false);
var gameImages = [];
for(var i = 0;i < ROWS * COLS / 2;i++) {
var imgId = Math.floor(Math.random() * imgArray.length);
var match = imgArray[imgId];
gameImages.push(match);
gameImages.push(match);
imgArray.splice(imgId, 1);
}
gameImages.sort(function() {
return 0.5 - Math.random();
});
var cards = [];
var loadedImages = [];
var index = 0;
var imgLoader = function(imgsToLoad, callback) {
for(var i = 0;i < imgsToLoad.length;i++) {
var img = new Image();
img.src = imgsToLoad[i];
loadedImages.push(img);
cards.push(new Card(0, 0, img));
img.onload = function() {
if(loadedImages.length >= imgsToLoad.length) {
callback();
}
};
}
for(var i = 0;i < COLS;i++) {
for(var j = 0;j < ROWS;j++) {
cards[index].x = i * 80 + i * 10 + 20;
cards[index].y = j * 100 + j * 10 + 20;
index++;
}
}
for(var i = 0;i < cards.length;i++) {
cards[i].drawFaceDown();
}
};
imgLoader(gameImages, doneLoading);
var checkMatches = function() {
for(var i = 0;i < cards.length;i++) {
if(!cards[i].match) {
cards[i].drawFaceDown();
}
}
flippedCards = [];
};
var delay = function(ms) {
var start = new Date().getTime();
var timer = false;
while(!timer) {
var now = new Date().getTime();
if(now - start > ms) {
timer = true;
}
}
};
The screen won't be refreshed until your function exits. The usual way to handle this is using setTimeout. Put the code that you want to run after the delay in a separate function and use setTimeout to call that function after the desired delay. Note that setTimeout will return immediately; the callback will be executed later.
I have implemented this below, just replace your click event listener code with the following:
canvas.addEventListener("click", function(e) {
for(var i = 0;i < cards.length;i++) {
var mouseX = e.clientX - e.currentTarget.offsetLeft;
var mouseY = e.clientY - e.currentTarget.offsetTop;
if(cards[i].mouseOverCard(mouseX, mouseY)) {
if(flippedCards.length < 2 && !this.isFaceUp) {
cards[i].drawFaceUp(); //draw card face up
flippedCards.push(cards[i]);
if(flippedCards.length === 2) {
numTries++;
if(flippedCards[0].img.src === flippedCards[1].img.src) {
flippedCards[0].match = true;
flippedCards[1].match = true;
}
//delay(600); //delay after image has been drawn
//checkMatches();
window.setTimeout(checkMatchesAndCompletion, 600);
}
}
}
}
function checkMatchesAndCompletion() {
checkMatches();
var foundAllMatches = true;
for(var i = 0;i < cards.length;i++) {
foundAllMatches = foundAllMatches && cards[i].match;
}
if(foundAllMatches) {
var winText = "You Win!";
var textWidth = ctx.measureText(winText).width;
ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.font = "40px Arial";
ctx.fillText(winText, canvas.width / 2 - textWidth, canvas.height / 2);
}
}
}, false);

Categories