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