第六章 组块的移动

本章将添加四个控制按钮,并绘制四个方块组成的红色条形组块,通过点击控制按钮,来控制组块的左移、右移及加速下落。

第一节 按钮及水平布局组件

将开发工具切换到设计视图,我们先来添加必要的组件。

  1. 在画布下方添加一个水平布局组件,并设置其宽度为“充满”;
  2. 向水平布局组件中添加4个按钮,并分别做如下设置:
    • 将按钮重命名为“左移按钮”、“快落按钮”、“旋转按钮”及“右移按钮”;
    • 按钮宽度为“充满”;
    • 显示文本为“←”、“↓”、“旋转”及“→”;
    • 设“←”、“↓”、“→”为24号粗体字;
    • 设旋转按钮高度为充满,字号为20。

为了在预览窗口中看到完整的用户界面,勾选Screen1的允许滚动属性,结果如图6-1所示。

图6-1 向屏幕内添加四个按钮组件

第二节 绘制红色长条组块

一、确定绘图坐标

在俄罗斯方块游戏中,有一个四个方块连成一行(或一列)的组块,如图6-2所示,本节将以此为例,来讲解块的移动控制。

图6-2 两种红色组块

现在我们需要在同一行(或列)内依次绘制四个红色方块,这就产生了一个问题:在绘制一个方块时,我们是以方块所在的行和列来确定绘图坐标的,如图6-3所示,但现在需要在水平或垂直方向上绘制四个方块。对于水平组块来说,只有一行,因此它所在的行是可以确定的,但是它占据了四个列,那么以哪一列为基准列,来确定整个组块的位置呢?为了保证整个程序的一致性,我们必须做一个约定:对于水平组块,以右边数第二块为基准来确定组块所在的行和列;对于垂直组块,以最下面的块为基准来确定组块所在的行和列,这样我们就可以得出组块中每个方块的坐标计算方法,见表6-1。

图6-3 绘制单个方块时,用块所在行、列来计算绘图坐标

表6- 1水平及垂直组块中每个方块的绘图坐标

在游戏开始时,假设我们让组块完整地出现在屏幕顶端的中间位置,这时水平组块的行数为1,列数为7,而垂直组块的行数为4,列数也为7,如图6-4所示。

图6-4 红色组块的初始状态

在控制组块移动之前,我们需要先绘制出这两种组块,为此,我们需要对原有的画块过程进行改造,并创建两个新的过程——绘制红色水平组块、绘制红色垂直组块,来绘制游戏中真正要用到的两种组块。

二、改造画块过程

此前我们声明了两个全局变量:所在行、所在列,并以此来计算画块的坐标,实现画块功能。这两个全局变量适合于绘制一个方块,而现在我们需要绘制四个方块。对于水平条形组块来说,四个方块的所在行与基准行的值相同,但所在列有4个不同的值;同样,对于垂直条形组块,四个方块的所在列与基准列相同,但所在行也是4个不同的值。我们面临的问题是:如何让画块过程能够适应这种变化,用一对行、列值绘制出整个组块中的所有方块。

图6-5 绘制水平组块时需要替换“画块”过程中的“所在列”

在一个自定义的过程中,如果某些数据为变量,那么可以将这些变量转化为过程的参数,来提高过程的通用性。

在图6-5中,为了绘制水平组块,需要将所在列替换成四个不同的值,这恰好符合为过程添加参数的条件。如图6-6所示,我们将原来的全局变量所在行、所在列改名为基准行及基准列,并为画块过程添加了参数所在列,来适应水平组块中方块的x坐标随所在列变化的特点。

图6-6 将原有全局变量所在行、所在列改名为基准行、基准列

修改之后的过程,在绘制水平条形组块时,只需要将参数所在列的值分别设为(基准列-2)、(基准列-1)、基准列、(基准列+1),就可以完成四个方块的绘制。

同样,在绘制垂直条形组块时,基准列的值只有一个,但方块所在行分别为(基准行-3)、(基准行-2)、(基准行-1)、基准行,为了适应这样的变化,为画块过程再添加一个参数所在行,来替代对全局变量基准行的调用。修改后的结果如图6-7所示。

图6-7 为画块过程添加两个参数,可以绘制任意行、任意列的方块

注意:这里将变量的命名做了调整,将全局变量从原来的所在行、所在列改为基准行、基准列,以适应绘制多个方块时,行、列的值不唯一的情况。而将画块过程的参数设置为所在行、所在列,以符合参数的实际意义。

三、改造擦除过程

与画块过程类似,擦除过程也要适应在擦除组块时,行、列值不唯一的情况。改造的方法与画块过程类似,如图6-8所示。要留心的是,原来的擦除过程中,计算坐标的依据是与画块过程共用的全局变量所在行与所在列,因此,行数需要-2,而新的擦除过程直接以过程自带的参数所在行及所在列为计算依据,因此只需-1,如果忽略了这一点,程序将无法实现预期的效果,你可以试试看,我经历了这样的错误。

图6-8 擦除过程也要适应对组块的擦除操作

四、绘制组块

通过对画块过程的改造,我们很容易就定义了一个新的过程——绘制红色水平组块,如图6-9所示。

