我在下面包含了一个有效的代码段(如果您想看到全部效果,只需用小精灵替换红色矩形;基本上,每次我重播时,小精灵动画也会加快速度)。似乎每次单击重播按钮时,不仅精灵,而且背景和前景的滚动速度也在加快(为简单起见,由于使用了本地,我省略了代码段中渲染的背景和前景。图像,但是它具有与精灵加速相同的视觉效果。我已经仔细检查了我是如何处理播放器文件中的帧数的,而且看起来还不错,数字并没有什么疯狂的事情。该错误可能在哪里发生?

注意:要进入游戏结束/重播按钮,您需要让绿色方块与红色方块发生碰撞。您需要按几次重播才能看到加速错误。由于某种原因,只有在重播按钮上点击2-3次后,它才会发生。为了获得更好的视觉效果,请确保以全屏方式查看代码段!



// Util functions file
const Util = {
  // Find distance between two points.
  dist(pos1, pos2) {
    return Math.sqrt(
      Math.pow(pos1[0] - pos2[0], 2) + Math.pow(pos1[1] - pos2[1], 2)
    );
  },
  inherits(ChildClass, BaseClass) {
    ChildClass.prototype = Object.create(BaseClass.prototype);
    ChildClass.prototype.constructor = ChildClass;
  },
  // Gets a random number
  randomNum(max, min) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }
};

// Game file
const MAX_ENEMIES = 10;

class Game {
  // Constructor for game
  constructor(gameCtx, gameCanvas, backgroundCtx, backgroundCanvas, foregroundCtx, foregroundCanvas) {
    // Setting context and canvas
    this.gameCtx = gameCtx;
    this.gameCanvas = gameCanvas;

    // Setting up game objects
    this.dino = [];
    this.enemies = [];

    // Setting game assets
    this.addDino();

    // Setting game state
    this.gameOver = false;
    this.paused = false;
    this.timeInterval = 0;

    // Binding class methods
    this.draw = this.draw.bind(this);
    this.keyDownListener = this.keyDownListener.bind(this);
    this.keyUpListener = this.keyUpListener.bind(this);

    // Setting keypresses
    this.setKeypresses();
  }

  // Adding dino player to the game
  addDino() {
    const dino = new Dino({
      position: [30, this.gameCanvas.height - 25],
      canvas: this.gameCanvas,
      ctx: this.gameCtx,
      game: this
    });

    this.add(dino);

    return dino;
  }

  // Adding enemies to the game
  // change time interval === for difficulty level
  addEnemies() {
    this.timeInterval += 1;

    if (this.timeInterval === 20 && this.enemies.length < MAX_ENEMIES) {
      this.add(new Enemy({ game: this }));
      this.timeInterval = 0;
    }
  }

  // Adding objects to respective arrays
  add(object) {
    if (object instanceof Dino) {
      this.dino.push(object);
    } else if (object instanceof Enemy) {
      this.enemies.push(object);
    } else {
      throw new Error('Unknown type of object');
    }
  };

  // Removing objects from respective arrays
  remove(object) {
    if (object instanceof Enemy) {
      this.enemies.splice(this.enemies.indexOf(object), 1);
    } else {
      throw new Error('Unknown type of object');
    }
  }

  // Checking to see if the position is out of bounds
  isOutOfBounds(pos, type) {
    let result;

    if (type === 'enemy') {
      result = pos[0] < 0;
    }

    return result;
  };

  // Gets a random position
  randomPosition() {
    return [
      this.gameCanvas.width + Util.randomNum(50, 150),
      this.gameCanvas.height - Util.randomNum(10, 20)
    ];
  };

  // Setting keypresses
  setKeypresses() {
    this.gameCanvas.addEventListener('keydown', this.keyDownListener);
    this.gameCanvas.addEventListener('keyup', this.keyUpListener);
  }

  // Handler for key down
  keyDownListener(e) {
    const dino = this.dino[0];
    e.preventDefault();

    // Array of valid key codes
    const validKeys = ['ArrowUp', 'ArrowDown', 'Space'];

    if (!this.gameOver) {
      // Prevents continuous actions when key is held down
      if (e.repeat) {
        if (e.code !== 'ArrowDown') {
          dino.toggleDirection('idle');
        } else {
          return;
        }
      } else if (validKeys.includes(e.code)) {
        dino.toggleDirection(`${e.code}`);
      }
    }
  }

