第5章 瓢虫快跑

游戏是移动应用中最令人兴奋的部分,无论是玩游戏,还是做游戏。最近红极一时“愤怒的小鸟”,根据开发者Rovio公司称,第一年下载量达50万次,同时每天运行的人时数超过一百万小时。(甚至有人说要把它拍成故事片!)我们可无法保证电影的成功,但可以让您用App Inventor创建自己的游戏“瓢虫快跑”,里面的瓢虫要吃蚜虫,同时要避免被青蛙吃掉。

应用描述

如图5-1所示的“瓢虫快跑”应用,用户可以:

  • 通过倾斜设备来控制瓢虫移动;
  • 查看屏幕上的能量指示条,能量会随时间减少,并引起瓢虫的饥饿;
  • 让瓢虫追逐并吃掉蚜虫来获得能量,抵御饥饿;
  • 帮助瓢虫躲避青蛙,因为青蛙吃瓢虫。

学习要点

图5-1 瓢虫快跑游戏手机截屏
在开始探索本章之前,我们假设你已经完成了第3章MoleMash的学习,并熟悉了过程创建、随机数生成、Ifelse块以及ImageSprite、Canvas、Sound和Clock组件。 本章在复习MoleMash以及前几章内容的基础上,主要介绍以下内容:

  • 使用多个ImageSprite组件,并检测它们之间的碰撞;
  • 使用OrientationSensor(方向传感器)组件检测设备的倾斜,并用它来控制ImageSprite;
  • 改变ImageSprite的显示图片;
  • 在Canvas组件上画线;
  • 用Clock组件控制多个事件;
  • 用变量来记录数值(瓢虫的能量水平);
  • 创建和使用带参数的过程;
  • 使用and块。

设计组件

在应用中,使用一个Canvas组件作为三个ImageSprite组件的活动场地,三个ImageSprite组件分别代表瓢虫、蚜虫和青蛙,此外,还要为青蛙配一个声音组件。OrientationSensor(方向传感器)通过测量设备的倾斜来移动瓢虫,Clock组件用来改变蚜虫的运动方向。另有一个显示瓢虫能量水平的Canvas组件;一个重新启动按钮,当瓢虫饿死或被吃掉时,用来重新启动游戏。表5-1提供了本应用中使用的全部组件列表。

表5-1 瓢虫快跑游戏中的所有组件
组件类型 面板中分组 命名 作用
Canvas Drawing and Amination FieldCanvas 运动场地
ImageSprite Drawing and Amination Ladybug 用户控制的角色
OrientationSensor Sensor OrientationSensor1 测试手机的倾斜,控制瓢虫移动
Clock User Interface Clock1 决定何时改变Imagesprite的方向
ImageSprite Drawing and Amination Aphid 蚜虫:瓢虫的捕食对象
ImageSprite Drawing and Amination Frog 青蛙:瓢虫的捕食者
Canvas Drawing and Amination EnergyCanvas 显示瓢虫的能量水平
Button User Interface RestartButton 重启游戏
Sound Media Sound1 青蛙吃瓢虫时发出的声音

准备开始

下载瓢虫、蚜虫、死瓢虫及青蛙的图像,此外还有青蛙的声音文件。 登陆App Inventor网站,建一个名为“LadybugChase”新项目,屏幕标题设置为“瓢虫快跑”。打开块编辑器并连接到测试设备,将下载的图片及声音文件上载(Upload file)到媒体面板。 如果使用设备而不是模拟器,你需要禁用“屏幕自动旋转”功能,否则当设备旋转时,会改变设备的显示方向。在大多数设备上,可以点击设置->显示,然后取消选中的“屏幕自动旋转”复选框即可。

活动的瓢虫

在这个“第一人称”的游戏中,瓢虫代表玩家,玩家通过倾斜手机来控制瓢虫的运动。与MoleMash不同,这里玩家被带入游戏,而不是在设备以外用手触碰。

添加组件