图6-9 四次调用画块过程(注意:后三次调用采用内嵌输入项的方式以减小代码高度)

在图6-9中,首先,四次调用画块过程,每次调用所提供的颜色及所在行参数是相同的,不同的只是所下列参数。其次,这样的代码似乎显得过于啰嗦,看起来有进一步整合的可能性:参数所在列的四个值分别为(基准列-2)、(基准列-1)、基准列、(基准列+1),它们之间的差值为1,这让我们想到了循环语句,“针对从(基准列-2)到(基准列+1)且增量为1的每个列,执行...”,这样岂不是可以让代码量减到最少。最终代码整理为图6-10的样子。

图6-10 经过整理之后的过程代码

同样的思路,我们很容易地写出了“绘制红色垂直组块”的过程,如图6-11所示。

图6-11 绘制红色垂直组块的过程代码

五、擦除组块

为了实现组块移动的效果,需要在某一时刻绘制出红色组块,同时擦除在上一时刻已经绘制的红色组块。擦除的方式与绘制相似,关键在于获得上一时刻的“基准行”与“基准列”的值。对于自由下落的块,其列的值不会改变,只是行数+1,但对于有用户参与(左右移动、快落及旋转)的下落过程,其上一时刻的值不能从当前的基准值中求得,因此,我们需要再声明两个全局变量,用于保存上一时刻的行、列的基准值。如图6-12所示,我们以“前一刻基准行”、“前一刻基准列”为依据,来擦除上一时刻的水平及垂直组块。至于如何求得上一时刻的行、列值,我们会在移动控制中进行讨论。

图6-12 声明全局变量来保存前一时刻的基准行、列值,并以此来确定擦除过程的参数

至此我们已经编写出绘制两种条形组块的绘制及擦除过程,下面以此为基础,来控制这两种组块的移动。相对于左右移动和快速下落而言,旋转的操作更为复杂,因此本章首先解决简单的左右移动和快速下落的操作,旋转的控制留到下一章讲解。

第三节 控制块的移动

一、组块的左右移动

  1. 功能描述:
    • 当用户点击左右移动按钮时,组块产生左右移动;
    • 由于画布边界的存在,因此,当组块下落到底部边界时,将停止向任何方向的移动;同样,组块的移动也不能超出画布的左右边界。
  2. 功能的实现

组块的自然下落要靠计时事件来推动,而左右移动是用户通过点击按钮来触发的,因此这里将创建两个新的事件处理程序:左移按钮点击事件处理程序(以下简称左移程序)及右移按钮点击事件处理程序(以下简称右移程序),并对计时程序进行改造。

我们先来改造之前的计时事件处理程序,依然以水平组块为例,如图6-13所示,特别要提示的是,每一时刻在红色水平组块绘制完成后,都要将当前基准行的值赋给前一刻基准行,然后再对当前基准行的值+1,以便在下一时刻擦除上一行的组块,并绘制下一行的组块。这里调整了绘制过程与擦除过程的执行顺序,是为了防止擦除操作误将当前时刻绘制的组块擦掉(在垂直组块中存在这样的情况)。

图6-13 自由下落的代码比较:红色水平组块与单个方块

在此基础上,我们来处理用户点击按钮的事件,先来看看左移操作。当左移按钮被点击时,组块的基准列发生改变,变为(基准列-1),但当基准列 = 3 时,组块已经到达左边界,无法继续向左移动。同样,当组块向右移动,且基准列=11时,组块已经到达右边界,将无法继续向右移动。如图6-14所示。

图6-14 组块左右移动的边界

基于以上分析,需要为组块的左右移动设置条件:当基准列>3时才能左移,当基准列<11时,才能右移。具体代码如图6-15所示。

图6-15 左移、右移按钮点击事件处理程序

为什么在按钮点击事件处理程序中只改变基准列的值,而不进行组块的绘制与擦除呢?这里面隐含着一个游戏开发的原则:图像的绘制与数据的更新,是游戏开发中的两类不同的操作,要尽可能地将两类操作分别加以处理,以避免出现重复操作,并且为错误的纠正提供方便。因此在这里,让左移及右移程序负责修改基准列的值,而由计时程序来处理组块的绘制及擦除操作,这样看起来组块的移动会有些延迟(不会超过一个计时间隔),但可以确保整个程序的正确运行。

二、组块的快速下落

1.、功能描述:

我们在设计游戏时,对于“快落”的描述可以有很多种,如点击一次按钮,可以让组块多下落1行、2行或多行,这个快落的策略会影响代码的编写,而且主要影响条件语句的编写,无论如何,组块不能落到底部边界以外。如果点击一次快落按钮导致组块多下落1行,则每个计时间隔内组块将下落两行(在计时事件和快落事件中分别下落一行),则快落的执行条件是“基准行+2≤16”,或者“基准行≤14”,或者“基准行<15”;如果点击一次快落按钮导致组块多下落2行,则快落执行条件变为“基准行+3≤16”,或者“基准行≤13”,或者“基准行<14”,以此类推。

2、功能实现:

