index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>游戏</title>
  8. <style>
  9. #gameScreen{
  10. border: 1px solid black;
  11. }
  12. #img_ball,#img_brick{
  13. display: none;
  14. }
  15. </style>
  16. </head>
  17. <body>
  18. <img id="img_ball" src="/img/ball.png">
  19. <img id="img_brick" src="/img/brick.png">
  20. <canvas id="gameScreen" width="800" height="600"></canvas>
  21. <script src="collisionDetection.js"></script>
  22. <script src="ball.js"></script>
  23. <script src="brick.js"></script>
  24. <script src="levels.js"></script>
  25. <script src="paddle.js"></script>
  26. <script src="input.js"></script>
  27. <script src="game.js"></script>
  28. <script src="index.js"></script>
  29. </body>
  30. </html>

index.js

  1. const canvas = document.getElementById('gameScreen');
  2. const ctx = canvas.getContext('2d');
  3. const GAME_WIDTH = 800;
  4. const GAME_HEIGHT = 600;
  5. // 载入game
  6. const game = new Game(GAME_WIDTH, GAME_HEIGHT);
  7. let lastTime = 0;
  8. function gameLoop(timestamp) {
  9. let deltaTime = timestamp - lastTime;
  10. lastTime = timestamp;
  11. ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
  12. // 更新
  13. game.update(deltaTime);
  14. game.draw(ctx);
  15. requestAnimationFrame(gameLoop);
  16. }
  17. requestAnimationFrame(gameLoop);

input.js

  1. class InputHandler {
  2. constructor(paddle, game) {
  3. // 按下事件
  4. document.addEventListener('keydown', e => {
  5. switch (e.keyCode) {
  6. case 37:
  7. paddle.moveLeft();
  8. break;
  9. case 39:
  10. paddle.moveRight();
  11. break;
  12. case 27:
  13. game.togglePause();
  14. break;
  15. case 32:
  16. game.start();
  17. break;
  18. }
  19. })
  20. document.addEventListener('keyup', e => {
  21. switch (e.keyCode) {
  22. case 37:
  23. if (paddle.speed < 0)
  24. paddle.stop();
  25. break;
  26. case 39:
  27. if (paddle.speed > 0)
  28. paddle.stop();
  29. break;
  30. }
  31. })
  32. }
  33. }