在前几章,我们一次性地创建了所有的组件,但这不是开发人员的习惯做法。相反,通常每次只创建一部分组件,编写相应的程序,并进行测试,然后在进入到下一部分。在本节中,我们先来创建瓢虫并控制它的运动。

  • 在组件设计器中创建一个Canvas,命名为FieldCanvas,并设置其宽度为“Fill parent”,高度为300像素;
  • 在FieldCanvas上放置一个ImageSprite,重命为Ladybug,并设置其Picture属性为活的瓢虫图片。不必在意它的x、y属性,这取决于ImageSprite被放在画布上的位置。

也许你已经注意到,ImageSprites还有Interval、Heading以及speed属性,而这些都是在本程序中要用到的:

  • Interval属性:在本游戏中可以设置为10(毫秒),来设定ImageSprite自身的移动频率(而不是像MoleMash中那样,运动被MoveTo过程所控制);
  • Heading属性:指示ImageSprite将要移动的方向。例如:0表示向右,90表示向上,180表示向左,等等。现在就让它取默认值——向右,我们将在块编辑器中改变它;
  • Speed属性:指定ImageSprite在每个时间间隔内移动的距离(单位为像素)。我们将在块编辑器中设置Speed属性。

瓢虫的运动由OrientationSensor通过检测设备的倾斜程度来进行控制;Clock组件用来每隔10毫秒(每秒100次)检测一次设备的方向,并相应地改变瓢虫的Heading(方向)属性 。我们将在块编辑器中做如下设置:

  1. 添加OrientationSensor组件,它将出现在“不可见组件”区域;
  2. 添加Clock组件,它也将出现在“不可见组件”区域,并设置其TimerInterval属性为10毫秒。对照图5-2检查添加的组件。

图5-2 在组件设计器中为动画瓢虫设置用户界面

添加行为

切换到块编辑器,创建名为UpdateLadybug的过程(procedure)及Clock1.Timer块,如图5-3所示。尝试不使用抽屉,直接输入块的名字(如“when Clock1.Timer”)来生成块。(请注意,对数字100的乘法操作使用的是星号(*),但图中看不到。)虽然可以单击右键选择添加注释,但这不是必须的。 在UpdateLadybug过程里用到了两个OrientationSensor最有用的属性:

  • Angle(角度):表示设备倾斜的方向;
  • Magnitude(幅度):表示设备的倾斜程度,范围从0 (不倾斜)至1(最大倾斜)。

Magnitude乘以100是告诉瓢虫,在每个时间间隔(TimerInterval)内,在某个特定的方向,移动的距离在0到100像素之间。时间间隔为之前在组件设计器中设定的10毫秒。 虽然在连接设备上可以测试瓢虫的移动,但与打包下载到设备上的运行效果相比,瓢虫的速度要么太慢,要么太快。对于安装运行的应用,如果太慢,可以增加速度;相反,则减小速度。

图5-3 每隔10毫秒改变一次瓢虫的方向及速度

显示能量水平

在第二个Canvas组件上用一个红色线条来显示瓢虫的能量水平。线条高度为1个像素,宽度为瓢虫的能量值,取值范围从200(健康)到0(死)。

添加组件

在组件设计器中,在FieldCanvas下方创建一个新的Canvas组件,命名为EnergyCanvas;设置Width属性为“Fill parent”,Height属性为1个像素。

创建变量:Energy

在块编辑器中,创建一个初始值为200的变量来记录瓢虫的能量水平。(还记得吧,在第2章PaintPot中,第一次使用变量dotSize)以下是具体步骤:

  1. 在块编辑器中,拖出一个initialize global name to块,将name改为energy;
  2. 如果energy块的右侧插槽内有其他块,删掉它:选中并按Delete键或直接拖到垃圾桶;
  3. 创建一个数组块200(直接输入数字200或拖动Math抽屉中的0块),然后插入initialize global energy to块,如图5-4所示。

图5-4 将变量energy初始化为200

图5-5 从初始化变量块中获得set及get块
图5-5中显示了当鼠标悬浮在初始化变量块的“energy”文本上时,呼出了全局变量energy的“get”及“set”块;

画出能量条

