From 7237494b4b096f5fa4849738bc470637c65a1000 Mon Sep 17 00:00:00 2001 From: wizardforcel <562826179@qq.com> Date: Thu, 31 May 2018 22:22:13 +0800 Subject: [PATCH] 16. --- 16.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/16.md b/16.md index d73ca96..0ae7410 100644 --- a/16.md +++ b/16.md @@ -10,7 +10,7 @@ 本章将会介绍如何实现一个小型平台游戏。平台游戏(或者叫作“跳爬”游戏)要求玩家操纵一个角色在世界中移动,这种游戏往往是二维的,而且采用单一侧面作为观察视角,玩家可以来回跳跃。 -### 15.1 游戏 +## 游戏 我们游戏大致基于由Thomas Palef开发的Dark Blue([www.lessmilk.com/games/10](http://www.lessmilk.com/games/10))。我之所以选择了这个游戏,是因为这个游戏既有趣又简单,而且不需要编写大量代码。该游戏看起来如下页图所示。 @@ -22,7 +22,7 @@ 该游戏包含一个固定的背景,使用网格方式进行布局,可可移动元素则覆盖在背景之上。网格中的元素可能是空气、固体或岩浆。可可移动元素是玩家、硬币或者某一块岩浆。这些元素的位置不限于网格,它们的坐标可以是分数,允许平滑运动。 -### 15.2 实现技术 +## 实现技术 我们会使用浏览器的DOM来展示游戏界面,我们会通过处理按键事件来读取用户输入。 @@ -34,7 +34,7 @@ 在下一章中,我们会研究另一种浏览器技术——标签。该标签提供了一种更为传统的图像绘制方式,直接处理形状和像素而非DOM元素。 -### 15.3 关卡 +## 关卡 我们需要一种人类可读的、可编辑的方法来指定关卡。因为一切最开始都可以在网格,所以我们可以使用大型字符串,其中每个字符代表一个元素,要么是背景网格的一部分,要么是可移动元素。 @@ -59,7 +59,7 @@ var simpleLevelPlan = ` 整个游戏包含了许多关卡,玩家必须完成所有关卡。每关的过关条件是玩家需要收集所有硬币。如果玩家碰到熔岩,当前关卡会恢复初始状态,而玩家可以再次尝试过关。 -### 15.4 读取关卡 +## 读取关卡 下面的类存储了关卡对象。它的参数应该是定义关卡的字符串。 @@ -117,7 +117,7 @@ class State { 这又是一个持久性数据结构,更新游戏状态会创建新状态,并使旧状态保持完整。 -### 15.5 角色 +## 角色 角色对象表示,游戏中给定可移动元素的当前位置和状态。所有的角色对象都遵循相同的接口。它们的`pos`属性保存元素的左上角坐标,它们的`size`属性保存其大小。 @@ -237,7 +237,7 @@ const levelChars = { 这给了我们创建`Level`实例所需的所有部件。 -``` +```js let simpleLevel = new Level(simpleLevelPlan); console.log(`${simpleLevel.width} by ${simpleLevel.height}`); // → 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元素来显示关卡。 @@ -433,7 +433,7 @@ DOMDisplay.prototype.scrollPlayerIntoView = function(state) { 我们可以在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”)。 -### 15.11 运行游戏 +## 运行游戏 我们在第十四章中看到的requestAnimationFrames函数是一种产生游戏动画的好方法。但该函数的接口有点过于原始。该函数要求我们跟踪上次调用函数的时间,并在每一帧后再次调用requestAnimationFrame方法。 @@ -703,9 +703,9 @@ async function runGame(plans, Display) { ``` -### 15.12 习题 +## 习题 -#### 15.12.1 游戏结束 +### 游戏结束 按照惯例,平台游戏中玩家一开始会有有限数量的生命,每死亡一次就扣去一条生命。当玩家生命耗尽时,游戏就从头开始了。 @@ -730,7 +730,7 @@ async function runGame(plans, Display) { ``` -#### 15.12.2 暂停游戏 +### 暂停游戏 现在实现一个功能——当用户按下ESC键时可以暂停或继续游戏。 @@ -774,3 +774,51 @@ async function runGame(plans, Display) { ### 怪物 +它是传统的平台游戏,里面有敌人,你可以跳到它顶上来打败它。这个练习要求你把这种角色类型添加到游戏中。 + +我们称之为怪物。怪物只能水平移动。你可以让它们朝着玩家的方向移动,或者像水平熔岩一样来回跳动,或者拥有你想要的任何运动模式。这个类不必处理掉落,但是它应该确保怪物不会穿过墙壁。 + +当怪物接触玩家时,效果取决于玩家是否跳到它们顶上。你可以通过检查玩家的底部是否接近怪物的顶部来近似它。如果是这样的话,怪物就消失了。如果没有,游戏就输了。 + +```html + + + + + + +```