levels.js

  1. function buildLevel(game, level) {
  2. const bricks = [];
  3. level.forEach((row, rowIndex) => {
  4. row.forEach((brick, brickIndex) => {
  5. if (brick === 1) {
  6. let position = {
  7. x: 80 * brickIndex,
  8. y: 75 + 24 * rowIndex
  9. };
  10. bricks.push(new Brick(game, position));
  11. }
  12. });
  13. });
  14. return bricks;
  15. }
  16. const level1 = [
  17. [0, 1, 1, 0, 0, 0, 0, 1, 1, 0],
  18. [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  19. [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  20. [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
  21. ]
  22. const level2 = [
  23. [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  24. [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  25. [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  26. [0, 1, 1, 0, 0, 0, 0, 1, 1, 0],
  27. ]

paddle.js

  1. class Paddle {
  2. constructor(game) {
  3. this.gameWidth = game.gameWidth;
  4. this.width = 150;
  5. this.height = 20;
  6. this.maxSpeed = 7;
  7. this.speed = 0;
  8. this.position = {
  9. x: game.gameWidth / 2 - this.width / 2,
  10. y: game.gameHeight - this.height - 10
  11. }
  12. }
  13. // 左移动
  14. moveLeft() {
  15. this.speed = -this.maxSpeed;
  16. }
  17. // 右移动
  18. moveRight() {
  19. this.speed = this.maxSpeed;
  20. }
  21. // 停止
  22. stop() {
  23. this.speed = 0;
  24. }
  25. draw(ctx) {
  26. ctx.fillStyle = '#0ff';
  27. ctx.fillRect(this.position.x, this.position.y, this.width, this.height);
  28. }
  29. update(deltaTime) {
  30. this.position.x += this.speed;
  31. if (this.position.x < 0) this.position.x = 0;
  32. if (this.position.x + this.width > this.gameWidth) this.position.x = this.gameWidth - this.width;
  33. }
  34. }

game.js

  1. const GAMESTATE = {
  2. PAUSED: 0,
  3. RUNNING: 1,
  4. MENU: 2,
  5. GAMEOVER: 3,
  6. NEWLEVEL: 4
  7. }
  8. class Game {
  9. constructor(gameWidth, gameHeight) {
  10. this.gameWidth = gameWidth;
  11. this.gameHeight = gameHeight;
  12. this.gamestate = GAMESTATE.MENU;
  13. this.paddle = new Paddle(this);
  14. this.ball = new Ball(this);
  15. new InputHandler(this.paddle, this);
  16. this.lives = 3;
  17. this.bricks = [];
  18. this.gameObjects = [];
  19. this.levels = [level1, level2];
  20. this.currentLevel = 0;
  21. }
  22. start() {
  23. // 只有从菜单进来时才能进行下方的操作
  24. if (this.gamestate !== GAMESTATE.MENU && this.gamestate !== GAMESTATE.NEWLEVEL) return;
  25. this.bricks = buildLevel(this, this.levels[this.currentLevel]);
  26. // 重置
  27. this.ball.reset();
  28. this.gameObjects = [this.ball, this.paddle];
  29. this.gamestate = GAMESTATE.RUNNING;
  30. }
  31. update(deltaTime) {
  32. if (this.lives === 0) this.gamestate = GAMESTATE.GAMEOVER;
  33. if (
  34. this.gamestate === GAMESTATE.PAUSED ||
  35. this.gamestate === GAMESTATE.MENU ||
  36. this.gamestate === GAMESTATE.GAMEOVER
  37. ) return;
  38. // 判断长度
  39. if (this.bricks.length === 0) {
  40. console.log('切换新的游戏画面');
  41. if (this.currentLevel + 1 < this.levels.length) {
  42. this.currentLevel++;
  43. this.gamestate = GAMESTATE.NEWLEVEL;
  44. this.start();
  45. }
  46. }
  47. [...this.gameObjects, ...this.bricks].forEach(object => object.update(deltaTime));
  48. this.bricks = this.bricks.filter(brick => !brick.markedForDeletion);
  49. }
  50. draw(ctx) {
  51. [...this.gameObjects, ...this.bricks].forEach(object => object.draw(ctx));
  52. // 如果是暂停
  53. if (this.gamestate === GAMESTATE.PAUSED) {
  54. ctx.rect(0, 0, this.gameWidth, this.gameHeight);
  55. ctx.fillStyle = "rgba(0,0,0,0.7)";
  56. ctx.fill();
  57. ctx.font = "30px Arial";
  58. ctx.fillStyle = 'white';
  59. ctx.textAlign = 'center';
  60. ctx.fillText('暂停', this.gameWidth / 2, this.gameHeight / 2);
  61. }
  62. if (this.gamestate === GAMESTATE.MENU) {
  63. ctx.rect(0, 0, this.gameWidth, this.gameHeight);
  64. ctx.fillStyle = "rgba(0,0,0,1)";
  65. ctx.fill();
  66. ctx.font = "30px Arial";
  67. ctx.fillStyle = 'white';
  68. ctx.textAlign = 'center';
  69. ctx.fillText('按下空格后开始游戏', this.gameWidth / 2, this.gameHeight / 2);
  70. }
  71. if (this.gamestate === GAMESTATE.GAMEOVER) {
  72. ctx.rect(0, 0, this.gameWidth, this.gameHeight);
  73. ctx.fillStyle = "rgba(0,0,0,1)";
  74. ctx.fill();
  75. ctx.font = "30px Arial";
  76. ctx.fillStyle = 'white';
  77. ctx.textAlign = 'center';
  78. ctx.fillText('游戏结束', this.gameWidth / 2, this.gameHeight / 2);
  79. }
  80. }
  81. togglePause() {
  82. //暂停
  83. if (this.gamestate == GAMESTATE.PAUSED) {
  84. this.gamestate = GAMESTATE.RUNNING;
  85. } else {
  86. this.gamestate = GAMESTATE.PAUSED;
  87. }
  88. }
  89. }

collistionDetection.js

  1. function detectCollistion(ball, gameObject) {
  2. // 检测是否是小平台
  3. const bootomOfBall = ball.position.y + ball.size;
  4. const topOfBall = ball.position.y;
  5. const topOfObject = gameObject.position.y;
  6. const leftSideOfObject = gameObject.position.x;
  7. const rightSideOfObject = gameObject.position.x + gameObject.width;
  8. const bottomOfObject = gameObject.position.y + gameObject.height;
  9. // 如果底部的高度大于等于球的高度值,则处于碰撞状态
  10. if (bootomOfBall >= topOfObject
  11. && topOfBall <= bottomOfObject
  12. && ball.position.x >= leftSideOfObject
  13. && ball.position.x + ball.size <= rightSideOfObject
  14. ) {
  15. return true;
  16. } else {
  17. return false;
  18. }
  19. }

bricks.js

  1. class Brick {
  2. constructor(game, position) {
  3. this.image = document.getElementById('img_brick');
  4. this.game = game;
  5. this.position = position;
  6. this.width = 80;
  7. this.height = 24;
  8. this.markedForDeletion = false;
  9. }
  10. update() {
  11. if (detectCollistion(this.game.ball, this)) {
  12. this.game.ball.speed.y = -this.game.ball.speed.y;
  13. this.markedForDeletion = true;
  14. }
  15. }
  16. draw(ctx) {
  17. ctx.drawImage(this.image, this.position.x, this.position.y, this.width, this.height);
  18. }
  19. }

ball.js

  1. class Ball {
  2. constructor(game) {
  3. this.image = document.getElementById('img_ball');
  4. this.gameWidth = game.gameWidth;
  5. this.gameHeight = game.gameHeight;
  6. this.game = game;
  7. this.reset();
  8. this.size = 16;
  9. }
  10. reset() {
  11. this.speed = { x: 4, y: -2 };
  12. this.position = { x: 10, y: 400 };
  13. }
  14. draw(ctx) {
  15. ctx.drawImage(this.image, this.position.x, this.position.y, this.size, this.size);
  16. }
  17. update(deltaTime) {
  18. this.position.x += this.speed.x;
  19. this.position.y += this.speed.y;
  20. // 判断边界 左右
  21. if (this.position.x + this.size > this.gameWidth || this.position.x < 0) {
  22. this.speed.x = -this.speed.x;
  23. }
  24. // 上下
  25. if (this.position.y < 0) {
  26. this.speed.y = -this.speed.y;
  27. }
  28. // 触碰到底部
  29. if (this.position.y + this.size > this.gameHeight) {
  30. this.game.lives--;
  31. this.reset();
  32. }
  33. // 碰撞检测
  34. if (detectCollistion(this, this.game.paddle)) {
  35. this.speed.y = -this.speed.y;
  36. this.position.y = this.game.paddle.position.y - this.size;
  37. }
  38. }
  39. }

图片资源

image.png
image.png