这里我们假设每点击一次快落按钮,组块多下落1行,则执行“基准行=基准行+1”的条件是“基准行<15”,具体代码如图6-16所示。

图6-16 快落按钮点击事件处理程序(在一个计时间隔内一次下落两行)

我们可以在手机上对上述代码进行一次测试,看看代码的执行效果如何,并对执行效果进行评价,看是否有需要改进的地方。

第四节 逐步了解开发工具

本章中我们瞥见了一丝游戏的端倪,开始创造并控制游戏中的物件,同时也进入了一种富有挑战意味的编程状态:一个看似简单的动作背后隐藏着繁琐而又严谨的逻辑。初学者面对这样的场面,会感觉到手足无措,但这也正是你进步的开始。在现实中思考游戏的逻辑,并在工具中寻求实现的手段,这就是程序员的成长之路。

一、事件驱动

最初的计算机程序用于科学计算及信息处理,通过输入特定的指令来触发程序的运行。随着互联网技术的发展,计算机程序走下神坛,变换面孔,以网页的形式面向最普通的人群,用户通过点击页面上的交互元素来推动程序的运行。再进而,智能终端(手机、平板电脑)的普及,为人机之间的交互提供了更加丰富的手段,甚至收到一条短信、打进一个电话都可以引发一段程序的运行,更不必说在终端屏幕上的触摸以及划出的各种手势。

无论是输入指令,还是触摸屏幕,它们的目的都是触发一段程序的运行,就像要射出子弹必须扣动扳机一样,而“扣动扳机”就是一个事件,是引发后续改变的一个触发器。因此,我们试图这样来定义事件:事件是引发某段程序开始运行的触发器。

在安卓系统的应用程序中,事件的种类异常丰富,大致可以划分为两类:用户事件与系统事件。

  1. 用户事件:用户事件指的是在应用程序运行过程中,由用户操作引发的事件。最常见的就是按钮点击事件,当然,点击是通过触摸屏幕来实现的。最富特色的用户事件来源于设备中的各类传感器,如加速度传感器、方向传感器等,用户可以通过摇晃或转动手机来触发程序的运行。
  2. 系统事件:在应用程序运行过程中,用户事件之外的引发某段程序运行的因素。如计时器组件的计时事件、收到短信事件、接通及结束通话事件、网络数据库的获得数据事件等等。

在App Inventor中,许多组件,包括可视组件及非可视组件,都具有触发事件的功能,例如在本章中,用到了屏幕的初始化事件、计时器的计时事件以及按钮的点击事件。一但在程序中添加了具有触发事件功能的组件,就可以在编程视图中点击该组件。在弹出的代码块抽屉中,最上端的若干个黄色块就是用来定义事件处理程序的代码块,如图6-17所示。

图6-17 按钮组件相关的事件处理程序

在游戏类应用程序中,人机交互非常频繁,充分利用App Inventor提供的各种交互类组件,以及安卓设备自身的传感器,可以开发出丰富多彩的充满趣味性的产品。

二、条件语句

“如果...则”、“如果...则...否则”被称为“条件语句”,一个程序中最重要的逻辑都体现在条件语句中。条件语句起到引导编程走向的作用,是所有编程语言中都具备的语言要素。 本章用按钮组件来控制组块的左右移动及快速下落,这些操作虽然简单,但存在着某些限定条件,当不满足条件是,操作是无效的。如当组块碰到画布的左边界时,左移按钮的操作将失效。

本章使用的是最简单的条件语句,即,“如果...则”,这个语句可以扩展成更为复杂的形式。如图6-18所示,在“如果...则”代码块的左上角有一个蓝色方形标记,点击蓝色标记将弹出代码块的扩展框,可以将“否则”或“否则,如果”块拖拽到“如果”块中,来完成对条件语句的扩展。在App Inventor中,凡是带有这种蓝色标记的代码块,都被成为可扩展代码块。图中显示了几种扩展条件语句的方法,在下一章中将会使用到其中的一些方法。

图6-18 扩展条件语句的方法

对于“如果...则...否则”来说,代码块分为三个部分,以图6-19中的代码为例:

  1. 如果:即判断条件,是一个逻辑表达式,如果逻辑表达式的值为“真”,程序将执行“则”后面的代码;如果逻辑表达式的值为“假”,程序将执行“否则”后面的代码;
  2. 则:条件语句的一个分支,当满足条件时,执行此后的代码;
  3. 否则:条件语句的另一个分支,当条件不满足时,执行此后的代码。
图6-19 具有两个分支的条件语句

图6-19中的代码是一个简单的双分支条件语句:当水平组块的基准列的值等于3时,让左移按钮处于不可用的状态;当基准列的值不等于3时,按钮处于可用状态。

小结

  1. 通过使用带有参数的过程,来绘制任意行、任意列的方块,提高了代码的复用性;
  2. 使用循环语句来绘制并擦除条形水平及垂直组块;
  3. 在上一章《让方块动起来》的基础上,在计时事件中实现条形组块的自由下落,并通过按钮点击事件来控制下落中的组块,实现组块的左右移动及快速下落;
  4. 理解安卓应用中事件驱动的编程模式。