3.7 第七步:使玩家可通过敲击键盘来打鼹鼠

结束上一步之后,玩家还不能打鼹鼠。而鼠洞旁边的那几个标签(也就是A、S、D、F这几个字母)不能只是奇怪的摆设,必须使其发挥作用才行。首先我们发现,['A','S','D','F']这个数组字面量不止一次会用到,所以应该修改相关代码。程序清单3.17列出了game.js文件开头及文件末尾需要的地方。

程序清单3.17 创建game.keys属性

实际上,删去的那行代码  指的是“atom.input.bind(atom.key.LEFT_ARROW,'left');”。——译者注 自范例游戏引入之后就没什么实际用途了。它只是把键盘上的左箭头起了个别名叫做left而已。在新写的循环里,我们把四个鼠洞所对应的按键都绑定好,这样稍后就可以侦测到玩家按下键盘的事件了。在文件末尾,将调用game.makeHoles函数时所传入的首个参数由原来的数组字面量改为现在的game.keys。

提示

大家可能会问,代码里为什么不写成game.keys.i,而要写作game.keys[i]呢?在访问对象属性时,采用方括号[]和点·这两种写法通常都可以,但有时却只能采用其中一种方式,而不能采用另外一种。通过点来访问属性,更清晰、更简洁。所以只要有可能,就应该尽量这么做。而在通过点来访问属性会引发错误时,则可改用方括号来访问,比如["myProperty"+"1"]。另外,如果碰到本例这种情况,需要用变量i作为下标来访问atom.keys数组中的元素,而不是访问keys中名为“i值”的属性,那就得使用方括号了。

接下来编写bop对象,其中所含的逻辑代码用来处理玩家敲打鼹鼠的操作。程序清单3.18中的代码可以放在update函数后面。

程序清单3.18 game.bop对象

这段代码可以紧跟在game.draw函数后面。稍后编写的update函数在绘制新鼹鼠时,若发现bopped为false,则会扣掉玩家的分数,所以,此处我们先得把bopped设为true才行,否则,刚开始绘制游戏时,total的值就会减1,从而使得玩家一进入游戏就丢掉1分。接下来,把玩家已经打到的鼹鼠个数设为0。然后定义draw函数,以便把总分绘制到屏幕上。with_key函数是间接调用的,game.update方法如果发现玩家按下了某个与鼠洞对应的按键,就会以该按键为参数来调用此函数。若key参数与activeMole对象的label属性相符(也就是条件判断语句的后半部分  指的是“&&”右边的“key===game.holes[game.activeMole].label”。——译者注 ),那么就递增total值,然后把activeMole设为-1、并将bopped设为true。若key参数与label属性不符,则递减total值。程序清单3.19列出了修改后的game.update方法,其中含有检测玩家是否打中地鼠所用的代码。

提示

在with_key函数中,条件判断语句的前半部分有些不太直观。当game.activeMole为undefined时,后半部分的代码会出错,为了防止这个问题发生,所以我们才加上了前半部分。这种技术叫做“guard”  大意为“守护式条件判断”。——译者注 。也可以在某处为game.activeMole设定好默认值,不过本例所采用的这种写法更为简单。笔者现在就将此写法解释一下。

JavaScript会把if(0)这种条件判断语句中的0判定为false。请注意,!!(value)这种内联式写法可以判断出受测的value对象是否为“真”。但game.activeMole的取值范围(可以取0、1、2、3)却会导致这种写法出问题,因为在game.activeMole尚未赋值时,其值为undefined,而JavaScript会把!!(undefine)与!!(0)都判定为false。我们所希望的是:当鼹鼠在第一个鼠洞时(这个鼠洞的下标为0,所以此时activeMole也是0),条件判断语句中的内容能够为true。而且,我们还要求:当玩家打中当前钻出来的这只鼹鼠并使之消失时,条件判断语句依然能够正确处理此后的情况。此时可以把activeMole设为undefined,但这么做会使后来阅读代码的人感到非常费解。所以,为了判断当前是否有鼹鼠钻出来,我们需要为原来的受测值加1。也就是说,原来的0~3,变成了现在的1~4,这样一来,这四个值都能判定为true了。原来的-1(也就是玩家打中了activeMole之后的情况)变成了现在的0,于是也可以正确判定为false了。若activeMole是undefined,则JavaScript在判定时会将其视为NaN。该值加1之后,JavaScript会将其判定为false,而此做法也能满足我们在这种情况下的需求。

上面这段话简单介绍了如何在JavaScript中判断某个变量是否已赋值、如何用guard提前拦住条件判断语句中的undefined,以及如何检测对象是否为真。用JavaScript语言来编程时,有许多特殊情况  edge case,也称“边界状况”。——译者注 需要考虑,若想在条件判断语句中把它们全都处理得当,有时真要颇费一番心思。

程序清单3.19 判断玩家是否打中鼹鼠

在刚加入的这部分粗体代码中,第一部分的意思是:如果绘制新鼹鼠时发现玩家没打中上一只鼹鼠,那么就扣分。而else分支则负责处理玩家打中鼹鼠时的情况,此时bopped会重置为false,用以记录玩家是否打中下一只鼹鼠。for循环会遍历开发者原来向引擎所注册的那些按键,若发现玩家按下了某键,则调用game.bop.with_key方法。

最后还要修改一处:请参照程序清单3.20把绘制玩家总得分的代码放入主draw函数里。

程序清单3.20 修改后的主draw函数

太棒了!游戏终于做好了!如果一切顺利,那么打开游戏并玩过一段时间之后,画面就会呈现图3.5这个样子。

图3.5 好游戏,好分数

游戏基本可以算完成了。不过在结束本章之前,请花点时间想一想如何把这款“派对游戏”(party game)改编成“音乐节拍游戏”(rhythm game)。

[1] 指的是“atom.input.bind(atom.key.LEFT_ARROW,'left');”。——译者注
[2] 指的是“&&”右边的“key===game.holes[game.activeMole].label”。——译者注
[3] 大意为“守护式条件判断”。——译者注
[4] edge case,也称“边界状况”。——译者注