动作与冲突
现在我们是时候来添加一些动作了。这是游戏中最令人着迷的一部分。实现动作的最基本的方案(也是大多数游戏采用的)是将时间划分为一个个时间段,根据角色的每一步速度和时间长度,将元素移动一段距离。我们将以秒为单位测量时间,所以速度以单元每秒来表示。
移动东西非常简单。比较困难的一部分是处理元素之间的相互作用。当玩家撞到墙壁或者地板时,不可能简单地直接穿越过去。游戏必须注意特定的动作会导致两个对象产生碰撞,并需要采取相应措施。如果玩家遇到墙壁,则必须停下来,如果遇到硬币则必须将其收集起来。
想要解决通常情况下的碰撞问题是件艰巨任务。你可以找到一些我们称之为物理引擎的库,这些库会在二维或三维空间中模拟物理对象的相互作用。我们在本章中采用更合适的方案:只处理矩形物体之间的碰撞,并采用最简单的方案进行处理。
在移动角色或岩浆块时,我们需要测试元素是否会移动到墙里面。如果会的话,我们只要取消整个动作即可。而对动作的反应则取决于移动元素类型。如果是玩家则停下来,如果是岩浆块则反弹回去。
这种方法需要保证每一步之间的时间间隔足够短,确保能够在对象实际碰撞之前取消动作。如果时间间隔太大,玩家最后会悬浮在离地面很高的地方。另一种方法明显更好但更加复杂,即寻找到精确的碰撞点并将元素移动到那个位置。我们会采取最简单的方案,并确保减少动画之间的时间间隔,以掩盖其问题。
该方法用于判断某个矩形(通过位置与尺寸限定)是否会碰到给定类型的网格。
Level.prototype.touches = function(pos, size, type) {var xStart = Math.floor(pos.x);var xEnd = Math.ceil(pos.x + size.x);var yStart = Math.floor(pos.y);var yEnd = Math.ceil(pos.y + size.y);for (var y = yStart; y < yEnd; y++) {for (var x = xStart; x < xEnd; x++) {let isOutside = x < 0 || x >= this.width ||y < 0 || y >= this.height;let here = isOutside ? "wall" : this.rows[y][x];if (here == type) return true;}}return false;};
该方法通过对坐标使用Math.floor和Math.ceil,来计算与身体重叠的网格方块集合。记住网格方块的大小是1x1个单位。通过将盒子的边上下颠倒,我们得到盒子接触的背景方块的范围。

我们通过查找坐标遍历网格方块,并在找到匹配的方块时返回true。关卡之外的方块总是被当作"wall",来确保玩家不能离开这个世界,并且我们不会意外地尝试,在我们的“rows数组的边界之外读取。
状态的update方法使用touches来判断玩家是否接触岩浆。
State.prototype.update = function(time, keys) {let actors = this.actors.map(actor => actor.update(time, this, keys));let newState = new State(this.level, actors, this.status);if (newState.status != "playing") return newState;let player = newState.player;if (this.level.touches(player.pos, player.size, "lava")) {return new State(this.level, actors, "lost");}for (let actor of actors) {if (actor != player && overlap(actor, player)) {newState = actor.collide(newState);}}return newState;};
它接受时间步长和一个数据结构,告诉它按下了哪些键。它所做的第一件事是调用所有角色的update方法,生成一组更新后的角色。角色也得到时间步长,按键,和状态,以便他们可以根据这些来更新。只有玩家才会读取按键,因为这是唯一由键盘控制的角色。
如果游戏已经结束,就不需要再做任何处理(游戏不能在输之后赢,反之亦然)。否则,该方法测试玩家是否接触背景岩浆。如果是这样的话,游戏就输了,我们就完了。最后,如果游戏实际上还在继续,它会查看其他玩家是否与玩家重叠。
overlap函数检测角色之间的重叠。它需要两个角色对象,当它们触碰时返回true,当它们沿X轴和Y轴重叠时,就是这种情况。
function overlap(actor1, actor2) {return actor1.pos.x + actor1.size.x > actor2.pos.x &&actor1.pos.x < actor2.pos.x + actor2.size.x &&actor1.pos.y + actor1.size.y > actor2.pos.y &&actor1.pos.y < actor2.pos.y + actor2.size.y;}
如果任何角色重叠了,它的collide方法有机会更新状态。触碰岩浆角色将游戏状态设置为"lost",当你碰到硬币时,硬币就会消失,当这是最后一枚硬币时,状态就变成了"won"。
Lava.prototype.collide = function(state) {return new State(state.level, state.actors, "lost");};Coin.prototype.collide = function(state) {let filtered = state.actors.filter(a => a != this);let status = state.status;if (!filtered.some(a => a.type == "coin")) status = "won";return new State(state.level, filtered, status);};