我们要在变量energy与红色线条之间建立通信,使线条长度(像素)与能量值相等。为此创建如下两个类似的组块:

  1. 在EnergyCanvas上从(0, 0)点到(energy, 0)点画一条红线,以显示当前的能量水平;
  2. 在EnergyCanvas上从(0, 0)点到(EnergyCanvas.Width, 0)点画一条白线,在画新能量水平线之前,清除当前的能量水平线。(记得前面设置EnergyCanvas.Width为“Fill parent”。)

然而,最好能创建一个过程,能用任何颜色在EnergyCanvas上画任意长度的线。为此,需要定义两个参数:length(长度)和color(颜色),当程序被调用时,我们只需要指定参数值,就像在MoleMash一章中调用random integer内置过程一样。下面是创建DrawEnergyLine过程的步骤,如图5-6所示。

  1. 进入Procedures抽屉,拖出一个to procedure块;
  2. 点击过程名(可能是“procedure” ),改为“DrawEnergyLine”;
  3. 点击过程块左上角的蓝色方块,呼出两个块:input及input x;
  4. 将input x块插入到input块内,将x修改为color;
  5. 重复步骤4:插入第二块input x并命名为“length”;
  6. 按照图5-6所示,为该过程添加的其余的块:将鼠标悬停在to DrawEnergyLine块的参数color及length文本上,获得get color及get length块;或者从Variables抽屉中直接拖出get块,插入到to DrawEnergyLine内部的块中,点击下拉菜单选择color或length。

图5-5a 为DrawEnergyLine过程添加输入(参数)

图5-6 定义过程DrawEnergyLine
现在,你已经掌握了创建过程的窍门,让我们再写一个DisplayEnergy的过程,两次调用DrawEnergyLine过程:第一次用来擦除旧线(覆盖整个EnergyCanvas的白线),第二次用来显示新的能量线,如图5-7所示。

图5-7 定义过程DisplayEnergy

DisplayEnergy过程由以下四行命令组成:

  1. 设定画笔颜色为白色;
  2. 画一条贯穿EnergyCanvas的横线(1个像素高);
  3. 设定画笔颜色为红色;
  4. 画一条长度等于energy值的线。
提示:将若干行代码规整到一个过程中,通过调用这个过程来取代逐行地执行这些代码,这个过程被称作重构,这种强大的技术使得程序更易于维护,也更可靠。在这种情况下,如果我们想改变能量线的高度或位置,我们只需对DrawEnergyLine过程做一次修改,而不必分两次来完成这一修改。

饥饿而死

不同于前几章的应用,本游戏设定了结束环节:如果瓢虫吃不到足够的蚜虫,或者被青蛙吃掉,则游戏结束。此时我们希望瓢虫不再移动(设置Ladybug.Enabled为false),并将活瓢虫图片换成死瓢虫(将Ladybug.Picture设置为已上传的图片文件名)。GameOver过程的创建如图5-8所示。

图5-8 定义GameOver过程

再按图5-9所示向UpdateLadybug(由Clock.Timer每10毫秒调用一次)添加红框内的代码:

  • 减少瓢虫的能量(energy = energy – 1);
  • 显示新的能量水平(call DisplayEnergy);
  • 如果energy值为0则游戏结束。
测试:你可以在设备上测试这段代码,并验证能量水平随时间的减少,并最终导致瓢虫死亡。重启应用可以点击“Reset Connection->AI Companion”。

图5-9 UpdateLadybug过程的第二个版本

添加蚜虫

下面来添加蚜虫,即让蚜虫在FieldCanvas上浮动。如果瓢虫撞上蚜虫(视同“吃”掉它),则瓢虫的能量水平升高,而蚜虫消失,且稍后会再次出现。(在用户看来,这完全是另一只蚜虫,但实际上是同一个ImageSprite组件。)

添加一个ImageSprite

添加蚜虫首先要回到组件设计器,创建另一个ImageSprite,要确保它不落在瓢虫上,命名为Aphid,其属性设置如下:

  1. Picture属性:设置为已上传的蚜虫图像文件;
  2. Interval属性:设置为10,即:像瓢虫一样,每10毫秒移动一次;
  3. Speed属性:设置为2,因此蚜虫移动不会太快,以便让瓢虫能抓住它。