  // Handler for key up
  keyUpListener(e) {
    const dino = this.dino[0];
    e.preventDefault();
    dino.toggleDirection('idle');
  }

  // Storing all moving game objects in an array
  allObjects() {
    return [].concat(this.dino, this.enemies);
  }

  // Updates objects
  updateObjects(ctx) {
    this.allObjects().forEach(object => object.update(ctx));
  }


  // Checking player collsions
  checkPlayerCollisions() {
    const dino = this.dino;
    const enemies = this.enemies;

    for (let i = 0; i < enemies.length; i++) {
      const obj1 = dino[0];
      const obj2 = enemies[i];

      if (obj1.collidedWith(obj2)) {
        const collision = obj1.collidedWith(obj2);
        if (collision) {
          this.gameOver = true;
          return;
        }
      }
    }
  }

  // Drawing the game
  draw(ctx) {
    ctx.clearRect(0, 0, this.gameCanvas.width, this.gameCanvas.height);

    // Adding enemies to game
    this.addEnemies();
  }

  // Replays a new game
  replay() {
    const dino = this.dino[0];

    document.getElementById('game-canvas').focus();

    // Resetting game variables
    this.gameOver = false;
    this.timeInterval = 0;
    dino.frames = 0;
    dino.gameOver = false;
    this.enemies = [];

    this.start();
  }

  // temp start function for game
  start() {
    if (!this.gameOver) {
      this.draw(this.gameCtx);
      this.updateObjects(this.gameCtx);
      this.checkPlayerCollisions();
      requestAnimationFrame(this.start.bind(this));
    } else {
      const gameOver = new GameOverMenu({ game: this });
      gameOver.draw();
    }
  }
}


// Dino player file
// Constants
const DINO_WIDTH = 24;
const DINO_HEIGHT = 24;

// Creating arrays for sprite walking, jumping, and crouching
let walk = [];
let jump = [];
let crouch = [];
let hit = [];

for (let i = 4; i < 10; i++) {
  walk.push([DINO_WIDTH * i, 0, DINO_WIDTH, DINO_HEIGHT]);
}

jump = [[DINO_WIDTH * 11, 0, DINO_WIDTH, DINO_HEIGHT]];

for (let i = 18; i < 24; i++) {
  crouch.push([DINO_WIDTH * i, 0, DINO_WIDTH, DINO_HEIGHT]);
}

// Populating hit array
for (let i = 14; i < 17; i++) {
  hit.push([DINO_WIDTH * i, 0, DINO_WIDTH, DINO_HEIGHT]);
}

hit.push([DINO_WIDTH * 7, 0, DINO_WIDTH, DINO_HEIGHT]);
hit.push([DINO_WIDTH * 8, 0, DINO_WIDTH, DINO_HEIGHT]);
hit.push([DINO_WIDTH * 9, 0, DINO_WIDTH, DINO_HEIGHT]);

const SPRITES = {
  walk,
  jump,
  crouch,
  hit
};

class Dino {
  // Constructor for dino
  constructor(options) {
    // Setting player positioning and action
    this.position = options.position;
    this.canvas = options.canvas;
    this.ctx = options.ctx;
    this.game = options.game;
    this.frames = 0;
    this.direction = 'idle';

    // Setting game state boolean
    this.gameOver = false;

    // Setting new HTML img element
    // eventually add different dino color selection here...
    this.dino = new Image();

    // Preventing browser(s) from smoothing out/blurring lines
    this.ctx.mozImageSmoothingEnabled = false;
    this.ctx.webkitImageSmoothingEnabled = false;
    this.ctx.msImageSmoothingEnabled = false;
    this.ctx.imageSmoothingEnabled = false;

    this.dino.src = '../dist/assets/spritesheets/red_dino.png';

    // Setting jump counter and boolean
    this.jumps = 0;
    this.isJumping = false;
  }

