习题

游戏结束

按照惯例,平台游戏中玩家一开始会有有限数量的生命,每死亡一次就扣去一条生命。当玩家生命耗尽时,游戏就从头开始了。

调整runGame来实现生命机制。玩家一开始会有 3 条生命。每次启动时输出当前生命数量(使用console.log)。

  1. <link rel="stylesheet" href="css/game.css">
  2. <body>
  3. <script>
  4. // The old runGame function. Modify it...
  5. async function runGame(plans, Display) {
  6. for (let level = 0; level < plans.length;) {
  7. let status = await runLevel(new Level(plans[level]),
  8. Display);
  9. if (status == "won") level++;
  10. }
  11. console.log("You've won!");
  12. }
  13. runGame(GAME_LEVELS, DOMDisplay);
  14. </script>
  15. </body>

暂停游戏

现在实现一个功能 —— 当用户按下 ESC 键时可以暂停或继续游戏。

我们可以修改runLevel函数,使用另一个键盘事件处理器来实现在玩家按下 ESC 键的时候中断或恢复动画。

乍看起来,runAnimation无法完成该任务,但如果我们使用runLevel来重新安排调度策略,也是可以实现的。

当你完成该功能后,可以尝试加入另一个功能。我们现在注册键盘事件处理器的方法多少有点问题。现在arrows对象是一个全局绑定,即使游戏没有运行时,事件处理器也是有效的。我们称之为系统泄露。请扩展tracKeys,提供一种方法来注销事件处理器,接着修改runLevel在启动游戏时注册事件处理器,并在游戏结束后注销事件处理器。

  1. <link rel="stylesheet" href="css/game.css">
  2. <body>
  3. <script>
  4. // The old runLevel function. Modify this...
  5. function runLevel(level, Display) {
  6. let display = new Display(document.body, level);
  7. let state = State.start(level);
  8. let ending = 1;
  9. return new Promise(resolve => {
  10. runAnimation(time => {
  11. state = state.update(time, arrowKeys);
  12. display.setState(state);
  13. if (state.status == "playing") {
  14. return true;
  15. } else if (ending > 0) {
  16. ending -= time;
  17. return true;
  18. } else {
  19. display.clear();
  20. resolve(state.status);
  21. return false;
  22. }
  23. });
  24. });
  25. }
  26. runGame(GAME_LEVELS, DOMDisplay);
  27. </script>
  28. </body>

怪物

它是传统的平台游戏,里面有敌人,你可以跳到它顶上来打败它。这个练习要求你把这种角色类型添加到游戏中。

我们称之为怪物。怪物只能水平移动。你可以让它们朝着玩家的方向移动,或者像水平岩浆一样来回跳动,或者拥有你想要的任何运动模式。这个类不必处理掉落,但是它应该确保怪物不会穿过墙壁。

当怪物接触玩家时,效果取决于玩家是否跳到它们顶上。你可以通过检查玩家的底部是否接近怪物的顶部来近似它。如果是这样的话,怪物就消失了。如果没有,游戏就输了。

  1. <link rel="stylesheet" href="css/game.css">
  2. <style>.monster { background: purple }</style>
  3. <body>
  4. <script>
  5. // Complete the constructor, update, and collide methods
  6. class Monster {
  7. constructor(pos, /* ... */) {}
  8. get type() { return "monster"; }
  9. static create(pos) {
  10. return new Monster(pos.plus(new Vec(0, -1)));
  11. }
  12. update(time, state) {}
  13. collide(state) {}
  14. }
  15. Monster.prototype.size = new Vec(1.2, 2);
  16. levelChars["M"] = Monster;
  17. runLevel(new Level(`
  18. ..................................
  19. .################################.
  20. .#..............................#.
  21. .#..............................#.
  22. .#..............................#.
  23. .#...........................o..#.
  24. .#..@...........................#.
  25. .##########..............########.
  26. ..........#..o..o..o..o..#........
  27. ..........#...........M..#........
  28. ..........################........
  29. ..................................
  30. `), DOMDisplay);
  31. </script>
  32. </body>