不必在意它的x、y属性(只要不是在瓢虫上)或title属性,这些可以在块编辑器中设置。

控制蚜虫

实验发现,蚜虫每隔50毫秒(Clock1跳动5次)改变一次方向的效果最好。可以通过创建第二个Clock组件,并设定其TimerInterval属性为50毫秒来实现这一效果。但是,我们希望能够尝试不同的技术:使用random fraction(随机分数)块,每次调用,它都将返回一个≥0但<1的随机数。创建UpdateAphid过程,并用Clock1.Timer来调用它,如图5-10所示。

块的作用

定时器每次跳动(每秒100次)都将调用UpdateLadybug及UpdateAphid过程。UpdateAphid过程首先生成一个介于0到1之间的随机数,例如0.15,如果该数<0.20(在20%的时间里),蚜虫将改变方向,改变的角度为0到360之间的随机数;如果该数≥0.20(在其余80%的时间里),蚜虫方向保持不变。

图5-10 添加UpdateAphid过程

瓢虫吃掉蚜虫

下一步,当他们碰撞时,让瓢虫“吃掉”蚜虫。幸运的是,App Inventor提供了ImageSprite组件之间的碰撞检测。问题是:当瓢虫与蚜虫碰撞时,会发生哪些事情?在继续阅读之前,请你停下来想想这个问题。 为了处理瓢虫与蚜虫的碰撞,创建EatAphid过程,其具体步骤如下:

  • 瓢虫的能量水平上升50,来模拟享受美食;
  • 让蚜虫消失(设置其Visible属性为false);
  • 让蚜虫停止移动(设置其Enabled属性为false);
  • 让蚜虫移动到屏幕上任意位置(这与MoleMash中移动地鼠遵循了相同的编码方式)。

请对照图5-11检查您的块。如果你还能想到发生其他事情,比如音效,可以自行添加。

图5-11 创建EatAphid过程

块的作用

每次调用EatAphid,变量energy增加50,缓解了瓢虫的饥饿。然后,蚜虫的Visible及Enabled属性都被设置为false,看上去像是消失了。最后,产生随机的x、y坐标,并调用Aphid.MoveTo,这样,蚜虫会在一个新位置再次出现(否则,它一出现便会被立即吃掉)。

瓢虫与蚜虫之间的碰撞检测

图5-12显示了在瓢虫与蚜虫之间做碰撞检测的代码。

图5-12 检测并处理瓢虫与蚜虫之间的碰撞

块的作用

当瓢虫与另一个ImageSprite碰撞时,将调用Ladybug.CollidedWith,参数“other”指向任何与瓢虫发生相撞的ImageSprite。此时,只有蚜虫可以碰撞,但稍后会有青蛙加入进来。我们采用防御性编程方式,即在调用EatAphid之前,要确认碰撞的对象就是蚜虫;此外还要确认蚜虫可见,否则,蚜虫在被吃掉之后而重新出现之前,还会与瓢虫再次碰撞。如果缺少这项确认,隐形的蚜虫会被再次吃掉,并引起能量水平的再次增加,这会让用户感到费解。

提示:防御性编程是一种避免错误的编程方式,当程序被修改时,仍然可以正常工作。在图5-12中,对other=Aphid的检查并不是绝对必要的,因为此时瓢虫可碰撞的唯一对象就是蚜虫,但检查可以防止后续程序的错误:当添加另一个ImageSprite(青蛙)时,如果忘记了修改Ladybug.CollidedWith,程序就会出错。通常来说,程序员修复bug的时间要多余写新代码的时间,所以多花一点时间尝试防御型编程是非常值得的。

蚜虫的回归

最终蚜虫要重新出现,按图5-13所示修改UpdateAphid:仅当蚜虫可见时,令其改变方向(改变一个不可见的蚜虫岂不是浪费时间。);若蚜虫不可见(如刚刚被吃掉),将有1/20(5%)的机会重新出现,或者说会被再吃掉。

图5-13 修改UpdateAphid使隐形蚜虫起死回生

块的功能