  // Toggles direction boolean
  toggleDirection(direction) {
    this.direction = direction;

    if (this.direction === 'ArrowUp') {
      this.isJumping = true;
    }
  }

  // Gets the correct sprite
  getSprite() {
    // if (!this.gameOver) {
      if (this.gameOver) {
        return this.getHitSprite(SPRITES.hit);
      } else if (!this.onGround() || this.direction === 'ArrowUp') {
        return SPRITES.jump[0];
      } else if (this.direction === 'idle') {
        return this.getIdleSprite(SPRITES.walk);
      } else if (this.direction === 'ArrowDown' || this.direction === 'Space') {
        return this.getCrouchSprite(SPRITES.crouch);
      }
    // }
  }

  // Jumping action
  jump() {
    const gravity = 0.6;
    let jumpStrength = 9;

    if (this.isJumping) {
      if (this.jumps === 0 || !this.onGround()) {
        this.position[1] -= jumpStrength - gravity * this.jumps;
        this.jumps += 1;
      } else {
        this.position[1] = this.canvas.height - 25;
        this.jumps = 0;
        this.isJumping = false;
      }
    }
  }

  // Checks if dino is on the ground
  onGround() {
    return this.position[0] === 30 && this.position[1] >= this.canvas.height - 25;
  }

  // Checks if the dino collieded with an enemy
  collidedWith(otherObject) {
    const posX = this.hitbox().minX;
    const posY = this.hitbox().minY;

    const collided = (posX < otherObject.hitbox().minX + otherObject.hitbox().width &&
      posX + this.hitbox().width > otherObject.hitbox().minX &&
      posY < otherObject.hitbox().minY + otherObject.hitbox().height &&
      posY + this.hitbox().height > otherObject.hitbox().minY);

    if (collided) {
      this.gameOver = true;
      return true;
    }

    return false;
  };

  // Hitbox for dino
  hitbox() {
    return {
      minX: this.position[0] + 6,
      minY: this.position[1] + 5,
      width: DINO_WIDTH - 9,
      height: DINO_HEIGHT - 8
    };
  }

  // Draws the dino sprite
  draw(ctx) {
    ctx.beginPath();
    ctx.strokeStyle = 'red';
    ctx.fillStyle = 'red';
    ctx.fillRect(this.hitbox().minX, this.hitbox().minY, this.hitbox().width, this.hitbox().height);
    ctx.stroke();
  }

  update(ctx) {
    this.jump();
    this.draw(ctx);
  }
}

// Enemy file
const WIDTH = 5;
const HEIGHT = 5;

class Enemy {
  constructor(options) {
    this.position = options.position || options.game.randomPosition();
    this.speed = options.speed || Util.randomNum(1, 3);
    this.game = options.game;
    this.radius = 3;
    this.color = 'green';
    this.isWrappable = true;
  }

  // Moving an enemy
  move() {
    this.position[0] -= this.speed;
    if (this.game.isOutOfBounds(this.position, 'enemy')) this.remove();
   }

  // Hitbox for a mini devil
  hitbox() {
    return {
      minX: this.position[0],
      minY: this.position[1],
      width: WIDTH,
      height: HEIGHT
    };
  }

  // Checks if an enemy collieded with a fireball
  collidedWith(otherObject) {
    const posX = this.hitbox().minX;
    const posY = this.hitbox().minY;

    const collided = (posX < otherObject.hitbox().minX + otherObject.hitbox().width &&
      posX + this.hitbox().width > otherObject.hitbox().minX &&
      posY < otherObject.hitbox().minY + otherObject.hitbox().height &&
      posY + this.hitbox().height > otherObject.hitbox().minY);

    if (collided) {
      this.remove();
      otherObject.remove();
      return true;
    }

    return false;
  }

  // Removing an enemy
  remove() {
    this.game.remove(this);
  };

  // Drawing a mini devil
  draw(ctx) {
    ctx.beginPath();
    ctx.strokeStyle = this.color;
    ctx.fillStyle = this.color;
    ctx.fillRect(this.hitbox().minX, this.hitbox().minY, this.hitbox().width, this.hitbox().height);
    ctx.stroke();
  }

