mirror of
https://github.com/apachecn/eloquent-js-3e-zh.git
synced 2025-05-29 08:12:22 +00:00
16.
This commit is contained in:
parent
506b7d92eb
commit
7237494b4b
76
16.md
76
16.md
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
本章将会介绍如何实现一个小型平台游戏。平台游戏(或者叫作“跳爬”游戏)要求玩家操纵一个角色在世界中移动,这种游戏往往是二维的,而且采用单一侧面作为观察视角,玩家可以来回跳跃。
|
本章将会介绍如何实现一个小型平台游戏。平台游戏(或者叫作“跳爬”游戏)要求玩家操纵一个角色在世界中移动,这种游戏往往是二维的,而且采用单一侧面作为观察视角,玩家可以来回跳跃。
|
||||||
|
|
||||||
### 15.1 游戏
|
## 游戏
|
||||||
|
|
||||||
我们游戏大致基于由Thomas Palef开发的Dark Blue([www.lessmilk.com/games/10](http://www.lessmilk.com/games/10))。我之所以选择了这个游戏,是因为这个游戏既有趣又简单,而且不需要编写大量代码。该游戏看起来如下页图所示。
|
我们游戏大致基于由Thomas Palef开发的Dark Blue([www.lessmilk.com/games/10](http://www.lessmilk.com/games/10))。我之所以选择了这个游戏,是因为这个游戏既有趣又简单,而且不需要编写大量代码。该游戏看起来如下页图所示。
|
||||||
|
|
||||||
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
该游戏包含一个固定的背景,使用网格方式进行布局,可可移动元素则覆盖在背景之上。网格中的元素可能是空气、固体或岩浆。可可移动元素是玩家、硬币或者某一块岩浆。这些元素的位置不限于网格,它们的坐标可以是分数,允许平滑运动。
|
该游戏包含一个固定的背景,使用网格方式进行布局,可可移动元素则覆盖在背景之上。网格中的元素可能是空气、固体或岩浆。可可移动元素是玩家、硬币或者某一块岩浆。这些元素的位置不限于网格,它们的坐标可以是分数,允许平滑运动。
|
||||||
|
|
||||||
### 15.2 实现技术
|
## 实现技术
|
||||||
|
|
||||||
我们会使用浏览器的DOM来展示游戏界面,我们会通过处理按键事件来读取用户输入。
|
我们会使用浏览器的DOM来展示游戏界面,我们会通过处理按键事件来读取用户输入。
|
||||||
|
|
||||||
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
在下一章中,我们会研究另一种浏览器技术——<canvas>标签。该标签提供了一种更为传统的图像绘制方式,直接处理形状和像素而非DOM元素。
|
在下一章中,我们会研究另一种浏览器技术——<canvas>标签。该标签提供了一种更为传统的图像绘制方式,直接处理形状和像素而非DOM元素。
|
||||||
|
|
||||||
### 15.3 关卡
|
## 关卡
|
||||||
|
|
||||||
我们需要一种人类可读的、可编辑的方法来指定关卡。因为一切最开始都可以在网格,所以我们可以使用大型字符串,其中每个字符代表一个元素,要么是背景网格的一部分,要么是可移动元素。
|
我们需要一种人类可读的、可编辑的方法来指定关卡。因为一切最开始都可以在网格,所以我们可以使用大型字符串,其中每个字符代表一个元素,要么是背景网格的一部分,要么是可移动元素。
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ var simpleLevelPlan = `
|
|||||||
|
|
||||||
整个游戏包含了许多关卡,玩家必须完成所有关卡。每关的过关条件是玩家需要收集所有硬币。如果玩家碰到熔岩,当前关卡会恢复初始状态,而玩家可以再次尝试过关。
|
整个游戏包含了许多关卡,玩家必须完成所有关卡。每关的过关条件是玩家需要收集所有硬币。如果玩家碰到熔岩,当前关卡会恢复初始状态,而玩家可以再次尝试过关。
|
||||||
|
|
||||||
### 15.4 读取关卡
|
## 读取关卡
|
||||||
|
|
||||||
下面的类存储了关卡对象。它的参数应该是定义关卡的字符串。
|
下面的类存储了关卡对象。它的参数应该是定义关卡的字符串。
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ class State {
|
|||||||
|
|
||||||
这又是一个持久性数据结构,更新游戏状态会创建新状态,并使旧状态保持完整。
|
这又是一个持久性数据结构,更新游戏状态会创建新状态,并使旧状态保持完整。
|
||||||
|
|
||||||
### 15.5 角色
|
## 角色
|
||||||
|
|
||||||
角色对象表示,游戏中给定可移动元素的当前位置和状态。所有的角色对象都遵循相同的接口。它们的`pos`属性保存元素的左上角坐标,它们的`size`属性保存其大小。
|
角色对象表示,游戏中给定可移动元素的当前位置和状态。所有的角色对象都遵循相同的接口。它们的`pos`属性保存元素的左上角坐标,它们的`size`属性保存其大小。
|
||||||
|
|
||||||
@ -237,7 +237,7 @@ const levelChars = {
|
|||||||
|
|
||||||
这给了我们创建`Level`实例所需的所有部件。
|
这给了我们创建`Level`实例所需的所有部件。
|
||||||
|
|
||||||
```
|
```js
|
||||||
let simpleLevel = new Level(simpleLevelPlan);
|
let simpleLevel = new Level(simpleLevelPlan);
|
||||||
console.log(`${simpleLevel.width} by ${simpleLevel.height}`);
|
console.log(`${simpleLevel.width} by ${simpleLevel.height}`);
|
||||||
// → 22 by 9
|
// → 22 by 9
|
||||||
@ -245,7 +245,7 @@ console.log(`${simpleLevel.width} by ${simpleLevel.height}`);
|
|||||||
|
|
||||||
上面一段代码的任务是将特定关卡显示在屏幕上,并构建关卡中的时间与动作。
|
上面一段代码的任务是将特定关卡显示在屏幕上,并构建关卡中的时间与动作。
|
||||||
|
|
||||||
### 15.6 成为负担的封装
|
## 成为负担的封装
|
||||||
|
|
||||||
本章中大多数代码并没有过多考虑封装。首先,封装需要耗费额外精力。封装使得程序变得更加庞大,而且会引入额外的概念和接口。我尽量将程序的体积控制在较小的范围之内,避免读者因为代码过于庞大而走神。
|
本章中大多数代码并没有过多考虑封装。首先,封装需要耗费额外精力。封装使得程序变得更加庞大,而且会引入额外的概念和接口。我尽量将程序的体积控制在较小的范围之内,避免读者因为代码过于庞大而走神。
|
||||||
|
|
||||||
@ -255,7 +255,7 @@ console.log(`${simpleLevel.width} by ${simpleLevel.height}`);
|
|||||||
|
|
||||||
我们会封装的一部分代码是绘图子系统。其原因是我们会在下一章中使用另一种方式来展示相同的游戏。通过将绘图代码隐藏在接口之后,我们可以在下一章中使用相同的游戏程序,只需要插入新的显示模块即可。
|
我们会封装的一部分代码是绘图子系统。其原因是我们会在下一章中使用另一种方式来展示相同的游戏。通过将绘图代码隐藏在接口之后,我们可以在下一章中使用相同的游戏程序,只需要插入新的显示模块即可。
|
||||||
|
|
||||||
### 15.7 绘图
|
## 绘图
|
||||||
|
|
||||||
我们通过定义一个“显示器”对象来封装绘图代码,该对象显示指定关卡,以及状态。本章定义的显示器类型名为DOMDisplay,因为该类型使用简单的DOM元素来显示关卡。
|
我们通过定义一个“显示器”对象来封装绘图代码,该对象显示指定关卡,以及状态。本章定义的显示器类型名为DOMDisplay,因为该类型使用简单的DOM元素来显示关卡。
|
||||||
|
|
||||||
@ -433,7 +433,7 @@ DOMDisplay.prototype.scrollPlayerIntoView = function(state) {
|
|||||||
|
|
||||||
我们可以在link标签中使用rel="stylesheet",将一个CSS文件加载到页面中。文件game.css包含了我们的游戏所需的样式。
|
我们可以在link标签中使用rel="stylesheet",将一个CSS文件加载到页面中。文件game.css包含了我们的游戏所需的样式。
|
||||||
|
|
||||||
### 15.8 动作与冲突
|
## 动作与冲突
|
||||||
|
|
||||||
现在我们是时候来添加一些动作了。这是游戏中最令人着迷的一部分。实现动作的最基本的方案(也是大多数游戏采用的)是将时间划分为一个个时间段,根据角色的每一步速度和时间长度,将元素移动一段距离。我们将以秒为单位测量时间,所以速度以单元每秒来表示。
|
现在我们是时候来添加一些动作了。这是游戏中最令人着迷的一部分。实现动作的最基本的方案(也是大多数游戏采用的)是将时间划分为一个个时间段,根据角色的每一步速度和时间长度,将元素移动一段距离。我们将以秒为单位测量时间,所以速度以单元每秒来表示。
|
||||||
|
|
||||||
@ -595,7 +595,7 @@ Player.prototype.update = function(time, state, keys) {
|
|||||||
|
|
||||||
重力、跳跃速度和几乎所有其他常数,在游戏中都是通过反复试验来设定的。我测试了值,直到我找到了我喜欢的组合。
|
重力、跳跃速度和几乎所有其他常数,在游戏中都是通过反复试验来设定的。我测试了值,直到我找到了我喜欢的组合。
|
||||||
|
|
||||||
### 15.10 跟踪按键
|
## 跟踪按键
|
||||||
|
|
||||||
对于这样的游戏,我们不希望按键在每次按下时生效。相反,我们希望只要按下了它们,他们的效果(移动球员的数字)就一直有效。
|
对于这样的游戏,我们不希望按键在每次按下时生效。相反,我们希望只要按下了它们,他们的效果(移动球员的数字)就一直有效。
|
||||||
|
|
||||||
@ -623,7 +623,7 @@ const arrowKeys =
|
|||||||
|
|
||||||
两种事件类型都使用相同的处理程序函数。该处理函数根据事件对象的type属性来确定是将按键状态修改为true(“keydown”)还是false(“keyup”)。
|
两种事件类型都使用相同的处理程序函数。该处理函数根据事件对象的type属性来确定是将按键状态修改为true(“keydown”)还是false(“keyup”)。
|
||||||
|
|
||||||
### 15.11 运行游戏
|
## 运行游戏
|
||||||
|
|
||||||
我们在第十四章中看到的requestAnimationFrames函数是一种产生游戏动画的好方法。但该函数的接口有点过于原始。该函数要求我们跟踪上次调用函数的时间,并在每一帧后再次调用requestAnimationFrame方法。
|
我们在第十四章中看到的requestAnimationFrames函数是一种产生游戏动画的好方法。但该函数的接口有点过于原始。该函数要求我们跟踪上次调用函数的时间,并在每一帧后再次调用requestAnimationFrame方法。
|
||||||
|
|
||||||
@ -703,9 +703,9 @@ async function runGame(plans, Display) {
|
|||||||
</body>
|
</body>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 15.12 习题
|
## 习题
|
||||||
|
|
||||||
#### 15.12.1 游戏结束
|
### 游戏结束
|
||||||
|
|
||||||
按照惯例,平台游戏中玩家一开始会有有限数量的生命,每死亡一次就扣去一条生命。当玩家生命耗尽时,游戏就从头开始了。
|
按照惯例,平台游戏中玩家一开始会有有限数量的生命,每死亡一次就扣去一条生命。当玩家生命耗尽时,游戏就从头开始了。
|
||||||
|
|
||||||
@ -730,7 +730,7 @@ async function runGame(plans, Display) {
|
|||||||
</body>
|
</body>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 15.12.2 暂停游戏
|
### 暂停游戏
|
||||||
|
|
||||||
现在实现一个功能——当用户按下ESC键时可以暂停或继续游戏。
|
现在实现一个功能——当用户按下ESC键时可以暂停或继续游戏。
|
||||||
|
|
||||||
@ -774,3 +774,51 @@ async function runGame(plans, Display) {
|
|||||||
|
|
||||||
### 怪物
|
### 怪物
|
||||||
|
|
||||||
|
它是传统的平台游戏,里面有敌人,你可以跳到它顶上来打败它。这个练习要求你把这种角色类型添加到游戏中。
|
||||||
|
|
||||||
|
我们称之为怪物。怪物只能水平移动。你可以让它们朝着玩家的方向移动,或者像水平熔岩一样来回跳动,或者拥有你想要的任何运动模式。这个类不必处理掉落,但是它应该确保怪物不会穿过墙壁。
|
||||||
|
|
||||||
|
当怪物接触玩家时,效果取决于玩家是否跳到它们顶上。你可以通过检查玩家的底部是否接近怪物的顶部来近似它。如果是这样的话,怪物就消失了。如果没有,游戏就输了。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<link rel="stylesheet" href="css/game.css">
|
||||||
|
<style>.monster { background: purple }</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
// Complete the constructor, update, and collide methods
|
||||||
|
class Monster {
|
||||||
|
constructor(pos, /* ... */) {}
|
||||||
|
|
||||||
|
get type() { return "monster"; }
|
||||||
|
|
||||||
|
static create(pos) {
|
||||||
|
return new Monster(pos.plus(new Vec(0, -1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
update(time, state) {}
|
||||||
|
|
||||||
|
collide(state) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Monster.prototype.size = new Vec(1.2, 2);
|
||||||
|
|
||||||
|
levelChars["M"] = Monster;
|
||||||
|
|
||||||
|
runLevel(new Level(`
|
||||||
|
..................................
|
||||||
|
.################################.
|
||||||
|
.#..............................#.
|
||||||
|
.#..............................#.
|
||||||
|
.#..............................#.
|
||||||
|
.#...........................o..#.
|
||||||
|
.#..@...........................#.
|
||||||
|
.##########..............########.
|
||||||
|
..........#..o..o..o..o..#........
|
||||||
|
..........#...........M..#........
|
||||||
|
..........################........
|
||||||
|
..................................
|
||||||
|
`), DOMDisplay);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
```
|
||||||
|
Loading…
x
Reference in New Issue
Block a user