第十五章 完善游戏功能

为提高游戏的趣味性及完整性,本章为游戏添加如下功能:

  • 利用安卓设备的触屏功能,用划屏手势来控制组块的移动;
  • 保存最高成绩记录,并在游戏结束时,提示玩家;
  • 为游戏添加退出功能;
  • 预报下一个出现的块;
  • 添加组块直落功能——长按快落按钮时,组块立刻一落到底。

第一节 用手势控制组块移动

画布组件能够侦测到屏幕划动事件,当用户手指在画布区域划过时,会触发“划动”事件,我们利用这一功能,来实现手势对组块移动(左右移动、快落及旋转)的控制。

一、改造控制按钮的点击事件处理程序

由于手势的作用与此前按钮的作用完全相同,因此,我们可以借用按钮点击事件处理程序(以下简称点击程序)中的代码来处理划动事件,为此,我们将原有点击程序中的代码封装为独立的过程,供两类事件处理程序调用。如图15-1所示,将原有的左移程序封装为左移过程,并在左移程序中调用该过程。

图15-1 创建左移过程,并在左移程序中调用该过程

同样,创建右移、快落及旋转过程,并在各自的点击程序中调用它们。如图15-2所示,这里我们将旋转过程中的两个条件判断调换了位置,首先判断组块编号是否=3,这样可以避免对3号组块进行触犯禁区检测,减少不必要的运算。这样做的目的在于提高程序的执行效率,但对于人类的感受能力来说,却未必能够体会到。不过,这应该是程序员的一种好习惯。

图15-2 定义四个移动控制过程,并在按钮的点击程序中调用它们

二、了解划动事件的相关参数

图15-3显示的是画布划动事件处理程序的定义块,这是一个带有七个参数的事件处理程序,如图所示:x坐标、y坐标、速度、方向等,当划动事件发生时,这些参数中记录了与此次事件相关的值,可以在处理程序中读取它们的值。我们在俄罗斯方块游戏中只用到了方向值,因此我们这里只介绍方向参数的含义。

图15-3 带有许多参数的划动事件处理程序
图15-4 划动事件中对方向的定义

如图15-4,假设手机屏幕正对着我们,则向右的方向为0°,从0°开始逆时针旋转为正角度,转向左侧时为180°;顺时针旋转为负角度,转向左侧时为-180°。注意,这里-180°与180°为同一个方向。

有了以上知识,我们就可以开始编写划动程序了。如图15-5所示,有了之前的代码储备,这个程序写起来易如反掌。这里设置了划动事件起作用的条件,只有当计时器启用计时功能时,才执行该程序。经过测试,一切正常,不过程序的反应有些慢,可能与测试状态有关,稍后我们把程序编译为.apk文件,安装到手机上,看看是否反应会快一些。

图15-5 划动事件处理程序

第二节 保存成绩及退出游戏

在App Inventor中,允许将数据保存到手机上,使用的组件是微数据库组件(TinyDB)。这里我们先介绍一下该组件。

一、本地数据库组件

将开发环境切换到设计视图,在左侧的组件面板中打开数据存储分组,第三项就是本地数据库组件。将组件拖拽到界面预览区域内,它将自动排列到预览区下方的非可视组件区,如图15-6所示。在图右侧的属性面板中,只有本地数据库的名称(本地数据库1),没有任何其他的属性需要设置。

图15-6 在设计视图中添加本地数据库组件

现在我们将开发环境切换到编程视图,在左侧代码块中选中本地数据库1,将打开与本地数据库组件相关的代码块,如图15-7所示。

图15-7 与本地数据库组件相关的代码块

图中有5个紫色的块,被称为本地数据库组件的内置过程,与我们的自定义过程相比,这些过程是App Inventor的系统中固有的过程。从输出结果上划分,其中第1、2及第5个过程为无返回值过程,第3、4个过程为有返回值过程;从输入的参数上划分,第2、4、5个过程在调用时需要提供参数,而另外两个则不需要提供参数(输入项)。