  // Draws and updates enemy movement
  update(ctx) {
    this.move();
    this.draw(ctx);
  }
}


// Game over menu display file
class GameOverMenu {
  // Constructor for GameOverMenu class
  constructor(options) {
    this.game = options.game;
    this.setReplay = this.setReplay.bind(this);
  }

  // Handles user clicks on replay button
  clickHandler() {
    const replay = document.getElementById('replay-button');
    replay.addEventListener('click', this.setReplay);
  }

  // Prepares for game's replay function
  setReplay() {
    const menu = document.getElementById('game-over-menu');
    menu.classList.remove('active');
    this.game.replay();
  }

  // Drawing the game over menu
  draw() {
    const menu = document.getElementById('game-over-menu');
    menu.classList.add('active');
    this.clickHandler();
  }
}

document.addEventListener('DOMContentLoaded', function () {
  // Getting main game canvas
  const gameCanvas = document.getElementById('game-canvas');
  const gameCanvasCtx = gameCanvas.getContext('2d');

  // Parallax scrolling effect
  // Getting background canvas
  const backgroundCanvas = document.getElementById('background-canvas');
  const backgroundCanvasCtx = backgroundCanvas.getContext('2d');

  // Getting foreground canvas
  const foregroundCanvas = document.getElementById('foreground-canvas');
  const foregroundCanvasCtx = foregroundCanvas.getContext('2d');

  const game = new Game(
    gameCanvasCtx,
    gameCanvas,
    backgroundCanvasCtx,
    backgroundCanvas,
    foregroundCanvasCtx,
    foregroundCanvas
  );

  game.start();
});

body {
  position: relative;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

/* For rendering sprites without blurring */
canvas {
  image-rendering: pixelated;
}

/* Canvas styling */
.canvas-container {
 z-index: 1;
}

#game-canvas {
  position: absolute;
  top: 0;
  left: 0;
  tabindex: 1;
  width: 100%;
  height: 100%;
  background: lightblue;
}

#background-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 90%;
  background: green;
  z-index: 0;
}

#foreground-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* z-index: 1; */
}

.overlay-wrapper {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.3);
  /* z-index: 2; */
}

/* Game over screen */
.game-over-container {
  opacity: 0;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.7);
  font-family: sans-serif;
  display: flex;
  flex-direction: column;
  justify-content: center;
  z-index: -1;
}

.game-over-container h1 {
  font-family: sans-serif;
  font-size: 45px;
  color: white;
}

.game-over-container button {
  width: 100px;
  padding: 15px;
  color: black;
}

.game-over-container button:hover {
  background: lightblue;
  cursor: pointer;
}

.game-over-container.active {
  opacity: 1;
  z-index: 1;
}

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script type="application/javascript" src="./main.js"></script>
  <link rel="stylesheet" type="text/css" href="./assets/stylesheets/reset.css">
  <link rel="stylesheet" type="text/css" href="./assets/stylesheets/index.css">
  <title>Goodzilla</title>
</head>
<body>

  <div class="canvas-container">
    <canvas id="background-canvas"></canvas>
    <canvas id="foreground-canvas" height="302" width="802"></canvas>
    <div class="overlay-wrapper"></div>
    <canvas id="game-canvas" tabindex="1"></canvas>
  </div>

  <div id="game-over-menu" class="game-over-container">
    <h1>Game Over</h1>
    <button id="replay-button" class="replay-button-wrapper">Replay</button>
  </div>
</body>
</html>

最佳答案

问题在于,每次播放器释放时,您都会创建一个新的GameOverMenu实例,并且该GameOverMenu的构造函数将新的事件处理程序附加到相同的HTML 元素上。

因此,每次游戏结束时,都会向按钮添加一个新事件,并且单击该按钮时,所有以前的GameOverMenu实例都会要求您的游戏进入replay位置,并且每个实例都将再次开始各自的动画循环。

我不会告诉您如何解决它,有很多解决方案,现在您知道了问题,那主要是设计决策。

关于javascript - 每次重播后,为什么 Sprite 会加快一点速度?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/60658983/

10-09 15:31