> **作者介紹**
>
> **Ellen Spertus**
>
> 
>
> 本書的共同作者之一,美國加州奧克蘭市米爾斯大學的計算機科學教授,同時也是谷歌公司的資深科學家。她先后在MIT獲得了計算機科學與工程學士學位、電子工程與計算機科學碩士及博士學位,并利用暑假的空閑時間為微軟公司工作。她曾撰文探討技術及社會問題,而且經常將兩者相結合。1993年紐約時報曾以《改變計算機領域面貌的女性》為題介紹Spertus,并在后續的文章中稱其為“最性感的活著的極客”。2009年Spertus加入谷歌的App Inventor for Android團隊,并參與撰寫了本書的部分章節。
本章將創建一個“打地鼠”的游戲,游戲靈感來自一款經典的街機游戲Whac-A-Mole,其中的小動物會突然從洞中冒出,玩家則用木槌擊打它們,擊中得分。“打地鼠”的創作者是一名App Inventor團隊的成員,與其說她是為了測試sprite組件的功能(她做到了),不如說是她自己喜歡玩游戲。


**圖 3-1 打地鼠游戲的用戶界面**
當Ellen Spertus加入Google公司的App Inventor團隊時,她希望App Inventor也可以用于游戲的開發,因此她自告奮勇地承擔起sprites的實現任務。sprite原本用來表示神話中的角色,如仙女、妖精等,到20世紀70年代開始出現在計算機界,用來代表那些能夠在電腦屏幕上移動的圖像(在電子游戲中)。Ellen第一次使用sprite是在20世紀80年代早期,她曾經參加電腦訓練營并使用TI 99/4 編程。她在sprites以及“打地鼠”游戲上所做的努力,受到了雙重懷舊情緒的驅使——計算機以及游戲——她童年時代的最愛。
可以查看Android版“打地鼠”游戲的[視頻教程](http://www.appinventor.org/AndroidMash-steps)。【此教程由Wolber教授基于上一個版本的App Inventor錄制的,但同樣可以有助于理解開發過程。】
## **學習目標**
如圖3-1所示的“打地鼠”應用將實現以下功能:
* 一只地鼠隨機出現在屏幕上,每秒鐘移動一次;
* 如果手指觸碰到地鼠,則讓設備震動,顯示的命中數加1,地鼠隨機移動到一個新位置;
* 如果手指直接觸摸到屏幕但沒點擊中地鼠,則顯示失敗數加1;
* 點擊“重新開始”按鈕,游戲重新開始,命中和失敗的計數歸零。
## **學習內容**
本章內容覆蓋了以下的組件及概念:
* ImageSprite組件:具有觸感的可移動圖像;
* Canvas組件:容納ImageSprite的平臺;
* Clock組件:用來計時,讓sprite隨即移動;
* Sound組件:擊中地鼠時產生震動;
* Button組件:開始新游戲;
* Procedures:用來實現一系列的指令,可以重復調用,如移動地鼠;
* 產生隨機數;
* 使用加法塊(+)及減法塊(-)。
## **準備開始**
登陸App Inventor網站,開始新項目“MoleMash ”,將屏幕標題(title)設為“打地鼠”,并連接到測試設備。
下載地鼠圖片mole.png。下載方法:控制鍵+單擊(Mac)或單擊右鍵(Windows)并選擇“圖片另存為”或類似選項。下載成功后,在設計器組件列表下方的Media部分,單擊“Upload file…”,找到剛下載的文件mole.png并上傳到App Inventor中。
## **設計組件**
創建“打地鼠”游戲需要以下組件:
* Canvas組件:用來限定游戲中地鼠的活動區域;
* ImageSprite組件:用來顯示地鼠圖片,隨機移動,并具有觸感;
* Sound組件:當地鼠被觸摸到時,發出震動;
* Label組件:用來顯示“命中: ”、“失敗: ”以及命中、失敗的次數;
* HorizontalArrangements組件:用來放置Label組件,使組件的布局合理;
* Button組件:用來將命中及失敗次數歸零(重新開始游戲);
* Clock組件:使地鼠每秒鐘隨機移動一次。
表3-1顯示了應用中用到的全部組件。
**表3-1 “打地鼠”應用中的全部組件列表**
| 組件類型| 組件種類 | 命名 | 作用|
| --- | --- | --- | --- |
| Canvas | Drawing and Animation | Canvas1|ImageSprite的容 |
| ImageSprite | Drawing and Animation | Mole| 用戶點擊的目標 |
|Button | User Interface | ResetButton| 重新設置得分 |
| Clock| User Interface | Clock1 | 控制地鼠的移動頻率 |
| Sound | Media | Sound1 | 當地鼠被擊中時震動|
| Label| User Interface| HitsLabel | 顯示文字“擊中: ” |
| Label | User Interface| HitsCountLabel | 顯示擊中次數 |
| HorizontalArrangement | Layout | HorizontalArrangement1 |放置HitsLabel及HitsCountLabel |
| Label | User Interface | MissesLabel | 顯示文字“失敗: ” |
| Label | User Interface | MissesCountLabel | 顯示失敗次數 |
| HorizontalArrangement | Layout | HorizontalArrangement2 |放置MissesLabel及MissesCountLabel |
### **設置活動組件**
本節將設置游戲中所需的活動組件,下節再來設置顯示分數的組件。
1\. 找到Palette->Drawing and Animation->Canvas組件,拖入預覽窗口,采用其默認名稱Canvas1,設置Width屬性為“Fill parent”,即與屏幕等寬,設置Height屬性為300像素;
2\. 找到Palette->Drawing and Animation->ImageSprite,將ImageSprite組件拖入到Canvas1中的任何位置,在組件列表底部單擊rename,改名為“Mole”,設置其Picture屬性為之前上傳的mole.png;
3\. 找到Palette->User Interface->Button,拖動Button組件放在Canvas1下面,改名為“ResetButton”,并設置其Text屬性為“重新開始”;
4\. 找到Palette->User Interface->Clock,拖入Clock組件,它將落在預覽窗口下方的“非可是組件”區域;
5\. 找到Palette->Media->Sound,拖入Sound組件,它也將落在“非可視組件”區域。
現在組件設計器看起來應該如圖3-2(地鼠的位置有可能不同)。

**圖 3-2 組件設計器視圖中的所有“活動”組件**
### **布置Label組件**
現在設置顯示用戶得分的組件,即,顯示命中與失敗次數的組件。
1\. 找到Palette->Layout->HorizontalArrangement,拖動組件放在“重新啟動”按鈕的下方,保留HorizontalArrangement1的默認名稱;
2\. 從Palette->User Interface中拖動兩個Label組件到HorizontalArrangement1中;
* 將左側Label改名為HitsLabel,設置其Text屬性為“命中: ”(確保冒號后有一個空格);
* 將右側Label改名為HitsCountLabel,設置其Text屬性為“0”;
3\. 拖入第二個HorizontalArrangement,將其放在HorizontalArrangement1下面;
4\. 將兩個Label拖放在HorizontalArrangement2中;
* 左側Label改名為MissesLabel,設置其Text屬性為“失敗: ”(確保冒號后有一個空格);
* 右側Label改名為MissesCountLabel,設置其Text屬性為“0”。
你的屏幕看起來如圖3-3。

**圖 3-3 組件設計器視圖中“打地鼠”應用的所有組件**
## **為組件添加行為**
組件已經創建完成,下面切換到塊編輯器來實現程序的行為。設置的目標:①讓地鼠每秒鐘在Canvas1上隨機移動一次;②用戶拍打這只隨機移動的地鼠,應用顯示用戶命中或失敗的次數(注:建議用手指而不是木槌拍打!);按下“重新啟動”按鈕命中及失敗次數歸零。
### **移動地鼠**
在迄今為止完成的應用中,曾經調用過內置過程 ,如HelloPurr中的Sound1.Vibrate(震動)。假如App Inventor中有一個內置過程,可以將ImageSprite移動到屏幕上的某個隨機位置,那豈不是很好?可惜沒有,不過我們可以自己來創建過程!就像內置過程一樣,自己創建的過程會顯示在Procedures抽屜中,需要時可以隨時調用它。
具體來說,創建一個名為MoveMole的過程,讓地鼠在屏幕上移動到某個隨機位置。游戲開始時調用一次MoveMole過程,當用戶成功地點擊到地鼠后,每秒鐘執行一次該過程。
### **創建MoveMole過程**
要理解地鼠如何移動,需要了解Android的圖形定位機制。Canvas(以及Screen)可以看作是由x(水平)坐標和y(垂直)坐標織成的網格,其左上角的(x,y)坐標為(0,0)。 x坐標向右為增大, y坐標向下為增大,如圖3-4所示。一個ImageSprite的x、y屬性表示它左上角的位置,因此當地鼠位于屏幕左上角時,他的x和y值都是0。

**圖 3-4 屏幕上Mole的位置——坐標、高度和寬度信息,x坐標及寬度以藍色表示,y坐標和高度以橙色表示**
為了將地鼠的移動限制在屏幕之內,要確定x和y的最大值,這要用到地鼠Mole和畫布Canvas1的Width(寬度)及Height(高度)屬性。(地鼠的Width和Height屬性值與上傳的圖片的大小相同,而在創建Canvas1時,你設置的高度是300像素,寬度為“Fill parent”,即等于它的“父”容器——屏幕的寬度。)如果地鼠圖片的寬度是36像素,畫布寬度是200像素,那么Mole的x坐標最低可以為0(靠近屏幕左側邊緣),而最大為164(200 - 36,或Canvas1.Width - Mole.Width),這樣才能保證Mole不超出屏幕的右側邊緣。同樣,Mole頂部的y坐標范圍可從0到Canvas1.Height - Mole.Height。
圖3-5顯示了創建的MoveMole過程,圖中標有詳細注釋(可以有選擇地添加到過程中)。
為了隨機地放置Mole,x坐標要在0到Canvas1.Width - Mole.Width的范圍內選擇,同樣,y坐標要在0到Canvas1.Height - Mole.Height的范圍內。使用Math抽屜里的內置過程random integer生成一個隨機整數,將“from”參數從改默的1改為0,同樣修改“to”參數,如圖3-5所示。

**圖 3-5A MoveMole過程,用于將Mole放在一個隨機的位置上**
按如下步驟創建過程:
1\. 找到Procedures:單擊塊編輯器中的Procedures抽屜;
2\. 得到to procedure:在Procedures抽屜中點擊to procedure塊(不帶result的to procedure);
3\. 設置過程名稱:單擊塊中的文字“procedure”并輸入“MoveMole”;
4\. 移動Mole:單擊Mole抽屜,將call Mole.MoveTo塊拖到procedure塊中“do”的右側;注意:我們還需要提供x和y的坐標;
5\. 設定Mole的x坐標:如前所述,x坐標范圍在0與Canvas1.Width - Mole.Width之間:
* 點擊Math抽屜;
* 拖出random integer from塊,將左側插頭(突起)插入call Mole.MoveTo塊的“x”插槽;
* 點選from之后的數字1并輸入0;
* 丟棄數字100:點擊該塊,再按鍵盤上的Del或Delete鍵,或直接拖入垃圾箱;
* 點擊Math抽屜,將一個減法塊(-)拖入to插槽;
* 點擊Canvas1抽屜,向下滾動直到看見Canvas1.BackgroundColor ,將其拖入到減法塊“-”的左側,然后從BackgroundColor所在的下拉菜單中選擇Width選項;
* 同樣,點擊Mole抽屜并拖入Mole.Enabled塊,然后從Enabled塊所在的下拉菜單中選擇Width選項,并將它插入到“-”右側的插槽中;
6\. 按類似步驟設定y坐標,應該是一個從0到Canvas1.Height - Mole.Height的隨機整數;
7\. 對圖3-5A(行內輸入)或3-5B(外展輸入)檢查操作結果。
8\. random integer from to塊的“external inputs”(外展輸入)方式:右鍵點擊random塊,選擇列表第三項external inputs;如果想恢復行內輸入,右鍵點擊random塊,選擇inline inputs。

**圖 3-5B MoveMole過程,用于將Mole放在一個隨機的位置上**
### **在應用啟動時調用MoveMole過程**
已經完成了MoveMole過程,現在該調用它了。對于程序員來說,最熟悉的事情就是在應用啟動的同時執行某些指令,塊Screen1.Initialize就是專為這個目的而設計的:
1\. 點擊Screen1抽屜,并拖出Screen1.Initialize塊;
2\. 單擊Procedures抽屜,你會看到一個call MoveMole塊(這很有趣:你自己創建了一個新塊,不是嗎?!)。把它拖入Screen1.Initialize,如圖3-6所示。

**圖 3-6 在應用啟動時調用MoveMole過程**
### **每秒鐘調用一次MoveMole過程**
要讓地鼠每一秒移動一次,需要用到Clock組件。設置Clock1的TimerInterval屬性為其默認值1000(毫秒),即1秒,我們稱每秒一次的計時為計時器的心跳。這意味著,在Clock1.Timer塊中,無論設定什么動作,它都會隨著計時器的心跳,每秒鐘執行一次。以下是具體設置:
1\. 單擊Clock1抽屜,并拖出Clock1.Timer;
2\. 單擊Procedures抽屜,將call MoveMole塊拖到Clock1.Timer塊中,如圖3-7所示。

**圖 3-7 計時器開始計時后,每次心跳(每秒)都會調用一次MoveMole過程**
如果你覺得心跳得太快或太慢,可以在組件設計器中改變Clock1的TimerInterval屬性,來增加或減小地鼠的移動頻率。
### **記錄成績**
剛才我們創建了兩個Label:初始值為0的HitsCountsLabel和MissesCountsLabel,希望以此來記錄用戶的成績:當用戶命中Mole一次,或失敗一次(直接拍打到屏幕)時,對應Label中的數字增加,為此要用到Canvas1.Touched塊,它表示Canvas被觸摸到,并記錄了觸摸點的x和y坐標(我們不必關心),以及是否碰到了sprite(這是我們關心的)。圖3-8顯示了即將創建的代碼。

**圖 3-8 觸碰到Canvas1時,讓命中(HitsCountLabel)或失敗(MissesCountLabel)次數遞增**
圖3-8可以理解為:當觸碰到canvas時,檢查sprite是否也被碰到。應用中只有一個sprite,即Mole,如果碰到Mole,則HitsCountLabel.Text中的數字+1,否則,MissesCountLabel.Text中的數字+1(如果沒碰到sprite,則touchedSprite的值為false )。
下面介紹如何創建這些塊:
1\. 點擊Canvas1抽屜,并拖出Canvas1.Touched;
2\. 單擊Control抽屜,拖出Ifelse塊(先拖入if塊,然后為其添加else塊:點擊if左邊的藍色方塊,在彈出框中將else塊拖入if塊),并放入Canvas1.Touched塊中;
3\. 從Variables抽屜中拖出get塊,放入ifelse的if插槽內,選擇下拉菜單中的touchedSprite選項;或者將鼠標懸停在when Canvas.Touched塊的參數touchedSprite上,從中獲取get touchedSprite塊;
4\. 按照我們的設想,如果if檢測成功(即Mole被觸摸到),則HitsCountLabel.Text遞增:
* 從HitsCountLabel抽屜里拖出set HitsCountLabel.Text to塊并放入“then”的右邊;
* 點擊Math抽屜,拖出一個加號(+),將其放在“to”插槽中;
* 點擊HitsCountLabel抽屜,拖動HitsCountLabel.Text塊到“+”的左邊;
* 點擊Math抽屜,并拖動一個“0”塊到“+”的右邊,將0改為1 ;
5\. 在ifelse塊的“else”部分,對MissesCountLabel塊重復步驟4。
>  測試:測試你的新代碼:在設備上觸摸Canvas,命中或錯過地鼠,看看分數有什么變化。
### **過程抽象**
計算機科學的重要手段之一,就是命名然后調用一組指令(如MoveMole),這種能力被稱為過程抽象。之所以叫做“抽象”,是因為過程的調用者(在實際項目中,很有可能不是過程的開發者)只需要知道過程的功能(如移動地鼠),而不需要知道過程的實現方法(生成兩個隨機整數)。如果沒有過程抽象,不可能實現那些大型程序,因為它們的代碼量太大,對個人來說是力所不及的,這一點與現實世界中的勞動分工相類似。例如,不同的工程師設計出汽車的不同部件,沒有人了解所有的細節,而司機只需要了解接口(例如,踩下制動踏板把車停下來),而無需了解如何實現這些接口。
與復制和粘貼代碼相比,過程抽象的優勢在于:
* 由于過程的代碼獨立于其它部分的程序,因此更易于對過程的測試;
* 如果代碼中有錯誤,只需要對局部進行修改;
* 如果需要改變過程的實現 (或功能),如確保地鼠不連續出現在同一個位置,只需要修改一處的代碼;
* 可以將過程匯集到一個程序庫中,以便在不同的程序中使用。(遺憾的是App Inventor暫時不支持這項功能。)
* 將大塊代碼拆分成代碼片段,有助于對應用做深入剖析,并加以實現(“分而治之”)。
* 給過程一個有意義的命名,將有助于提高代碼的可讀性,更易被別人(或一個月后的自己)讀懂;
在后面的章節中,還將學到過程更加強大的功能:添加參數,提供返回值,以及調用過程本身。有關內容請參見第21章。
### **重置分數**
朋友看到你玩MoleMash,他可能也想試試身手,所以最好能讓成績歸零。根據前面學過的內容,不經提示你也有能力把它做出來。閱讀之前動腦筋試試看。
我們要在ResetButton.Click塊中設置HitsCountLabel.Text和MissesCountLabel.Text的值為0。如圖3-9所示。

**圖 3-9 按下Reset按鈕讓命中次數(HitsCountLabel)和失敗次數(MissesCountLabel)歸零**
此處提供一個技巧,來快速建立ResetButton.Click的事件處理程序:在工作區直接輸入0并回車,將生成數字塊0,等同于從Math抽屜中拖出。(這種輸入方式對其他塊也同樣有效。)
>  測試:開始游戲,嘗試多次命中及錯過地鼠,然后按下“重新啟動”按鈕。
### **添加觸摸地鼠行為**
我們希望在觸摸到地鼠時,設備能夠振動,這要用到Sound1.Vibrate塊。如圖3-10所示。

**圖 3-10 碰到地鼠時讓設備短暫振動(100毫秒)**
>  測試:當你在設備上實際觸摸到地鼠時,看看振動的效果如何。如果你覺得振動時間過長或過短,可以修改Sound1.Vibrate塊的毫秒數。
## **完整的MoleMash應用**
圖3-11中描述了完整的MoleMash應用中所有的塊。

**圖 3-11 完整的MoleMash應用**
## **改進**
對MoleMash應用還可以做如下補充:
* 添加按鈕,讓用戶可以控制地鼠的移動速度;
* 添加一個Label,隨時顯示地鼠出現(或移動)的次數;
* 添加另一個ImageSprite,如一朵花的圖片:用戶不許碰到它,如果碰到將會受到懲罰,減少得分或結束游戲;
* 用ContactPicker組件從用戶手機的電話簿中選擇圖片,來替代地鼠圖片。
## **小結**
本章介紹了一些非常有用的技巧,適用于普通應用,更適用于游戲:
* Canvas組件使用了直角坐標系,其中x表示水平方向(從左邊的0到右邊的Canvas.Width -1),y表示垂直方向(從頂部的0到底部的Canvas.Height -1)。從Canvas的高度和寬度中減去某個ImageSprite的高度和寬度,這個范圍可以確保sprite在畫布上完整地顯示;
* 利用Canvas和ImageSprite組件的Touched方法 來實現對設備觸感的應用;
* 創建實時交互應用:不僅可以對用戶的操作做出實時響應,也可以對設備內部的計時器做出響應。具體地說,Clock.Interval屬性用于設定計時器的心跳頻率,這個頻率也可以用于控制ImageSprite (或其它)組件的移動;
* Label可用于顯示得分,根據玩家的操作結果,得分會相應升高(或下降);
* 通過Sound.Vibrate方法對用戶的觸摸事件進行反饋,讓設備震動一定的毫秒數;
* 不僅可以調用內置過程,也可以通過給一組塊設定名稱(MoveMole),來創建自己的過程,這些過程也可以像內置過程一樣被調用,這就是所謂的過程抽象,在計算機科學中這是一個非常重要的思想,可以實現代碼的復用 ,也使得創建復雜應用成為可能;
* 利用Math抽屜中的random integer(隨機整數)塊,可以產生不可預知行為,讓游戲每次開始時都有所不同。
在第5章(瓢蟲快跑)中,將了解更多的游戲制作技巧,包括移動中的ImageSprite組件之間的碰撞檢測。
### **術語解釋**
procedure:譯為過程、步驟、程序,在早期面向過程的編程語言中,也被稱作子程序。它是一段由單行或多行語句組成的代碼片段,被包裝成一個有名稱的單元,相對獨立,用于完成某種特定功能。當主程序中需要實現該功能時,就會引用它的名稱來實現對它的調用。本書中譯為“過程”。
false:中文譯為“假”,與true(真)相反,是布爾(Boolean)類型變量的值。
Boolean:譯為“布爾數學體系的”,是一個形容詞,是為了紀念數學家布爾的偉大貢獻。與這個詞相關的短語還有boolean algebra(布爾代數)、boolean operation(布爾運算)、boolean value(布爾值)等等。其中計算機軟件中使用Boolean表示一種變量類型:布爾型變量,這種變量只有兩個值:true(真)與false(假)。
### **背景知識**
George Boole:中文譯為“喬治·布爾”,是19世紀一位自學成才的愛爾蘭數學家,他創立了布爾代數(boolean algebra)。布爾代數用于解決集合運算及邏輯運算問題,是當代計算機硬件設計的理論基礎。在軟件領域中,邏輯運算(and,or,not)是程序編寫過程中的關鍵環節,在App Inventor中具體體現為“if”塊,它根據邏輯運算的結果(true、false)來決定下一步程序的走向。此外,在“while”塊中也用到了邏輯運算。
TI 99/4:由美國德州儀器生產的一款早期的家用電腦,發布于1979年11月,價值$1,150,CPU為TI TMS9900,主頻3MHz,16K內存,26K只讀存儲器,192x256、16色、13英寸監視器,內置TI BASIC語言。(引自[http://oldcomputers.net](http://oldcomputers.net/),順便說一句,這是一個很有趣的網站!)
### **中英文對照**
button:按鈕
clock:時鐘
control:控制
count:計數
false:假
fill:充滿
hit:擊打 命中
if:如果
integer:整數
interface:界面
interval:間隔
label:標簽
mash:搗碎
media:媒體
miss:錯過
mole:鼴鼠
parent:雙親
procedure:過程
random:隨機
sound:聲音
sprite:精靈
text:文字
true:真
user:用戶
vibrate:震動
### **資源下載**
[mole.png](http://www.17coding.net/images/right/3/mole.png)
- 簡介
- 序言
- 前言
- 第 1 章 Hello 貓咪
- 第 2 章 油漆桶
- 第 3 章 打地鼠
- 第 4 章 開車不發短信
- 第 5 章 瓢蟲快跑
- 第 6 章 巴黎地圖旅游
- 第 7 章 安卓,我的車在哪?
- 第 8 章 總統測驗
- 第 9 章 木琴
- 第 10 章 出題及答題
- 第 11 章 廣播中心
- 第 12 章 遙控機器人
- 第 13 章 亞馬遜掌上書店
- 第 14 章 理解應用的結構
- 第 15 章 軟件工程與應用調試
- 第 16 章 應用中的存儲
- 第 17 章 創建動畫應用
- 第 18 章 程序中的決策:條件塊
- 第 19 章 數據列表編程
- 第 20 章 循環
- 第 21 章 定義過程
- 第 22 章 數據庫
- 第 23 章 傳感器
- 第 24 章 與Web API通信