UpdateAphid变得有些复杂,让我们仔细推敲一下:

  • 如果蚜虫可见(这应该是常态,除非刚刚被吃掉),UpdateAphid的行为没有变化,即有20%的几率改变方向;
  • 如果蚜虫不可见(刚被吃掉),则执行“else”部分。首先生成一个随机分数,如果它<0.05(在5%的时间里),蚜虫再次变得可见并且可用,即,有资格被再次吃掉。

因为Clock1.Timer每隔10毫秒调用一次UpdateAphid,而当蚜虫隐形后,只有1/20(5%)的机会恢复可见,因此蚜虫平均重现的时间是200毫秒(1/5秒)。

添加重新启动按钮

在测试蚜虫被吃的新功能时,你可能已经注意到,游戏的确需要一个重新启动按钮。(在创建应用过程中,将应用分解成小的功能模块,完成一块就测试一块,这种开发模式大有裨益。在测试过程中,经常会发现一些被忽略了事情,一边做一边测一边改,比起整个应用完成之后再回来做修改,要容易得多。)在组件设计器中,将Button组件添加在EnergyCanvas下方,改名为“ResetButton”,并设置其Text属性为“重新启动”。 在块编辑器中,创建当RestartButton被点击时的代码,如图5-14所示:

  1. 能量水平设置回200;
  2. 重新使蚜虫可见并且可用;
  3. 重新使瓢虫可用,并将其图片改为活的瓢虫(除非你想要僵尸瓢虫!)。

图5-14 按下“重新启动”按钮让游戏重新开始

添加青蛙

到目前为止,让瓢虫活着并不难,因此我们需要一个捕食者。就是说我们要添加一个奔向瓢虫的青蛙,如果发生碰撞,瓢虫被吃掉了,游戏结束。

让青蛙追捕瓢虫

首先回到组件设计器,在FieldCanvas上添加第三个ImageSprite组件Frog,设置其Picture属性为相应的图片,interval属性为10,Speed为1,让它的移动速度慢于其他生物。 图5-15显示了Clock1.Timer中调用了新创建的过程。

图5-15 让青蛙向着瓢虫移动

块的功能

现在你应该可以熟练地使用随机分数来控制事件的发生几率了,这里,青蛙有10%的机会直接直奔向瓢虫。这里用到了三角函数,但别害怕,你不必明白其中的道理!App Inventor提供了大量的数学函数,也包括三角函数。本例中使用的ATAN2(反正切)块,返回值是一个角度,该角度由一组给定的x、y值相对应。(如果你熟悉三角函数,会发现求解ATAN2时所用的y值与你所期望的y值符号正好相反,即y的减法顺序是反的,这是因为在Android的画布上,向下是y坐标的增加方向,这与标准的x-y坐标系正相反。)

让青蛙吃掉瓢虫

现在需要修改碰撞代码,如果瓢虫与青蛙碰撞,能量水平以及能量线都将变为0,且游戏结束,如图5-16所示。

图5-16让青蛙吃掉瓢虫

块的功能

在第一个if(瓢虫与蚜虫的碰撞检测)块的基础上,添加了第二个if块,来检测瓢虫与青蛙的碰撞。如果瓢虫和青蛙碰撞,将发生三件事情:

  1. 变量energy降为0,瓢虫失去生命力;
  2. DisplayEnergy被调用,以清除此前的能量线(并绘制新的空白线);
  3. 调用前面写过的GameOver过程,以便让瓢虫停止移动,并将图片改为死瓢虫。

瓢虫回归

RestartButton.Click已经用程序将死瓢虫图片替换成了活的图片。现在,需要添加代码将瓢虫移动到任意位置。(想想看,在新游戏开始时,如果没有移动瓢虫,会发生什么?瓢虫与青蛙的位置关系如何?)图5-17显示了游戏重新启动时用来移动瓢虫的块。

图5-17 ResetButton.Click的最终版本

块的功能

两个版本的RestartButton.Click之间,唯一的差别就是Ladybug.MoveTo块及其参数。调用了两次内置的随机整数函数,分别用于生成合法的x、y坐标。虽然没有任何设置来防止瓢虫与蚜虫、瓢虫与青蛙的位置上的重叠,但几率起到了决定性作用。

测试:重新启动游戏,并确信瓢虫出现在一个新的任意位置。

