index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>游戏</title> <style> #gameScreen{ border: 1px solid black; } #img_ball,#img_brick{ display: none; } </style></head><body> <img id="img_ball" src="/img/ball.png"> <img id="img_brick" src="/img/brick.png"> <canvas id="gameScreen" width="800" height="600"></canvas> <script src="collisionDetection.js"></script> <script src="ball.js"></script> <script src="brick.js"></script> <script src="levels.js"></script> <script src="paddle.js"></script> <script src="input.js"></script> <script src="game.js"></script> <script src="index.js"></script></body></html>
index.js
const canvas = document.getElementById('gameScreen');const ctx = canvas.getContext('2d');const GAME_WIDTH = 800;const GAME_HEIGHT = 600;// 载入gameconst game = new Game(GAME_WIDTH, GAME_HEIGHT);let lastTime = 0;function gameLoop(timestamp) { let deltaTime = timestamp - lastTime; lastTime = timestamp; ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT); // 更新 game.update(deltaTime); game.draw(ctx); requestAnimationFrame(gameLoop);}requestAnimationFrame(gameLoop);
input.js
class InputHandler { constructor(paddle, game) { // 按下事件 document.addEventListener('keydown', e => { switch (e.keyCode) { case 37: paddle.moveLeft(); break; case 39: paddle.moveRight(); break; case 27: game.togglePause(); break; case 32: game.start(); break; } }) document.addEventListener('keyup', e => { switch (e.keyCode) { case 37: if (paddle.speed < 0) paddle.stop(); break; case 39: if (paddle.speed > 0) paddle.stop(); break; } }) }}
levels.js
function buildLevel(game, level) { const bricks = []; level.forEach((row, rowIndex) => { row.forEach((brick, brickIndex) => { if (brick === 1) { let position = { x: 80 * brickIndex, y: 75 + 24 * rowIndex }; bricks.push(new Brick(game, position)); } }); }); return bricks;}const level1 = [ [0, 1, 1, 0, 0, 0, 0, 1, 1, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]const level2 = [ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 1, 1, 0, 0, 0, 0, 1, 1, 0],]
paddle.js
class Paddle { constructor(game) { this.gameWidth = game.gameWidth; this.width = 150; this.height = 20; this.maxSpeed = 7; this.speed = 0; this.position = { x: game.gameWidth / 2 - this.width / 2, y: game.gameHeight - this.height - 10 } } // 左移动 moveLeft() { this.speed = -this.maxSpeed; } // 右移动 moveRight() { this.speed = this.maxSpeed; } // 停止 stop() { this.speed = 0; } draw(ctx) { ctx.fillStyle = '#0ff'; ctx.fillRect(this.position.x, this.position.y, this.width, this.height); } update(deltaTime) { this.position.x += this.speed; if (this.position.x < 0) this.position.x = 0; if (this.position.x + this.width > this.gameWidth) this.position.x = this.gameWidth - this.width; }}
game.js
const GAMESTATE = { PAUSED: 0, RUNNING: 1, MENU: 2, GAMEOVER: 3, NEWLEVEL: 4}class Game { constructor(gameWidth, gameHeight) { this.gameWidth = gameWidth; this.gameHeight = gameHeight; this.gamestate = GAMESTATE.MENU; this.paddle = new Paddle(this); this.ball = new Ball(this); new InputHandler(this.paddle, this); this.lives = 3; this.bricks = []; this.gameObjects = []; this.levels = [level1, level2]; this.currentLevel = 0; } start() { // 只有从菜单进来时才能进行下方的操作 if (this.gamestate !== GAMESTATE.MENU && this.gamestate !== GAMESTATE.NEWLEVEL) return; this.bricks = buildLevel(this, this.levels[this.currentLevel]); // 重置 this.ball.reset(); this.gameObjects = [this.ball, this.paddle]; this.gamestate = GAMESTATE.RUNNING; } update(deltaTime) { if (this.lives === 0) this.gamestate = GAMESTATE.GAMEOVER; if ( this.gamestate === GAMESTATE.PAUSED || this.gamestate === GAMESTATE.MENU || this.gamestate === GAMESTATE.GAMEOVER ) return; // 判断长度 if (this.bricks.length === 0) { console.log('切换新的游戏画面'); if (this.currentLevel + 1 < this.levels.length) { this.currentLevel++; this.gamestate = GAMESTATE.NEWLEVEL; this.start(); } } [...this.gameObjects, ...this.bricks].forEach(object => object.update(deltaTime)); this.bricks = this.bricks.filter(brick => !brick.markedForDeletion); } draw(ctx) { [...this.gameObjects, ...this.bricks].forEach(object => object.draw(ctx)); // 如果是暂停 if (this.gamestate === GAMESTATE.PAUSED) { ctx.rect(0, 0, this.gameWidth, this.gameHeight); ctx.fillStyle = "rgba(0,0,0,0.7)"; ctx.fill(); ctx.font = "30px Arial"; ctx.fillStyle = 'white'; ctx.textAlign = 'center'; ctx.fillText('暂停', this.gameWidth / 2, this.gameHeight / 2); } if (this.gamestate === GAMESTATE.MENU) { ctx.rect(0, 0, this.gameWidth, this.gameHeight); ctx.fillStyle = "rgba(0,0,0,1)"; ctx.fill(); ctx.font = "30px Arial"; ctx.fillStyle = 'white'; ctx.textAlign = 'center'; ctx.fillText('按下空格后开始游戏', this.gameWidth / 2, this.gameHeight / 2); } if (this.gamestate === GAMESTATE.GAMEOVER) { ctx.rect(0, 0, this.gameWidth, this.gameHeight); ctx.fillStyle = "rgba(0,0,0,1)"; ctx.fill(); ctx.font = "30px Arial"; ctx.fillStyle = 'white'; ctx.textAlign = 'center'; ctx.fillText('游戏结束', this.gameWidth / 2, this.gameHeight / 2); } } togglePause() { //暂停 if (this.gamestate == GAMESTATE.PAUSED) { this.gamestate = GAMESTATE.RUNNING; } else { this.gamestate = GAMESTATE.PAUSED; } }}
collistionDetection.js
function detectCollistion(ball, gameObject) { // 检测是否是小平台 const bootomOfBall = ball.position.y + ball.size; const topOfBall = ball.position.y; const topOfObject = gameObject.position.y; const leftSideOfObject = gameObject.position.x; const rightSideOfObject = gameObject.position.x + gameObject.width; const bottomOfObject = gameObject.position.y + gameObject.height; // 如果底部的高度大于等于球的高度值,则处于碰撞状态 if (bootomOfBall >= topOfObject && topOfBall <= bottomOfObject && ball.position.x >= leftSideOfObject && ball.position.x + ball.size <= rightSideOfObject ) { return true; } else { return false; }}
bricks.js
class Brick { constructor(game, position) { this.image = document.getElementById('img_brick'); this.game = game; this.position = position; this.width = 80; this.height = 24; this.markedForDeletion = false; } update() { if (detectCollistion(this.game.ball, this)) { this.game.ball.speed.y = -this.game.ball.speed.y; this.markedForDeletion = true; } } draw(ctx) { ctx.drawImage(this.image, this.position.x, this.position.y, this.width, this.height); }}
ball.js
class Ball { constructor(game) { this.image = document.getElementById('img_ball'); this.gameWidth = game.gameWidth; this.gameHeight = game.gameHeight; this.game = game; this.reset(); this.size = 16; } reset() { this.speed = { x: 4, y: -2 }; this.position = { x: 10, y: 400 }; } draw(ctx) { ctx.drawImage(this.image, this.position.x, this.position.y, this.size, this.size); } update(deltaTime) { this.position.x += this.speed.x; this.position.y += this.speed.y; // 判断边界 左右 if (this.position.x + this.size > this.gameWidth || this.position.x < 0) { this.speed.x = -this.speed.x; } // 上下 if (this.position.y < 0) { this.speed.y = -this.speed.y; } // 触碰到底部 if (this.position.y + this.size > this.gameHeight) { this.game.lives--; this.reset(); } // 碰撞检测 if (detectCollistion(this, this.game.paddle)) { this.speed.y = -this.speed.y; this.position.y = this.game.paddle.position.y - this.size; } }}
图片资源