注意到有参数的过程,参数名称有三类:标记、数值及无标记返回。这些参数与本地数据库组件保存数据的方式有关。在App Inventor中,用本地数据库组件保存的数据采用“键值对”的格式,即,为每项数据设定一个名称,这个名称被称为键(key),与值(value)一一对应。
本地数据库组件既可以保存数据(使用第5个过程),也可以从手机中提取数据(使用第4个过程),其它过程的功能可以参见《App Inventor中文参考手册》( http://www.17coding.net/reference )。

二、功能描述

按照时间顺序描述功能如下:

  1. 游戏结束时,显示本次游戏得分;
  2. 从数据库获取最高分数的历史记录;
  3. 如果返回的分数为空,则将本次游戏分数写入数据库;
  4. 如果返回值不为空,显示历史记录,并将历史记录与本次游戏分数进行比较:如果本次游戏分数高于历史记录,则将新成绩写入数据库,否则,不保存成绩;
  5. 用户可以清除历史记录;
  6. 用户可以选择退出游戏或返回游戏。

三、可用组件

对话框组件、本地数据库组件。

现有程序使用的是对话框组件最简单的功能:当有组块触顶时,弹出对话框,提示用户“游戏结束”,然后对话框慢慢隐去。对话框组件还有许多复杂的功能,例如,显示必要的数据,提供若干个选择按钮等等。这里我们利用对话框的内置过程“显示选择对话框”,来显示历史记录以及本次得分,并提供两个选择按钮:返回游戏及清除成绩。如图15-8所示,

图15-8 对话框组件的高级功能

微数据库组件用于保存成绩,因为我们要保存的值是游戏的最高得分,我们可以把键设定为“最高分”。

四、编写——游戏结束

“游戏结束”的提示代码写在了计时程序中,而计时程序是我们整个游戏中最为重要的一个程序,为了保证这个程序的简洁坚固,我们要把一些细节的代码封装成过程,并在计时程序中调用这些过程。为此,我们创建一个游戏结束过程,来处理与此相关的操作。具体代码如图15-9及15-10所示:

  1. 声明临时变量历史记录,用来保存从本地数据库1中读出的最高分值;
  2. 成绩比较及保存:
    • 如果从未保存过历史记录,则本地数据库将返回空(“”),此时将本次游戏得分保存到本地数据库1中;
    • 如果历史记录不为空,且本次游戏得分高于历史记录,则保存本次游戏得分;
  3. 利用对话框组件显示成绩信息,并提供多项选择:
    • 如果历史记录为空,则显示本次游戏得分,否则,显示历史记录及本次得分;
    • 提供三个选择按钮,利用对话框1选择完成事件来分头处理不同选项:
      • 退出游戏:调用系统的退出程序功能(在编辑视图控制块分组中);
      • “返回”:利用这个返回按钮,可以返回游戏,并重新开始游戏;
      • 清除记录:调用本地数据库组件的内置过程清除所有数据来删除历史记录,并重新开始游戏。
图15-9 创建游戏结束过程
图15-10 在计时程序中调用游戏结束过程

五、编写代码——处理用户选择

游戏结束后,将弹出对话框,供用户选择; 程序将根据用户的选择,执行下一步的操作。

首先我们创建一个重新开始游戏过程,以供下一步操作调用。如图15-11所示。

图15-11A 定义重新开始游戏过程
图15-11B 定义重新开始游戏过程

接下来编写对话框的完成选择事件处理程序,来具体处理用户的选择,如图15-12所示。

图15-12 根据用户在对话框中的选择,执行下一步操作

六、测试

图15-13 在对话框中显示分数,并给游戏者多种选择

我们对游戏进行实时测试,发现如下问题:

  1. 虽然我们希望对话框能分行显示历史成绩和本次得分(在“本次游戏得分”之前添加换行符“\n”),但换行符不起作用,似乎被转换成了空格;
  2. 点击“退出游戏”按钮后,程序又返回游戏,并出现错误提示:“Closing forms is not currently supported during development.”,意思是,在开发阶段不支持退出功能;
  3. Cancel按钮的文字无法修改,如果它能显示“返回”就更好了,但这需要更改App Inventor的系统源码。 对于第一个问题,开发者无能为力,只能提交给App Inventor的开发工程师,修改系统的源代码;第二个问题,经过将程序编译打包安装到手机上测试,证明退出命令可以执行。

第三节 预报下一个出现的组块

一、添加一片新画布

在经典的俄罗斯方块游戏中,通常在游戏画面的右上方会有一个小窗口,来预报下一个将要出现的组块,我们这里也要添加这一功能,为此,需要将画布下方的按钮缩小,给画布上方留出一定的空间来显示预报组块。如图15-14所示,在页面顶端添加一个水平布局组件,设置其宽度为300像素(与画布的宽度相同),设置其水平对齐属性为右对齐,并在其中添加一个40×40像素、画笔线宽为9像素的画布,取名为预报组块。

图15-14 为游戏添加另一小片画布,用来预报下一个出现的块

二、预报功能描述

  1. 在游戏开始运行时,同时创建预报组块及下落组块;
    • 声明全局变量预报组块编号,并初始化为一个1~19之间的随机整数;
    • 在小画布中绘制预报组块;
    • 随时侦测下落组块是否触底或触块;
  2. 当下落组块触底或触块后,将预报组块转变为下落组块,并创建新的预报组块;

三、编写程序

1、添加全局变量预报组块编号

如图15-15所示,全局变量的初始值为1~19之间的随机整数。

图15-15 声明全局变量:预报组块编号

2、在屏幕初始化时绘制预报组块

首先需要确定组块在小画布中的位置。小画布的宽、高均为40像素,因此预报组块的方块应该为9个像素,方块之间间隔为1个像素,而且预报组块在小画布上的位置是固定的。图15-16中描述了1、2、16及17号组块在小画布中的位置,除了2号组块的基准行为4、17号组块的基准列为2之外,其余组块的基准行及基准列均为3。

图15-16组块在小画布中的位置

我们重温下落组块的绘制方法:在绘制组块过程中调用画块过程,在画块过程中调用画布的内置画线过程,并为化线过程指定起点及终点坐标。我们用同样的方法绘制预报组块,不同的是,后者的位置固定,而且画笔的宽度要小一些。

如图15-17,我们对比一下两个画块过程,不同之处在于画笔的宽度,预报方块的画笔宽度为9(体现为×10),而下落方块的宽度为24(体现为×25)。

图15-17 比较两个画块过程

再来看组块绘制过程,通过对画块过程的调用,来绘制完整的组块。如图15-18所示。

在绘制预报组块过程中,首先清除画布,并根据预报组块编号获取绘制组块的坐标列表;通过对坐标列表的遍历,绘制组块中的四个方块。这里需要注意的是,在绘制预报方块时,绝对行、列值不以基准行列值为基准,而以固定的行列值为基准。2号组块以(4,3)为基准,17号组块以(3,2)为基准,而其他组块均已(3,3)为基准。

图15-18 比较两种绘制组块过程

修改创建新组块过程,同时设置预报组块编号及下落组块的初始值,如图15-9所示。

图15-19 改造创建新组块过程

有了以上过程,就可以在屏幕初始化程序中绘制预报组块了,如图15-20所示。

图15-20 更新屏幕初始化程序

3、侦测下落组块的触底与触块

当屏幕初始化、下落组块触底或触块后,需要创建新组块,不同的是,原来的创建新组块过程中,随机生成一个1~19之间的整数作为组块编号,而在这里,让组块编号=预报组块编号,并在过程的末尾设置下一个预报组块的编号,如图15-19所示。

最后,在计时程序中调用创建新组块及绘制预报组块,这样,我们的功能就算实现了。

图15-21 在计时程序中调用绘制预报组块

经过测试,程序运行正常。只是有一个问题,画布下面的几个控制按钮变小了,点击起来命中率下降,而且不小心会碰到设备上的按键,意外退出,或弹出搜索等等,因此用划屏手势控制组块移动可能是一种好的选择。

另,可能是由于程序中的代码量过大,加之手机中运行的其他程序占用了设备的资源,在游戏测试过程中,偶尔会遇到方格背景不能显示,而出现白色背景,当组块下落时,组块经过的地方白色背景被恢复成灰色方格。

图15-22 测试预报组块功能

第四节 组块直落

利用快落按钮组件的长按事件来实现这一功能。像在第14章一样,我们按照三个步骤来实现这一功能:

  • 确定相关的数据表格;
  • 创建数据列表;
  • 编写相关代码。

不过我们不必建立新的数据表格,直落功能使用的数据及数据列表与快落功能相同,也与已经触块过程使用的数据及列表相同,我们这里将它们复制过来,如表15-1所示。并利用触块坐标列表来编写相关代码。具体代码如图15-23所示。

表15-1 19种组块的触块相对坐标

图15-23 求直落行过程

我们来解说一下求直落行过程:

  • 这是一个有返回值的过程,正如过程名称所提示的,返回值为最大可直落行;
  • 先假设局部变量最大可直落行=16,即,组块可以一落到底;
  • 根据组块编号从全局变量触块坐标列表中求局部变量触块坐标列表(包含1~4组坐标,是组块下边界方块的相对坐标);
  • 过程中使用了两层循环语句:
    • 外层循环对组块下方的所有行进行遍历,循环变量为测试行:
      • 首先判断是否最大可直落行=16:如果判断结果为真,则设局部变量可以直落=真,并执行内层循环,否则,进入下一次外层循环;
      • 首次执行外层循环时,由于最大可直落行=16,因此程序执行内层循环;
      • 如果在内层循环中判断出某一行可以直落=假,则设最大可直落行=测试行-1,这就意味着最大可直落行不再=16,程序将不会再次进入内层循环(不必再检测此行以下的行);
      • 如果内层循环检测出某一行可以直落=真,则保持最大可直落行的值不变(16),并进入下一次内层循环,直到检测出某一行的可以直落=假;
    • 内层循环对每一行中的触块坐标列表进行遍历:
      • 将相对坐标换算为绝对坐标;
      • 求绝对坐标位置方块的颜色码;
      • 一旦某个坐标位置的颜色码≠0,则可以直落=假;
    • 外层循2环将依据可以直落的值来处理最大可直落行,并将结果输出为返回值。

求直落行过程的示意图及流程图如图15-23所示,流程中最关键的步骤是外层循环的条件语句“如果最大基准行=16”,如果一旦发现某一行不能下落,就不必再检验这一行以下的所有行。试想,如果不设置这个条件,会出现什么样的后果?

图15-24 求直落行过程的示意图及流程图

下面为快落按钮添加长按事件处理程序,如图15-25所示。

图15-25 快落按钮的长按事件处理程序

同快落按钮点击事件处理程序相同,我们为组块的直落添加了“基准行<15”的条件,来避免不必要的程序调用。当满足条件时,让“基准行=直落行”。

经过测试,程序运行正常,并具备了组块直落功能。

程序做到这里,已经接近尾声,余下的是一些小修改:

  • 将Screen1的标题修改为“俄罗斯方块”;
  • 为项目设置一个图标,如图15-26所示,将图片上传,并设置为Screen1的图标属性;
  • 将五个按钮的字号统一为12。
图15-26 为项目设置图标

下一章我们将对全部代码进行归类整理,并绘制出它们之间的关系图,以便我们对程序有完整的认识。