Related
I'm trying to refactor a website inside a Vue 3 single page app.
Everything is working but I cannot understand which is the best way to insert and execute some javascript functions...
In my homepage, that now is a component, I use this function to create a parallax star + mountain landscape
document.addEventListener('DOMContentLoaded', () => {
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
window.setTimeout(callback, 1000 / 60);
};
window.requestAnimationFrame = requestAnimationFrame;
})();
// Terrain stuff.
var background = document.getElementById("bg-canvas")
var backgroundHome = document.getElementById("bg-home")
var bgCtx = background.getContext("2d"),
width = window.innerWidth,
height = window.innerHeight;
(height < 400) ? height = 400: height;
background.width = width;
background.height = height;
function Terrain(options) {
options = options || {};
this.terrain = document.createElement("canvas");
this.terCtx = this.terrain.getContext("2d");
this.scrollDelay = options.scrollDelay || 90;
this.lastScroll = new Date().getTime();
this.terrain.width = width;
this.terrain.height = height;
this.fillStyle = options.fillStyle || "#494776";
this.mHeight = options.mHeight || height;
// generate
this.points = [];
var displacement = options.displacement || 140,
power = Math.pow(2, Math.ceil(Math.log(width) / (Math.log(2))));
// set the start height and end height for the terrain
this.points[0] = this.mHeight; //(this.mHeight - (Math.random() * this.mHeight / 2)) - displacement;
this.points[power] = this.points[0];
// create the rest of the points
for (var i = 1; i < power; i *= 2) {
for (var j = (power / i) / 2; j < power; j += power / i) {
this.points[j] = ((this.points[j - (power / i) / 2] + this.points[j + (power / i) / 2]) / 2) + Math.floor(Math.random() * -displacement + displacement);
}
displacement *= 0.6;
}
background.after(this.terrain);
}
Terrain.prototype.update = function() {
// draw the terrain
this.terCtx.clearRect(0, 0, width, height);
this.terCtx.fillStyle = this.fillStyle;
if (new Date().getTime() > this.lastScroll + this.scrollDelay) {
this.lastScroll = new Date().getTime();
this.points.push(this.points.shift());
}
this.terCtx.beginPath();
for (var i = 0; i <= width; i++) {
if (i === 0) {
this.terCtx.moveTo(0, this.points[0]);
} else if (this.points[i] !== undefined) {
this.terCtx.lineTo(i, this.points[i]);
}
}
this.terCtx.lineTo(width, this.terrain.height);
this.terCtx.lineTo(0, this.terrain.height);
this.terCtx.lineTo(0, this.points[0]);
this.terCtx.fill();
}
// Second canvas used for the stars
bgCtx.fillStyle = '#05004c';
bgCtx.fillRect(0, 0, width, height);
// stars
function Star(options) {
this.size = Math.random() * 2;
this.speed = Math.random() * .05;
this.x = options.x;
this.y = options.y;
}
Star.prototype.reset = function() {
this.size = Math.random() * 2;
this.speed = Math.random() * .05;
this.x = width;
this.y = Math.random() * height;
}
Star.prototype.update = function() {
this.x -= this.speed;
if (this.x < 0) {
this.reset();
} else {
bgCtx.fillRect(this.x, this.y, this.size, this.size);
}
}
function ShootingStar() {
this.reset();
}
ShootingStar.prototype.reset = function() {
this.x = Math.random() * width;
this.y = 0;
this.len = (Math.random() * 80) + 10;
this.speed = (Math.random() * 10) + 6;
this.size = (Math.random() * 1) + 0.1;
// this is used so the shooting stars arent constant
this.waitTime = new Date().getTime() + (Math.random() * 3000) + 500;
this.active = false;
}
ShootingStar.prototype.update = function() {
if (this.active) {
this.x -= this.speed;
this.y += this.speed;
if (this.x < 0 || this.y >= height) {
this.reset();
} else {
bgCtx.lineWidth = this.size;
bgCtx.beginPath();
bgCtx.moveTo(this.x, this.y);
bgCtx.lineTo(this.x + this.len, this.y - this.len);
bgCtx.stroke();
}
} else {
if (this.waitTime < new Date().getTime()) {
this.active = true;
}
}
}
var entities = [];
// init the stars
for (var i = 0; i < height; i++) {
entities.push(new Star({
x: Math.random() * width,
y: Math.random() * height
}));
}
// Add 2 shooting stars that just cycle.
entities.push(new ShootingStar());
entities.push(new ShootingStar());
entities.push(new Terrain({
displacement: 150,
scrollDelay: 20,
fillStyle: "#221445",
mHeight: height / 2
}));
entities.push(new Terrain({
displacement: 120,
scrollDelay: 50,
fillStyle: "#2e1f56",
mHeight: (height / 2) - 30
}));
entities.push(new Terrain({
mHeight: (height / 2) - 60
}));
//animate background
function animate() {
bgCtx.fillStyle = '#055e8f';
bgCtx.fillRect(0, 0, width, height);
bgCtx.fillStyle = '#ffffff';
bgCtx.strokeStyle = '#ffffff';
var entLen = entities.length;
while (entLen--) {
entities[entLen].update();
}
requestAnimationFrame(animate);
}
animate();
});
Which is the most efficient way to execute this when my homepage component is mounted?
I've been making a coding project in which I simply create a 2 player shooter with asteroids. For some reason my asteroids occasionally disappear and I can't find a way to stop it. I tried some debugging strategies that i got off the internet. Forgive me for the messy code because I'm relatively new to coding and would love to have some help. This is my entire code -
const FPS = 60;
const FRICTION = 0.7;
const PROJ_DIST = 0.6;
const PROJ_EXPLODE_DUR = 0.3;
const PROJ_MAX = 3;
const PROJ_SPEED = 600;
const ROIDS_JAG = 0.4;
const ROIDS_NUM = 5;
const ROIDS_SPEED = 50;
const ROIDS_SIZE = 100;
const ROIDS_VERT = 10;
const PLAYER_SIZE = 30;
const ROT_SPEED = 250;
const PLAYER_THRUST = 5;
const PLAYER_MAX_SPEED = 12;
const SHOW_BOUNDING = false;
const SHOW_CENTER = false;
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;
addEventListener("resize", () => {
canvas.width = innerWidth
canvas.height = innerHeight
})
class Player {
constructor(x, y, r, c, a, pc, p, scoreEl) {
this.x = x;
this.y = y;
this.or = r;
this.r = r;
this.p = p
this.c = c;
this.a = a
this.rot = 0
this.pc = pc
this.v = {
x: 0,
y: 0
}
this.thrusting = false
this.projectiles = []
this.destroy = []
this.top = {
x: this.x + 4 / 3 * this.r * Math.cos(this.a),
y: this.y - 4 / 3 * this.r * Math.sin(this.a)
}
this.left = {
x: this.x - this.r * (2 / 3 * Math.cos(this.a) + Math.sin(this.a)),
y: this.y + this.r * (2 / 3 * Math.sin(this.a) - Math.cos(this.a))
}
this.right = {
x: this.x - this.r * (2 / 3 * Math.cos(this.a) - Math.sin(this.a)),
y: this.y + this.r * (2 / 3 * Math.sin(this.a) + Math.cos(this.a))
}
this.thrustdir = 0
this.canShoot = false
this.scoreEl = scoreEl
this.score = 0
this.exploding = false;
}
draw() {
ctx.fillStyle = this.c
ctx.beginPath();
ctx.moveTo(this.top.x, this.top.y);
ctx.lineTo(this.left.x, this.left.y);
ctx.lineTo(this.right.x, this.right.y);
ctx.closePath();
ctx.fill();
}
update() {
this.setPoints();
this.move();
this.edge()
this.Update();
this.scoreEl.innerHTML = this.score
this.draw()
}
rotate(dir) {
this.rot = toRadians(ROT_SPEED * dir) / FPS
}
move() {
if (!this.exploding) {
if (this.thrusting) {
this.v.x += (Math.abs(this.v.x) < PLAYER_MAX_SPEED) ? PLAYER_THRUST * this.thrustdir * Math.cos(this.a) / FPS : 0;
this.v.y -= (Math.abs(this.v.y) < PLAYER_MAX_SPEED) ? PLAYER_THRUST * this.thrustdir * Math.sin(this.a) / FPS : 0;
} else {
this.v.x -= FRICTION * this.v.x / FPS
this.v.y -= FRICTION * this.v.y / FPS
}
this.a += this.rot
this.x += this.v.x
this.y += this.v.y
}
}
thrust(dir) {
this.thrustdir = dir;
this.thrusting = (dir == 0) ? false : true
}
edge() {
if (this.x < 0 - this.r) {
this.x = canvas.width + this.r
} else if (this.x > canvas.width + this.r) {
this.x = 0 - this.r
}
if (this.y < 0 - this.r) {
this.y = canvas.height + this.r
} else if (this.y > canvas.height + this.r) {
this.y = 0 - this.r
}
}
setPoints() {
this.top = {
x: this.x + 4 / 3 * this.r * Math.cos(this.a),
y: this.y - 4 / 3 * this.r * Math.sin(this.a)
}
this.left = {
x: this.x - this.r * (2 / 3 * Math.cos(this.a) + Math.sin(this.a)),
y: this.y + this.r * (2 / 3 * Math.sin(this.a) - Math.cos(this.a))
}
this.right = {
x: this.x - this.r * (2 / 3 * Math.cos(this.a) - Math.sin(this.a)),
y: this.y + this.r * (2 / 3 * Math.sin(this.a) + Math.cos(this.a))
}
}
Update() {
this.projectiles.forEach((projectile, index) => {
projectile.update();
if (projectile.kill()) {
this.projectiles.splice(index, 1)
}
asteroids.forEach((asteroid, index2) => {
this.explode(asteroid, projectile, false, () => {
asteroids.splice(index2, 1)
var radius = Math.random() * (ROIDS_SIZE - 4) + 4
if (Math.random() < 0.5) {
var x = Math.random() < 0.5 ? 0 - radius * 2 : canvas.width + radius * 2
var y = Math.random() * canvas.height
} else {
var x = Math.random() * canvas.width
var y = Math.random() < 0.5 ? 0 - radius * 2 : canvas.height + radius * 2
}
asteroids.push(new Asteroid(x, y, radius, {
x: Math.random() * ROIDS_SPEED / FPS * (Math.random() < 0.5 ? 1 : -1),
y: Math.random() * ROIDS_SPEED / FPS * (Math.random() < 0.5 ? 1 : -1)
}))
},
(asteroid) => {
if (!this.exploding) {
this.score += 5
}
this.projectiles.splice(index, 1)
if (asteroid.r > 35) {
gsap.to(asteroid, 0.5, {
r: asteroid.r - 30
}).then(asteroid.or = asteroid.r)
return false;
}
return true;
})
})
});
asteroids.forEach((asteroid, index) => {
this.explode(this, asteroid, true, () => {
this.respawn(this);
}, () => {
this.v = {
x: 0,
y: 0
}
})
})
this.projectiles.forEach((projectile, index) => {
this.explode(players[this.p == 0 ? 1 : 0], projectile, false, () => {
this.respawn(players[this.p == 0 ? 1 : 0]);
console.log("hit")
}, () => {
if (!players[this.p == 0 ? 1 : 0].exploding) {
this.score += 10
};
players[this.p == 0 ? 1 : 0].v = {
x: 0,
y: 0
};
this.projectiles.splice(index, 1);
return true
})
})
}
shoot() {
if (this.canShoot && this.projectiles.length < PROJ_MAX) {
this.projectiles.push(new Projectile(this.top.x, this.top.y, 5, this.pc, {
x: PROJ_SPEED * Math.cos(this.a) / FPS,
y: -PROJ_SPEED * Math.sin(this.a) / FPS,
}));
this.canShoot = false
}
}
respawn(object) {
if (object.exploding) {
object.v = {
x: 0,
y: 0
}
do {
var x = Math.random() < 0.5
var y = Math.random() < 0.5
object.x = x ? object.or * 2 : canvas.width - object.or * 2
object.y = y ? object.or * 4 : canvas.height - object.or * 2
} while (this.hitAsteroid(object.x, object.y))
object.a = this.getStartAngle(x, y)
gsap.to(object, {
r: object.or
})
object.exploding = false
}
}
explode(object1, object2, place, t = function() {}, f = function(object1 = null, object2 = null, object3 = null) {
return false
}) {
if (this.hit(object1, object2)) {
var x = place ? object1.x : object2.x
var y = place ? object1.y : object2.y
for (var i = 0; i < object1.r * 2; i++) {
particles.push(new Particle(x, y, Math.random() * 2, object1.c, {
x: (Math.random() - 0.5) * object1.r / 15,
y: (Math.random() - 0.5) * object1.r / 15
}))
}
if (f(object1, object2, this)) {
gsap.to(object1, 0.5, {
r: 0
}).then(t)
}
object1.exploding = true
}
}
hit(object1, object2) {
return Math.hypot(object2.x - object1.x, object2.y - object1.y) < object2.r + object1.r
}
hitAsteroid(x, y) {
asteroids.forEach((asteroid) => {
if (this.hit({x: x, y: y}, asteroid)) {
return true
}
})
return false;
}
getStartAngle(x, y) {
if (x && y) {
return toRadians(315)
} else if (x) {
return toRadians(45)
} else if (y) {
return toRadians(225)
} else {
return toRadians(135)
}
}
}
class Projectile {
constructor(x, y, r, c, v) {
this.x = x;
this.y = y;
this.r = r;
this.c = c;
this.v = v;
this.dist = 0
}
draw() {
ctx.fillStyle = this.c
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
}
update() {
this.x += this.v.x
this.y += this.v.y
this.dist += Math.hypot(this.v.x, this.v.y)
this.edge();
this.draw()
}
kill() {
return (this.dist > PROJ_DIST * canvas.width)
}
edge() {
if (this.x < 0 - this.r) {
this.x = canvas.width + this.r
} else if (this.x > canvas.width + this.r) {
this.x = 0 - this.r
}
if (this.y < 0 - this.r) {
this.y = canvas.height + this.r
} else if (this.y > canvas.height + this.r) {
this.y = 0 - this.r
}
}
}
class Asteroid {
constructor(x, y, r, v) {
this.x = x;
this.y = y;
this.r = r;
this.c = `hsl(${Math.random() * 360}, 50%, 50%)`;
this.v = v;
this.a = toRadians(Math.random() * 360);
this.vert = Math.floor(Math.random() * (ROIDS_VERT + 1) + ROIDS_VERT / 2);
this.offset = this.createOffset();
this.or = r;
}
draw() {
ctx.fillStyle = this.c
ctx.beginPath();
ctx.moveTo(
this.x + this.r * this.offset[0] * Math.cos(this.a),
this.y + this.r * this.offset[0] * Math.sin(this.a)
);
for (var j = 1; j < this.vert; j++) {
ctx.lineTo(
this.x + this.r * this.offset[j] * Math.cos(this.a + j * Math.PI * 2 / this.vert),
this.y + this.r * this.offset[j] * Math.sin(this.a + j * Math.PI * 2 / this.vert)
)
}
ctx.closePath();
ctx.fill();
}
update() {
this.x += this.v.x
this.y += this.v.y
this.edge()
this.draw()
}
edge() {
if (this.x < 0 - this.r) {
this.x = canvas.width + this.r
} else if (this.x > canvas.width + this.r) {
this.x = 0 - this.r
}
if (this.y < 0 - this.r) {
this.y = canvas.height + this.r
} else if (this.y > canvas.height + this.r) {
this.y = 0 - this.r
}
}
createOffset() {
var off = [];
for (var i = 0; i < this.vert; i++) {
off.push(Math.random() * ROIDS_JAG * 2 + 1 - ROIDS_JAG)
}
return off
}
hit(projectile) {
return (Math.hypot(projectile.x - this.x, projectile.y - this.y) < this.r + projectile.r)
}
}
class Particle {
constructor(x, y, r, c, v) {
this.x = x;
this.y = y;
this.r = r;
this.c = c;
this.v = v;
this.al = 1
}
draw() {
ctx.save()
ctx.globalAlpha = this.al
ctx.fillStyle = this.c
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
ctx.restore();
}
update() {
this.draw()
this.x += this.v.x
this.y += this.v.y
this.al -= 0.01
}
}
const player1 = new Player(canvas.width / 2, canvas.height / 2, PLAYER_SIZE / 2, "blue", toRadians(Math.random() * 360), "blue", 0, document.getElementById("score1"));
const player2 = new Player(canvas.width / 2, canvas.height / 2, PLAYER_SIZE / 2, "red", toRadians(Math.random() * 360), "red", 1, document.getElementById("score2"));
const asteroids = []
for (var i = 0; i < ROIDS_NUM; i++) {
var radius = Math.random() * (ROIDS_SIZE - 4) + 4
if (Math.random() < 0.5) {
var x = Math.random() < 0.5 ? 0 - radius : canvas.width + radius
var y = Math.random() * canvas.height
} else {
var x = Math.random() * canvas.width
var y = Math.random() < 0.5 ? 0 - radius : canvas.height + radius
}
asteroids.push(new Asteroid(x, y, radius, {
x: Math.random() * ROIDS_SPEED / FPS * (Math.random() < 0.5 ? 1 : -1),
y: Math.random() * ROIDS_SPEED / FPS * (Math.random() < 0.5 ? 1 : -1)
}))
}
const particles = []
const players = [player1, player2]
addEventListener("keydown", (event) => {
switch (event.keyCode) {
case 32:
player1.shoot()
break;
case 37:
player1.rotate(1)
break;
case 39:
player1.rotate(-1)
break;
case 38:
player1.thrust(1)
break;
case 40:
player1.thrust(-1)
break;
case 69:
player2.shoot()
break;
case 65:
player2.rotate(1)
break;
case 68:
player2.rotate(-1)
break;
case 87:
player2.thrust(1)
break;
case 83:
player2.thrust(-1)
break;
}
});
addEventListener("keyup", (event) => {
switch (event.keyCode) {
case 37:
case 39:
player1.rot = 0
break;
case 38:
case 40:
player1.thrust(0);
case 32:
player1.canShoot = true;
case 65:
case 68:
player2.rot = 0
break;
case 87:
case 83:
player2.thrust(0);
case 69:
player2.canShoot = true;
}
});
var animationId;
function animate() {
animationId = requestAnimationFrame(animate);
ctx.fillStyle = "rgba(0, 0, 0, 0.1)"
ctx.fillRect(0, 0, canvas.width, canvas.height);
asteroids.forEach((asteroid) => {
asteroid.update()
})
particles.forEach((particle, index) => {
if (particle.al <= 0) {
particles.splice(index, 1)
} else {
particle.update();
}
})
player1.update();
player2.update()
}
function toRadians(a) {
return a / 180 * Math.PI
}
animate();
body {
margin: 0;
height: 100vh;
width: 100vw;
}
.scores {
width: 100vw;
}
#score1 {
display: inline-flex;
flex: right;
}
#score2 {
position: absolute;
top: 0;
left: calc(100vw - 4rem);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="./tailwind.min.css">
<title>Asteroids</title>
</head>
<body>
<div class="flex scores w-full">
<div class="fixed text-white ml-4 mt-1 text-4xl text-left" id="score1">0</div>
<div class="fixed text-white ml-4 mt-1 text-4xl text-right" id="score2">0</div>
</div>
<canvas id="gameCanvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js" integrity="sha512-IQLehpLoVS4fNzl7IfH8Iowfm5+RiMGtHykgZJl9AWMgqx0AmJ6cRWcB+GaGVtIsnC4voMfm8f2vwtY+6oPjpQ==" crossorigin="anonymous"></script>
<script src="./game.js"></script>
</body>
</html>
I'm caught in a logic knot with this game :(. I simply want to remove the explosions from the screen after say 1 second of each doing its loop. As you can see below they run at the frame rate of the game loop. This is the only way I could animate the explosion - by setting a sprite to move at the speed of the game loop (frame rate).
I don't understand how to connect a separate animation going at a different speed to this same canvas context that is being essentially cleared every frame.. I can't even figure out how to stop the explosion sprite loop.
I've tried creating a separate method drawExplosion() in the Explosion class and using setInterval in the Explosion constructor but it never likes the context I connect it to and throws this error:
Cannot read property 'drawImage' of undefined (i.e. the context is undefined)
If someone could just stop each explosion loop after 1 second I would understand where I went off course
The outline of the code is this:
class Entity
class Ball extends Entity
class Explosion extends Entity
class Enemy extends Entity
class Paddle extends Entity
class InputsManager
class mouseMoveHandler
class Game
const canvas = document.querySelector('canvas')
const game = new Game(canvas)
game.start()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
background-color: rgb(214, 238, 149);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
}
canvas {
background: url("https://picsum.photos/200");
width: 100%;
background-size: cover;
}
</style>
</head>
<body>
<canvas height="459"></canvas>
</body>
<script>
class Entity {
constructor(x, y) {
this.dead = false;
this.collision = 'none'
this.x = x
this.y = y
}
update() { console.warn(`${this.constructor.name} needs an update() function`) }
draw() { console.warn(`${this.constructor.name} needs a draw() function`) }
isDead() { console.warn(`${this.constructor.name} needs an isDead() function`) }
static testCollision(a, b) {
if (a.collision === 'none') {
console.warn(`${a.constructor.name} needs a collision type`)
return undefined
}
if (b.collision === 'none') {
d
console.warn(`${b.constructor.name} needs a collision type`)
return undefined
}
if (a.collision === 'circle' && b.collision === 'circle') {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2) < a.radius + b.radius
}
if (a.collision === 'circle' && b.collision === 'rect' || a.collision === 'rect' && b.collision === 'circle') {
let circle = a.collision === 'circle' ? a : b
let rect = a.collision === 'rect' ? a : b
// this is a waaaaaay simplified collision that just works in this case (circle always comes from the bottom)
const topOfBallIsAboveBottomOfRect = circle.y - circle.radius <= rect.y + rect.height
const bottomOfBallIsBelowTopOfRect = circle.y + circle.radius >= rect.y - rect.height
const ballIsRightOfRectLeftSide = circle.x + circle.radius >= rect.x - rect.width / 4
const ballIsLeftOfRectRightSide = circle.x - circle.radius <= rect.x + rect.width
return topOfBallIsAboveBottomOfRect && bottomOfBallIsBelowTopOfRect && ballIsRightOfRectLeftSide && ballIsLeftOfRectRightSide
}
console.warn(`there is no collision function defined for a ${a.collision} and a ${b.collision}`)
return undefined
}
static testBallCollision(ball) {
const topOfBallIsAboveBottomOfRect = ball.y - ball.radius <= this.y + this.height / 2
const bottomOfBallIsBelowTopOfRect = ball.y + ball.radius >= this.y - this.height / 2
const ballIsRightOfRectLeftSide = ball.x - ball.radius >= this.x - this.width / 2
const ballIsLeftOfRectRightSide = ball.x + ball.radius <= this.x + this.width / 2
return topOfBallIsAboveBottomOfRect && bottomOfBallIsBelowTopOfRect && ballIsRightOfRectLeftSide && ballIsLeftOfRectRightSide
}
}
class Ball extends Entity {
constructor(x, y) {
super(x, y)
this.dead = false;
this.collision = 'circle'
this.speed = 300 // px per second
this.radius = 2.5 // radius in px
this.color = '#fff'
this.ballsDistanceY = 12
}
update({ deltaTime }) {
// Ball still only needs deltaTime to calculate its update
this.y -= this.speed * deltaTime / 1000 // deltaTime is ms so we divide by 1000
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath()
context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
context.beginPath()
context.arc(this.x, this.y + this.ballsDistanceY, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
context.beginPath()
context.arc(this.x, this.y - this.ballsDistanceY, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
}
isDead(enemy) {
const outOfBounds = this.y < 0 - this.radius
const collidesWithEnemy = Entity.testCollision(enemy, this)
if (outOfBounds) {
return true
}
if (collidesWithEnemy) {
//console.log('dead')
this.dead = true;
game.hitEnemy();
return true
}
}
}
class Explosion extends Entity {
constructor(x, y, contextFromGameObject){
super(x, y)
this.contextFromGameObject = contextFromGameObject
this.imgExplosion = new Image();
this.imgExplosion.src = "https://i.ibb.co/9Ggfzxr/explosion.png";
this.totalNumberOfFrames = 12 // ten images in the image (see the url above)
this.spriteFrameNumber = 0 // This is changed to make the sprite animate
this.widthOfSprite = 1200 // this.imgExplosion.width; // find the width of the image
this.heightOfSprite = 100 // this.imgExplosion.height; // find the height of the image
this.widthOfSingleImage = this.widthOfSprite / this.totalNumberOfFrames; // The width of each image in the spirite
//this.timerId = setInterval(this.explode.bind(this), 100)
this.scaleExplosion = 0.5
//this.timerId = setInterval(this.drawExplosion, 100);
}
// drawExplosion(){
// console.log(this.spriteFrameNumber)
// //ctx.clearRect(0, 0, 500, 500)
// this.spriteFrameNumber += 1; // changes the sprite we look at
// this.spriteFrameNumber = this.spriteFrameNumber % this.totalNumberOfFrames; // Change this from 0 to 1 to 2 ... upto 9 and back to 0 again, then 1...
// this.contextFromGameObject.drawImage(this.imgExplosion,
// this.spriteFrameNumber * this.widthOfSingleImage, 0, // x and y - where in the sprite
// this.widthOfSingleImage, this.heightOfSprite, // width and height
// this.x - 25, this.y - 25, // x and y - where on the screen
// this.widthOfSingleImage, this.heightOfSprite // width and height
// );
// if (this.spriteFrameNumber > 9) {
// clearInterval(this.timerId)
// };
// }
/** #param {CanvasRenderingContext2D} context */
draw(context, frameNumber) {
console.log(frameNumber)
//ctx.clearRect(0, 0, 500, 500)
this.spriteFrameNumber += 1; // changes the sprite we look at
this.spriteFrameNumber = this.spriteFrameNumber % this.totalNumberOfFrames; // Change this from 0 to 1 to 2 ... upto 9 and back to 0 again, then 1...
context.drawImage(this.imgExplosion,
this.spriteFrameNumber * this.widthOfSingleImage, 0, // x and y - where in the sprite
this.widthOfSingleImage, this.heightOfSprite, // width and height
this.x - 25, this.y - 25, // x and y - where on the screen
this.widthOfSingleImage, this.heightOfSprite // width and height
);
}
update() {
}
isDead(ball, isDead) {
if(isDead == 'true'){
clearTimeout(this.timerId);
return true
}
return false
}
}
class Enemy extends Entity {
constructor(x, y) {
super(x, y)
this.collision = 'rect'
this.height = 50;
this.width = 50;
this.speedVar = 4;
this.speed = this.speedVar;
this.color = '#EC3AC8';
this.color2 = '#000000';
this.y = y;
this.imgEnemy = new Image();
this.imgEnemy.src = "https://i.ibb.co/kgXsr66/question.png";
this.runCount = 1;
this.timerId = setInterval(this.movePosish.bind(this), 1000);
}
movePosish() {
//console.log(this.runCount)
// x 10 -> 240
// y 10 -> 300
switch (this.runCount) {
case 0:
this.x = 20; this.y = 200;
break
case 1:
this.x = 200; this.y = 300;
break
case 2:
this.x = 30; this.y = 20;
break
case 3:
this.x = 230; this.y = 150;
break
case 4:
this.x = 200; this.y = 20;
break
case 5:
this.x = 30; this.y = 90;
break
case 6:
this.x = 240; this.y = 20;
break
case 7:
this.x = 30; this.y = 150;
break
case 8:
this.x = 180; this.y = 170;
break
case 9:
this.x = 30; this.y = 50;
break
case 10:
this.x = 130; this.y = 170;
break
}
//if 10th image remove image and clear timer
this.runCount += 1;
if (this.runCount > 10) {
//clearInterval(this.timerId)
this.runCount = 0;
console.log('ya missed 10 of em')
};
}
update() {
// //Moving left/right
// this.x += this.speed;
// if (this.x > canvas.width - this.width) {
// this.speed -= this.speedVar;
// }
// if (this.x === 0) {
// this.speed += this.speedVar;
// }
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
// context.beginPath();
// context.rect(this.x, this.y, this.width, this.height);
// context.fillStyle = this.color2;
// context.fill();
// context.closePath();
context.drawImage(this.imgEnemy, this.x, this.y);
}
isDead(enemy) {
//// collision detection
// const collidesWithEnemy = Entity.testCollision(enemy, ball)
// if (collidesWithEnemy){
// console.log('enemy dead')
// game.hitEnemy();
// return true
// }
// if (ball.dead){
// console.log('enemy dead')
// game.hitEnemy();
// return true
// }
return false
}
}
class Paddle extends Entity {
constructor(x, width) {
super(x, width)
this.collision = 'rect'
this.speed = 200
this.height = 25
this.width = 30
this.color = "#74FCEF"
}
update({ deltaTime, inputs, mouse }) {
// Paddle needs to read both deltaTime and inputs
// if mouse inside canvas AND not on mobile
if (mouse.insideCanvas) {
this.x = mouse.paddleX
} else {
this.x += this.speed * deltaTime / 1000 * inputs.direction
// stop from going off screen
if (this.x < this.width / 2) {
this.x = this.width / 2;
} else if (this.x > canvas.width - this.width / 2) {
this.x = canvas.width - this.width / 2
}
}
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath();
context.rect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height);
context.fillStyle = this.color;
context.fill();
context.closePath();
context.beginPath();
context.rect(this.x - this.width / 12, this.y - this.height / 1.1, this.width / 6, this.height);
context.fillStyle = this.color;
context.fill();
context.closePath();
}
isDead() { return false }
}
class InputsManager {
constructor() {
this.direction = 0 // this is the value we actually need in out Game object
window.addEventListener('keydown', this.onKeydown.bind(this))
window.addEventListener('keyup', this.onKeyup.bind(this))
}
onKeydown(event) {
switch (event.key) {
case 'ArrowLeft':
this.direction = -1
break
case 'ArrowRight':
this.direction = 1
break
}
}
onKeyup(event) {
switch (event.key) {
case 'ArrowLeft':
if (this.direction === -1) // make sure the direction was set by this key before resetting it
this.direction = 0
break
case 'ArrowRight':
this.direction = 1
if (this.direction === 1) // make sure the direction was set by this key before resetting it
this.direction = 0
break
}
}
}
class mouseMoveHandler {
constructor() {
// this.paddleWidth = paddleWidth;
this.x = 0;
this.paddleX = 0;
//this.canvas = canvas;
document.addEventListener("mousemove", this.onMouseMove.bind(this), false);
}
//'relative to canvas width' mouse position snippet
getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect(), // abs. size of element
scaleX = canvas.width / rect.width, // relationship bitmap vs. element for X
scaleY = canvas.height / rect.height; // relationship bitmap vs. element for Y
//console.log('canvas width = ' + canvas.width)
return {
x: (evt.clientX - rect.left) * scaleX, // scale mouse coordinates after they have
y: (evt.clientY - rect.top) * scaleY // been adjusted to be relative to element
}
}
onMouseMove(e) {
//console.log('moving')
//this.x = 100;
this.x = this.getMousePos(canvas, e).x; //relative x on canvas
this.y = this.getMousePos(canvas, e).y; //relative x on canvas
this.insideCanvas = false;
if (this.x > 0 && this.x < canvas.width) {
if (this.y > 0 && this.y < canvas.height) {
//console.log('inside')
this.insideCanvas = true;
} else {
this.insideCanvas = false;
}
}
if (this.x - 20 > 0 && this.x < canvas.width - 20) {
this.paddleX = this.x;
}
}
}
class Game {
/** #param {HTMLCanvasElement} canvas */
constructor(canvas) {
this.entities = [] // contains all game entities (Balls, Paddles, ...)
this.context = canvas.getContext('2d')
this.newBallInterval = 900 // ms between each ball
this.lastBallCreated = -Infinity // timestamp of last time a ball was launched
this.paddleWidth = 50
this.isMobile = false
this.frameNumber = 0;
}
endGame() {
//clear all elements, remove h-hidden class from next frame, then remove h-hidden class from the cta content
console.log('endgame')
const endGame = true;
game.loop(endGame)
}
hitEnemy() {
//this.timerId = setTimeout(endExplosion(), 500);
this.explosion = new Explosion(this.enemy.x, this.enemy.y, this.context)
this.entities.push(this.explosion)
}
start() {
this.lastUpdate = performance.now()
this.enemy = new Enemy(140, 220)
this.entities.push(this.enemy)
// we store the new Paddle in this.player so we can read from it later
this.player = new Paddle(150, 400)
// but we still add it to the entities list so it gets updated like every other Entity
this.entities.push(this.player)
//start watching inputs
this.inputsManager = new InputsManager()
//start watching mousemovement
this.mouseMoveHandler = new mouseMoveHandler()
//start game loop
this.loop()
}
update() {
// calculate time elapsed
const newTime = performance.now()
const deltaTime = newTime - this.lastUpdate
this.isMobile = window.matchMedia('(max-width: 1199px)');
// we now pass more data to the update method so that entities that need to can also read from our InputsManager
const frameData = {
deltaTime,
inputs: this.inputsManager,
mouse: this.mouseMoveHandler,
context: this.context
}
// update every entity
this.entities.forEach(entity => entity.update(frameData))
// other update logic (here, create new entities)
if (this.lastBallCreated + this.newBallInterval < newTime) {
// this is quick and dirty, you should put some more thought into `x` and `y` here
this.ball = new Ball(this.player.x, 360)
this.entities.push(this.ball)
this.lastBallCreated = newTime
}
// remember current time for next update
this.lastUpdate = newTime
}
cleanup() {
//console.log(this.entities[0])//Enemy
//console.log(this.entities[1])//Paddle
//console.log(this.entities[2])//Ball
//to prevent memory leak, don't forget to cleanup dead entities
this.entities.forEach(entity => {
if (entity.isDead(this.enemy)) {
const index = this.entities.indexOf(entity)
this.entities.splice(index, 1)
}
})
}
draw() {
//draw entities
this.entities.forEach(entity => entity.draw(this.context, this.frameNumber))
}
//main game loop
loop(endGame) {
this.myLoop = requestAnimationFrame(() => {
this.frameNumber += 1;
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height)
if (endGame) {
cancelAnimationFrame(this.myLoop);
this.endGame()
return;
}
this.update()
this.draw()
this.cleanup()
this.loop()
})
}
}
const canvas = document.querySelector('canvas')
const game = new Game(canvas)
game.start()
</script>
</html>
What you might want to do is to use a boolean that stays true as long as your animation should be running, it will call the function that draws your explosion.
class Entity {
constructor(x, y) {
this.dead = false;
this.collision = 'none'
this.x = x
this.y = y
}
update() { console.warn(`${this.constructor.name} needs an update() function`) }
draw() { console.warn(`${this.constructor.name} needs a draw() function`) }
isDead() { console.warn(`${this.constructor.name} needs an isDead() function`) }
static testCollision(a, b) {
if (a.collision === 'none') {
console.warn(`${a.constructor.name} needs a collision type`)
return undefined
}
if (b.collision === 'none') {
d
console.warn(`${b.constructor.name} needs a collision type`)
return undefined
}
if (a.collision === 'circle' && b.collision === 'circle') {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2) < a.radius + b.radius
}
if (a.collision === 'circle' && b.collision === 'rect' || a.collision === 'rect' && b.collision === 'circle') {
let circle = a.collision === 'circle' ? a : b
let rect = a.collision === 'rect' ? a : b
// this is a waaaaaay simplified collision that just works in this case (circle always comes from the bottom)
const topOfBallIsAboveBottomOfRect = circle.y - circle.radius <= rect.y + rect.height
const bottomOfBallIsBelowTopOfRect = circle.y + circle.radius >= rect.y - rect.height
const ballIsRightOfRectLeftSide = circle.x + circle.radius >= rect.x - rect.width / 4
const ballIsLeftOfRectRightSide = circle.x - circle.radius <= rect.x + rect.width
return topOfBallIsAboveBottomOfRect && bottomOfBallIsBelowTopOfRect && ballIsRightOfRectLeftSide && ballIsLeftOfRectRightSide
}
console.warn(`there is no collision function defined for a ${a.collision} and a ${b.collision}`)
return undefined
}
static testBallCollision(ball) {
const topOfBallIsAboveBottomOfRect = ball.y - ball.radius <= this.y + this.height / 2
const bottomOfBallIsBelowTopOfRect = ball.y + ball.radius >= this.y - this.height / 2
const ballIsRightOfRectLeftSide = ball.x - ball.radius >= this.x - this.width / 2
const ballIsLeftOfRectRightSide = ball.x + ball.radius <= this.x + this.width / 2
return topOfBallIsAboveBottomOfRect && bottomOfBallIsBelowTopOfRect && ballIsRightOfRectLeftSide && ballIsLeftOfRectRightSide
}
}
class Ball extends Entity {
constructor(x, y) {
super(x, y)
this.dead = false;
this.collision = 'circle'
this.speed = 300 // px per second
this.radius = 2.5 // radius in px
this.color = '#fff'
this.ballsDistanceY = 12
}
update({ deltaTime }) {
// Ball still only needs deltaTime to calculate its update
this.y -= this.speed * deltaTime / 1000 // deltaTime is ms so we divide by 1000
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath()
context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
context.beginPath()
context.arc(this.x, this.y + this.ballsDistanceY, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
context.beginPath()
context.arc(this.x, this.y - this.ballsDistanceY, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
}
isDead(enemy) {
const outOfBounds = this.y < 0 - this.radius
const collidesWithEnemy = Entity.testCollision(enemy, this)
if (outOfBounds) {
return true
}
if (collidesWithEnemy) {
//console.log('dead')
this.dead = true;
game.hitEnemy();
return true
}
}
}
class Explosion extends Entity {
constructor(x, y, contextFromGameObject){
super(x, y)
this.contextFromGameObject = contextFromGameObject
this.imgExplosion = new Image();
this.imgExplosion.src = "https://i.ibb.co/9Ggfzxr/explosion.png";
this.totalNumberOfFrames = 12 // ten images in the image (see the url above)
this.spriteFrameNumber = 0 // This is changed to make the sprite animate
this.widthOfSprite = 1200 // this.imgExplosion.width; // find the width of the image
this.heightOfSprite = 100 // this.imgExplosion.height; // find the height of the image
this.widthOfSingleImage = this.widthOfSprite / this.totalNumberOfFrames; // The width of each image in the spirite
//this.timerId = setInterval(this.explode.bind(this), 100)
this.scaleExplosion = 0.5
this.explosionHappened = 0;
//this.timerId = setInterval(this.drawExplosion, 100);
}
// drawExplosion(){
// console.log(this.spriteFrameNumber)
// //ctx.clearRect(0, 0, 500, 500)
// this.spriteFrameNumber += 1; // changes the sprite we look at
// this.spriteFrameNumber = this.spriteFrameNumber % this.totalNumberOfFrames; // Change this from 0 to 1 to 2 ... upto 9 and back to 0 again, then 1...
// this.contextFromGameObject.drawImage(this.imgExplosion,
// this.spriteFrameNumber * this.widthOfSingleImage, 0, // x and y - where in the sprite
// this.widthOfSingleImage, this.heightOfSprite, // width and height
// this.x - 25, this.y - 25, // x and y - where on the screen
// this.widthOfSingleImage, this.heightOfSprite // width and height
// );
// if (this.spriteFrameNumber > 9) {
// clearInterval(this.timerId)
// };
// }
/** #param {CanvasRenderingContext2D} context */
draw(context, frameNumber) {
//console.log(frameNumber)
if(this.explosionHappened)
{
//ctx.clearRect(0, 0, 500, 500)
this.spriteFrameNumber += 1; // changes the sprite we look at
this.spriteFrameNumber = this.spriteFrameNumber % this.totalNumberOfFrames; // Change this from 0 to 1 to 2 ... upto 9 and back to 0 again, then 1...
context.drawImage(this.imgExplosion,
this.spriteFrameNumber * this.widthOfSingleImage, 0, // x and y - where in the sprite
this.widthOfSingleImage, this.heightOfSprite, // width and height
this.x - 25, this.y - 25, // x and y - where on the screen
this.widthOfSingleImage, this.heightOfSprite // width and height
);
this.explosionHappened=this.spriteFrameNumber;
}
}
update() {
}
isDead(ball, isDead) {
if(isDead == 'true'){
clearTimeout(this.timerId);
return true
}
return false
}
}
class Enemy extends Entity {
constructor(x, y) {
super(x, y)
this.collision = 'rect'
this.height = 50;
this.width = 50;
this.speedVar = 4;
this.speed = this.speedVar;
this.color = '#EC3AC8';
this.color2 = '#000000';
this.y = y;
this.imgEnemy = new Image();
this.imgEnemy.src = "https://i.ibb.co/kgXsr66/question.png";
this.runCount = 1;
this.timerId = setInterval(this.movePosish.bind(this), 1000);
}
movePosish() {
//console.log(this.runCount)
// x 10 -> 240
// y 10 -> 300
switch (this.runCount) {
case 0:
this.x = 20; this.y = 200;
break
case 1:
this.x = 200; this.y = 300;
break
case 2:
this.x = 30; this.y = 20;
break
case 3:
this.x = 230; this.y = 150;
break
case 4:
this.x = 200; this.y = 20;
break
case 5:
this.x = 30; this.y = 90;
break
case 6:
this.x = 240; this.y = 20;
break
case 7:
this.x = 30; this.y = 150;
break
case 8:
this.x = 180; this.y = 170;
break
case 9:
this.x = 30; this.y = 50;
break
case 10:
this.x = 130; this.y = 170;
break
}
//if 10th image remove image and clear timer
this.runCount += 1;
if (this.runCount > 10) {
//clearInterval(this.timerId)
this.runCount = 0;
console.log('ya missed 10 of em')
};
}
update() {
// //Moving left/right
// this.x += this.speed;
// if (this.x > canvas.width - this.width) {
// this.speed -= this.speedVar;
// }
// if (this.x === 0) {
// this.speed += this.speedVar;
// }
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
// context.beginPath();
// context.rect(this.x, this.y, this.width, this.height);
// context.fillStyle = this.color2;
// context.fill();
// context.closePath();
context.drawImage(this.imgEnemy, this.x, this.y);
}
isDead(enemy) {
//// collision detection
// const collidesWithEnemy = Entity.testCollision(enemy, ball)
// if (collidesWithEnemy){
// console.log('enemy dead')
// game.hitEnemy();
// return true
// }
// if (ball.dead){
// console.log('enemy dead')
// game.hitEnemy();
// return true
// }
return false
}
}
class Paddle extends Entity {
constructor(x, width) {
super(x, width)
this.collision = 'rect'
this.speed = 200
this.height = 25
this.width = 30
this.color = "#74FCEF"
}
update({ deltaTime, inputs, mouse }) {
// Paddle needs to read both deltaTime and inputs
// if mouse inside canvas AND not on mobile
if (mouse.insideCanvas) {
this.x = mouse.paddleX
} else {
this.x += this.speed * deltaTime / 1000 * inputs.direction
// stop from going off screen
if (this.x < this.width / 2) {
this.x = this.width / 2;
} else if (this.x > canvas.width - this.width / 2) {
this.x = canvas.width - this.width / 2
}
}
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath();
context.rect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height);
context.fillStyle = this.color;
context.fill();
context.closePath();
context.beginPath();
context.rect(this.x - this.width / 12, this.y - this.height / 1.1, this.width / 6, this.height);
context.fillStyle = this.color;
context.fill();
context.closePath();
}
isDead() { return false }
}
class InputsManager {
constructor() {
this.direction = 0 // this is the value we actually need in out Game object
window.addEventListener('keydown', this.onKeydown.bind(this))
window.addEventListener('keyup', this.onKeyup.bind(this))
}
onKeydown(event) {
switch (event.key) {
case 'ArrowLeft':
this.direction = -1
break
case 'ArrowRight':
this.direction = 1
break
}
}
onKeyup(event) {
switch (event.key) {
case 'ArrowLeft':
if (this.direction === -1) // make sure the direction was set by this key before resetting it
this.direction = 0
break
case 'ArrowRight':
this.direction = 1
if (this.direction === 1) // make sure the direction was set by this key before resetting it
this.direction = 0
break
}
}
}
class mouseMoveHandler {
constructor() {
// this.paddleWidth = paddleWidth;
this.x = 0;
this.paddleX = 0;
//this.canvas = canvas;
document.addEventListener("mousemove", this.onMouseMove.bind(this), false);
}
//'relative to canvas width' mouse position snippet
getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect(), // abs. size of element
scaleX = canvas.width / rect.width, // relationship bitmap vs. element for X
scaleY = canvas.height / rect.height; // relationship bitmap vs. element for Y
//console.log('canvas width = ' + canvas.width)
return {
x: (evt.clientX - rect.left) * scaleX, // scale mouse coordinates after they have
y: (evt.clientY - rect.top) * scaleY // been adjusted to be relative to element
}
}
onMouseMove(e) {
//console.log('moving')
//this.x = 100;
this.x = this.getMousePos(canvas, e).x; //relative x on canvas
this.y = this.getMousePos(canvas, e).y; //relative x on canvas
this.insideCanvas = false;
if (this.x > 0 && this.x < canvas.width) {
if (this.y > 0 && this.y < canvas.height) {
//console.log('inside')
this.insideCanvas = true;
} else {
this.insideCanvas = false;
}
}
if (this.x - 20 > 0 && this.x < canvas.width - 20) {
this.paddleX = this.x;
}
}
}
class Game {
/** #param {HTMLCanvasElement} canvas */
constructor(canvas) {
this.entities = [] // contains all game entities (Balls, Paddles, ...)
this.context = canvas.getContext('2d')
this.newBallInterval = 900 // ms between each ball
this.lastBallCreated = -Infinity // timestamp of last time a ball was launched
this.paddleWidth = 50
this.isMobile = false
this.frameNumber = 0;
}
endGame() {
//clear all elements, remove h-hidden class from next frame, then remove h-hidden class from the cta content
console.log('endgame')
const endGame = true;
game.loop(endGame)
}
hitEnemy() {
//this.timerId = setTimeout(endExplosion(), 500);
this.explosion = new Explosion(this.enemy.x, this.enemy.y, this.context)
this.explosion.explosionHappened=1;
this.entities.push(this.explosion)
}
start() {
this.lastUpdate = performance.now()
this.enemy = new Enemy(140, 220)
this.entities.push(this.enemy)
// we store the new Paddle in this.player so we can read from it later
this.player = new Paddle(150, 400)
// but we still add it to the entities list so it gets updated like every other Entity
this.entities.push(this.player)
//start watching inputs
this.inputsManager = new InputsManager()
//start watching mousemovement
this.mouseMoveHandler = new mouseMoveHandler()
//start game loop
this.loop()
}
update() {
// calculate time elapsed
const newTime = performance.now()
const deltaTime = newTime - this.lastUpdate
this.isMobile = window.matchMedia('(max-width: 1199px)');
// we now pass more data to the update method so that entities that need to can also read from our InputsManager
const frameData = {
deltaTime,
inputs: this.inputsManager,
mouse: this.mouseMoveHandler,
context: this.context
}
// update every entity
this.entities.forEach(entity => entity.update(frameData))
// other update logic (here, create new entities)
if (this.lastBallCreated + this.newBallInterval < newTime) {
// this is quick and dirty, you should put some more thought into `x` and `y` here
this.ball = new Ball(this.player.x, 360)
this.entities.push(this.ball)
this.lastBallCreated = newTime
}
// remember current time for next update
this.lastUpdate = newTime
}
cleanup() {
//console.log(this.entities[0])//Enemy
//console.log(this.entities[1])//Paddle
//console.log(this.entities[2])//Ball
//to prevent memory leak, don't forget to cleanup dead entities
this.entities.forEach(entity => {
if (entity.isDead(this.enemy)) {
const index = this.entities.indexOf(entity)
this.entities.splice(index, 1)
}
})
}
draw() {
//draw entities
this.entities.forEach(entity => entity.draw(this.context, this.frameNumber))
}
//main game loop
loop(endGame) {
this.myLoop = requestAnimationFrame(() => {
this.frameNumber += 1;
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height)
if (endGame) {
cancelAnimationFrame(this.myLoop);
this.endGame()
return;
}
this.update()
this.draw()
this.cleanup()
this.loop()
})
}
}
const canvas = document.querySelector('canvas')
const game = new Game(canvas)
game.start()
body {
background-color: rgb(214, 238, 149);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
}
canvas {
background: url("https://picsum.photos/200");
width: 100%;
background-size: cover;
}
<canvas height="459"></canvas>
just for an additionnal information, i myself am a fan of ecs architecture and in order to achieve an explosion, or any other game mechanism cleanly you can use systems, there is an(experimental) firefox project that allows you to use entity component system architecture called ecsy
https://ecsy.io
When I'm trying to run ctx.fillRect() from class method im getting undefined when passing class object properties.
``javascript
export default classs Reel{
constructor(gameWidth,gameHeight){
this.width = 50;
this.height = 50;
this.position = {
x: gameWIdth / 2 - this.width / 2;
y: gameHeight / 2 - this.height / 2;
}
}
draw(ctx){
ctx.fillRect(this.position.x, this.position.y, this.width, this.height);
}
}
``
It should draw a rectangle on canvas.
I am following this tutorial and trying to implement it on my website. I am struggling to remove the 'Black' background to show the full background image with the stars.
Here's jsFiddle: jFiddle
Here's the screen shot below.
Here's the jQuery for your inspection:
width = window.innerWidth,
height = 300;
// Add 2 shooting stars that just cycle.
entities.push(new ShootingStar());
entities.push(new ShootingStar());
//animate background
function animate() {
bgCtx.fillStyle = "black";
bgCtx.fillRect(0, 0, width, height);
bgCtx.fillStyle = '#fff';
bgCtx.strokeStyle = "#fff";
var entLen = entities.length;
while (entLen--) {
entities[entLen].update();
}
requestAnimationFrame(animate);
}
animate();
Any help would be great! thanks :)
In the animate()function, replace
bgCtx.fillStyle = "black";
bgCtx.fillRect(0, 0, width, height);
with
bgCtx.fillStyle = "rgba(0,0,0,0)";
bgCtx.clearRect(0, 0, width, height);
and remove this line bgCtx.fillRect(0, 0, width, height);(l80)
http://jsfiddle.net/aernk8kk/13/
(function () {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60);
};
window.requestAnimationFrame = requestAnimationFrame;
})();
// Terrain stuff.
var background = document.getElementById("bgCanvas"),
bgCtx = background.getContext("2d"),
width = window.innerWidth,
height = 300;
(height < 400) ? height = 400 : height;
background.width = width;
background.height = height;
function Terrain(options) {
options = options || {};
this.terrain = document.createElement("canvas");
this.terCtx = this.terrain.getContext("2d");
this.scrollDelay = options.scrollDelay || 90;
this.lastScroll = new Date().getTime();
this.terrain.width = width;
this.terrain.height = height;
this.fillStyle = options.fillStyle || "#f30";
this.mHeight = options.mHeight || height;
// generate
this.points = [];
var displacement = options.displacement || 140,
power = Math.pow(2, Math.ceil(Math.log(width) / (Math.log(2))));
// set the start height and end height for the terrain
this.points[0] = this.mHeight;//(this.mHeight - (Math.random() * this.mHeight / 2)) - displacement;
this.points[power] = this.points[0];
// create the rest of the points
for (var i = 1; i < power; i *= 2) {
for (var j = (power / i) / 2; j < power; j += power / i) {
this.points[j] = ((this.points[j - (power / i) / 2] + this.points[j + (power / i) / 2]) / 2) + Math.floor(Math.random() * -displacement + displacement);
}
displacement *= 0.6;
}
document.body.appendChild(this.terrain);
}
Terrain.prototype.update = function () {
// draw the terrain
this.terCtx.clearRect(0, 0, width, height);
this.terCtx.fillStyle = this.fillStyle;
if (new Date().getTime() > this.lastScroll + this.scrollDelay) {
this.lastScroll = new Date().getTime();
this.points.push(this.points.shift());
}
this.terCtx.beginPath();
for (var i = 0; i <= width; i++) {
if (i === 0) {
this.terCtx.moveTo(0, this.points[0]);
} else if (this.points[i] !== undefined) {
this.terCtx.lineTo(i, this.points[i]);
}
}
this.terCtx.lineTo(width, this.terrain.height);
this.terCtx.lineTo(0, this.terrain.height);
this.terCtx.lineTo(0, this.points[0]);
this.terCtx.fill();
}
// Second canvas used for the stars
bgCtx.fillStyle = "rgb(255,255,255)";
// stars
function Star(options) {
this.size = Math.random() * 2;
this.speed = Math.random() * .05;
this.x = options.x;
this.y = options.y;
}
Star.prototype.reset = function () {
this.size = Math.random() * 2;
this.speed = Math.random() * .05;
this.x = width;
this.y = Math.random() * height;
}
Star.prototype.update = function () {
this.x -= this.speed;
if (this.x < 0) {
this.reset();
} else {
bgCtx.fillRect(this.x, this.y, this.size, this.size);
}
}
function ShootingStar() {
this.reset();
}
ShootingStar.prototype.reset = function () {
this.x = Math.random() * width;
this.y = 0;
this.len = (Math.random() * 80) + 10;
this.speed = (Math.random() * 10) + 6;
this.size = (Math.random() * 1) + 0.1;
// this is used so the shooting stars arent constant
this.waitTime = new Date().getTime() + (Math.random() * 3000) + 500;
this.active = false;
}
ShootingStar.prototype.update = function () {
if (this.active) {
this.x -= this.speed;
this.y += this.speed;
if (this.x < 0 || this.y >= height) {
this.reset();
} else {
bgCtx.lineWidth = this.size;
bgCtx.beginPath();
bgCtx.moveTo(this.x, this.y);
bgCtx.lineTo(this.x + this.len, this.y - this.len);
bgCtx.stroke();
}
} else {
if (this.waitTime < new Date().getTime()) {
this.active = true;
}
}
}
var entities = [];
// init the stars
for (var i = 0; i < height; i++) {
entities.push(new Star({
x: Math.random() * width,
y: Math.random() * height
}));
}
// Add 2 shooting stars that just cycle.
entities.push(new ShootingStar());
entities.push(new ShootingStar());
//animate background
function animate() {
bgCtx.fillStyle = "rgba(0,0,0,0)";
bgCtx.clearRect(0, 0, width, height);
bgCtx.fillStyle = '#fff';
bgCtx.strokeStyle = "#fff";
var entLen = entities.length;
while (entLen--) {
entities[entLen].update();
}
requestAnimationFrame(animate);
}
animate();
body, html {
color: #fff;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background: url(
http://cdn1.dottech.org/wp-content/uploads/2013/08/moon_clear_sky_wallpaper_2560x1440.jpg
) no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
canvas {
position:absolute;
top:0;
left:0
}
<canvas id="bgCanvas"></canvas>
check clearRect