添加音效

测试游戏时,你可能注意到:当蚜虫或瓢虫被吃掉时,缺少良好的反馈。要添加音效及触觉反馈,请执行以下操作:

  1. 在组件设计器中添加一个Sound组件。设置其Source属性为已上传的声音文件;
  2. 进入块编辑器,进行如下操作:
    • 在EatAphid过程中添加Sound1.Vibrate块,参数为100毫秒,以便在蚜虫被吃掉时,设备产生振动;
    • 在Ladybug.CollidedWith中调Sound1.Play,位置在调用GameOver之前,以便当青蛙吃掉瓢虫时发出叫声。

改进

下面这些想法目的是改进游戏,或者让游戏更个性化:

  • 目前,当游戏结束时,青蛙和蚜虫还在移动,这与其Enabled属性有关:在GameOver中将其设置为false,并在RestartButton.Click中重新设置为true;
  • 设置并显示一个分数,来表示瓢虫的存活时间。你可以用Label来显示一个数值,该数值在Clock1.Timer内不断递增;
  • 将EnergyCanvas的Height属性增加为2,以便使能量条更加明显,并在DrawEnergyLine内画两条线,一个在另一个之上。(使用一个过程,而不是复制代码先擦除再重绘能量线,这样做的另一个好处是:如果你需要修改线的粗细、颜色或位置时,只需要修改一处的代码。)
  • 添加背景图和更多音效来渲染气氛,比如用真声或预警声来提示瓢虫能量水平的降低;
  • 让游戏随时间推移而变得越来越难,如增加青蛙的速度,或降低Interval属性值;
  • 从技术上来说,被青蛙吃掉的瓢虫应该消失。改变游戏规则:如果瓢虫被吃,则隐形;如果是饿死,则显示死瓢虫图;
  • 将瓢虫、蚜虫和青蛙的图片换成更适合你个人口味的图片,如霍比特人、半兽人以及邪恶的巫师;或者是反叛星际战斗机、能源舱及帝国战机。

小结

已经有两个游戏被你收入囊中(假设你学习了MoleMash),现在你该知道如何创建自己的游戏了,这是许多新程序员或有志者的目标!具体来说,您学习了:

  • 可以创建多个ImageSprite组件(瓢虫,蚜虫和青蛙),并在它们之间做碰撞检测;
  • 用OrientationSensor可以检测设备的倾斜,而测得的值可用于控制sprite(或你能想到的任何其他对象)的移动;
  • 一个Clock组件可以控制多个发生频率相同(改变瓢虫和青蛙的方向),或通过使用random fraction块来控制频率不同的事件。例如,如果你想在一个周期中,有大约1/4(25%)的时间里会发生某事件,只要将它放在if块中,并设定条件为random fraction的结果<0.25即可;
  • 一个应用中可以使用多个Canvas组件,我们的例子中有两个,一个用于游戏场地,另一个用于变量的图形化显示(而不是用Label显示);
  • 用户自定义过程可以使用参数(如在DrawEnergyLine 中使用的“color”及“length”)来控制其行为,这极大地扩展了过程抽象的能力。

另一个游戏中常用的组件是Ball,与ImageSprite唯一不同的是,它的外观是一个被填充的圆形,而不是一张任意的图片。  

附录

资源下载

英汉对照

  • orientation: 方向
  • field: 场地
  • ladybug: 瓢虫
  • aphid: 蚜虫
  • frog: 青蛙
  • energy: 能量
  • restart: 重新开始
  • chase: 追逐,奔跑
  • upload: 上载,上传
  • file: 文件
  • interval: 间隔
  • heading: 前进方向
  • speed: 速度
  • timer: 计时器
  • updat: 更新
  • angle: 角度
  • magnitude: 幅度
  • delete: 删除
  • procedure: 过程
  • input: 输入
  • display: 显示
  • enable: 使有效
  • reset: 重置
  • visible: 可见的
  • eat: 吃
  • collide: 碰撞
  • with: 与…
  • else: 否则
  • fraction: 分数

发表评论

电子邮件地址不会被公开。 必填项已用*标注

跳至工具栏