I'm building a space invaders game and the code below works, the ball is fired and if it hits the enemy the isDead() function is triggered which also switches dead to true for that ball and this is also passed to the enemy class so it causes the enemy to get destroyed. however when I increase the ball frequency the enemy isDead function fails to run, I'm really not sure why when the ball interval is higher this whole system breaks.
when this.newBallInterval = 700
the enemy square dies
when this.newBallInterval = 600
it doesn't
why? and how to fix?
<!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;
}
canvas {
background-color: aquamarine;
}
</style>
</head>
<body>
<canvas height="300" width="300"></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 = 10 // radius in px
}
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 = "#1ee511";
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;
return true
}
}
}
class Enemy extends Entity {
constructor(x, y) {
super(x, y)
this.collision = 'rect'
this.height = 50;
this.width = 50;
this.speed = 0;
this.y = y;
}
update() {
this.x += this.speed;
if (this.x > canvas.width - this.width) {
this.speed -= 5;
}
if (this.x === 0) {
this.speed += 5;
}
}
/** @param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath();
context.rect(this.x, this.y, this.width, this.height);
context.fillStyle = "#9995DD";
context.fill();
context.closePath();
}
isDead(enemy, ball) {
//// 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
}
}
}
class Paddle extends Entity {
constructor(x, width) {
super(150, 300)
this.collision = 'rect'
this.speed = 200
this.height = 10
this.width = 50
}
update({ deltaTime, inputs }) {
// Paddle needs to read both deltaTime and inputs
this.x += this.speed * deltaTime / 1000 * inputs.direction
}
/** @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 = "#0095DD";
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 Game {
/** @param {HTMLCanvasElement} canvas */
constructor(canvas) {
this.entities = [] // contains all game entities (Balls, Paddles, ...)
this.context = canvas.getContext('2d')
this.newBallInterval = 700 // ms between each ball
this.lastBallCreated = -Infinity // timestamp of last time a ball was launched
}
endGame() {
//clear all elements, remove h-hidden class from next frame, then remove h-hidden class from the cta content
console.log('end game')
}
hitEnemy() {
const endGame = 1;
game.loop(endGame)
}
start() {
this.lastUpdate = performance.now()
this.enemy = new Enemy(100, 20)
this.entities.push(this.enemy)
// we store the new Paddle in this.player so we can read from it later
this.player = new Paddle()
// 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 game loop
this.loop()
}
update() {
// calculate time elapsed
const newTime = performance.now()
const deltaTime = newTime - this.lastUpdate
// 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,
}
// 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, 280)
this.entities.push(this.ball)
this.lastBallCreated = newTime
}0
//draw entities
this.entities.forEach(entity => entity.draw(this.context))
// remember current time for next update
this.lastUpdate = newTime
}
cleanup() {
//to prevent memory leak, don't forget to cleanup dead entities
this.entities.forEach(entity => {
if (entity.isDead(this.enemy, this.ball)) {
const index = this.entities.indexOf(entity)
this.entities.splice(index, 1)
}
})
}
//main game loop
loop(endGame) {
this.myLoop = requestAnimationFrame(() => {
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height)
if(endGame){
cancelAnimationFrame(this.myLoop);
this.endGame();
return;
}
this.update()
this.cleanup()
this.loop()
})
}
}
const canvas = document.querySelector('canvas')
const game = new Game(canvas)
game.start()
</script>
</html>
from Game loop failing when speed of ball is faster
No comments:
Post a Comment