微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

对游戏循环动画运行单独的画布动画

如何解决对游戏循环动画运行单独的画布动画

我被这个游戏困扰:(。我只是想在每个循环进行1秒钟后从屏幕上删除爆炸。如您所见,它们以帧的帧速率运行游戏循环。这是我动画爆炸的唯一方法-通过设置精灵以游戏循环的速度(帧速率)移动。

我不明白如何将以不同速度运行的单独动画连接到同一画布上下文,而该画布上下文实际上已在每一帧中清除。我什至不知道如何停止爆炸精灵循环。

我尝试在Explosion类中创建单独的方法drawExplosion(),并在Explosion构造函数中使用setInterval,但它从不喜欢我将其连接到的上下文并抛出此错误

 Cannot read property 'drawImage' of undefined (i.e. the context is undefined)

如果有人可以在1秒后停止每个爆炸循环,我就会明白我偏离航向的地方

代码的轮廓是这样的:

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,2 * Math.PI)
      context.fillStyle = this.color;
      context.fill()

      context.beginPath()
      context.arc(this.x,this.y + this.ballsdistanceY,this.y - this.ballsdistanceY,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,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,// 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,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,// x and y - where in the sprite
        this.widthOfSingleImage,// width and height
        this.x - 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)
      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.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.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,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>

解决方法

您可能想做的就是使用一个布尔值,只要您的动画应该运行,它就保持为真,它将调用绘制爆炸的函数。

<form name="quiz" id="quiz">
        <ol>
            <li>
                <div class="q1">
                    <p><strong>The value of \(\frac{1}{{{{\log }_4}120}} + \frac{1}{{{{\log }_5}120}} + \frac{1}{{{{\log }_6}120}}\) is</strong><br><br>
                        <input type="radio" name="question1" value="0">A.0<br>
                        <input type="radio" name="question1" value="1">B.1<br>
                        <input type="radio" name="question1" value="24">C.24<br>
                        <input type="radio" name="question1" value="120">D.120<br>
                    </p>
                </div>
                <div>
                    <button class="play">Play Solution</button>
                </div>
            </li>
            

            <li>
                <div>
                    <p><strong>For a 3x3 matrix A,|A| = 4 and adj A = \(\left( {\begin{array}{*{20}{c}}1&p&3\\1&3&3\\2&4&4\end{array}} \right)\),then the value of p is</strong><br><br>
                        <input type="radio" name="question2" value="4">A.4<br>
                        <input type="radio" name="question2" value="11">B.11<br>
                        <input type="radio" name="question2" value="5">C.5<br>
                        <input type="radio" name="question2" value="0">D.0<br>
                    </p>
                </div>
            </li>
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,2 * Math.PI)
      context.fillStyle = this.color;
      context.fill()

      context.beginPath()
      context.arc(this.x,this.y + this.ballsDistanceY,this.y - this.ballsDistanceY,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,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,// 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,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,// x and y - where in the sprite
        this.widthOfSingleImage,// width and height
        this.x - 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)
      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.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.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,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;
    }

仅出于补充信息,我本人是ECS体系结构的忠实拥护者,为了实现爆炸效果,或者您可以干净地使用任何其他游戏机制,可以使用系统,这里有一个(实验性)firefox项目,可让您使用实体组件系统架构称为ecsy

https://ecsy.io

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。