作者介紹
Ellen Spertus
本書的共同作者之一,美國加州奧克蘭市米爾斯大學(xué)的計算機科學(xué)教授,同時也是谷歌公司的資深科學(xué)家。她先后在MIT獲得了計算機科學(xué)與工程學(xué)士學(xué)位、電子工程與計算機科學(xué)碩士及博士學(xué)位,并利用暑假的空閑時間為微軟公司工作。她曾撰文探討技術(shù)及社會問題,而且經(jīng)常將兩者相結(jié)合。1993年紐約時報曾以《改變計算機領(lǐng)域面貌的女性》為題介紹Spertus,并在后續(xù)的文章中稱其為“最性感的活著的極客”。2009年Spertus加入谷歌的App Inventor for Android團隊,并參與撰寫了本書的部分章節(jié)。
本章將創(chuàng)建一個“打地鼠”的游戲,游戲靈感來自一款經(jīng)典的街機游戲Whac-A-Mole,其中的小動物會突然從洞中冒出,玩家則用木槌擊打它們,擊中得分?!按虻厥蟆钡膭?chuàng)作者是一名App Inventor團隊的成員,與其說她是為了測試sprite組件的功能(她做到了),不如說是她自己喜歡玩游戲。
圖 3-1 打地鼠游戲的用戶界面
當(dāng)Ellen Spertus加入Google公司的App Inventor團隊時,她希望App Inventor也可以用于游戲的開發(fā),因此她自告奮勇地承擔(dān)起sprites的實現(xiàn)任務(wù)。sprite原本用來表示神話中的角色,如仙女、妖精等,到20世紀(jì)70年代開始出現(xiàn)在計算機界,用來代表那些能夠在電腦屏幕上移動的圖像(在電子游戲中)。Ellen第一次使用sprite是在20世紀(jì)80年代早期,她曾經(jīng)參加電腦訓(xùn)練營并使用TI 99/4 編程。她在sprites以及“打地鼠”游戲上所做的努力,受到了雙重懷舊情緒的驅(qū)使——計算機以及游戲——她童年時代的最愛。
可以查看Android版“打地鼠”游戲的視頻教程?!敬私坛逃蒞olber教授基于上一個版本的App Inventor錄制的,但同樣可以有助于理解開發(fā)過程?!?/p>
如圖3-1所示的“打地鼠”應(yīng)用將實現(xiàn)以下功能:
一只地鼠隨機出現(xiàn)在屏幕上,每秒鐘移動一次;
如果手指觸碰到地鼠,則讓設(shè)備震動,顯示的命中數(shù)加1,地鼠隨機移動到一個新位置;
如果手指直接觸摸到屏幕但沒點擊中地鼠,則顯示失敗數(shù)加1;
本章內(nèi)容覆蓋了以下的組件及概念:
ImageSprite組件:具有觸感的可移動圖像;
Canvas組件:容納ImageSprite的平臺;
Clock組件:用來計時,讓sprite隨即移動;
Sound組件:擊中地鼠時產(chǎn)生震動;
Button組件:開始新游戲;
Procedures:用來實現(xiàn)一系列的指令,可以重復(fù)調(diào)用,如移動地鼠;
產(chǎn)生隨機數(shù);
登陸App Inventor網(wǎng)站,開始新項目“MoleMash ”,將屏幕標(biāo)題(title)設(shè)為“打地鼠”,并連接到測試設(shè)備。
下載地鼠圖片mole.png。下載方法:控制鍵+單擊(Mac)或單擊右鍵(Windows)并選擇“圖片另存為”或類似選項。下載成功后,在設(shè)計器組件列表下方的Media部分,單擊“Upload file…”,找到剛下載的文件mole.png并上傳到App Inventor中。
創(chuàng)建“打地鼠”游戲需要以下組件:
Canvas組件:用來限定游戲中地鼠的活動區(qū)域;
ImageSprite組件:用來顯示地鼠圖片,隨機移動,并具有觸感;
Sound組件:當(dāng)?shù)厥蟊挥|摸到時,發(fā)出震動;
Label組件:用來顯示“命中: ”、“失敗: ”以及命中、失敗的次數(shù);
HorizontalArrangements組件:用來放置Label組件,使組件的布局合理;
Button組件:用來將命中及失敗次數(shù)歸零(重新開始游戲);
表3-1顯示了應(yīng)用中用到的全部組件。
表3-1 “打地鼠”應(yīng)用中的全部組件列表
組件類型 | 組件種類 | 命名 | 作用 |
---|---|---|---|
Canvas | Drawing and Animation | Canvas1 | ImageSprite的容 |
ImageSprite | Drawing and Animation | Mole | 用戶點擊的目標(biāo) |
Button | User Interface | ResetButton | 重新設(shè)置得分 |
Clock | User Interface | Clock1 | 控制地鼠的移動頻率 |
Sound | Media | Sound1 | 當(dāng)?shù)厥蟊粨糁袝r震動 |
Label | User Interface | HitsLabel | 顯示文字“擊中: ” |
Label | User Interface | HitsCountLabel | 顯示擊中次數(shù) |
HorizontalArrangement | Layout | HorizontalArrangement1 | 放置HitsLabel及HitsCountLabel |
Label | User Interface | MissesLabel | 顯示文字“失?。?” |
Label | User Interface | MissesCountLabel | 顯示失敗次數(shù) |
HorizontalArrangement | Layout | HorizontalArrangement2 | 放置MissesLabel及MissesCountLabel |
本節(jié)將設(shè)置游戲中所需的活動組件,下節(jié)再來設(shè)置顯示分?jǐn)?shù)的組件。
1. 找到Palette->Drawing and Animation->Canvas組件,拖入預(yù)覽窗口,采用其默認(rèn)名稱Canvas1,設(shè)置Width屬性為“Fill parent”,即與屏幕等寬,設(shè)置Height屬性為300像素;
2. 找到Palette->Drawing and Animation->ImageSprite,將ImageSprite組件拖入到Canvas1中的任何位置,在組件列表底部單擊rename,改名為“Mole”,設(shè)置其Picture屬性為之前上傳的mole.png;
3. 找到Palette->User Interface->Button,拖動Button組件放在Canvas1下面,改名為“ResetButton”,并設(shè)置其Text屬性為“重新開始”;
4. 找到Palette->User Interface->Clock,拖入Clock組件,它將落在預(yù)覽窗口下方的“非可是組件”區(qū)域;
5. 找到Palette->Media->Sound,拖入Sound組件,它也將落在“非可視組件”區(qū)域。
現(xiàn)在組件設(shè)計器看起來應(yīng)該如圖3-2(地鼠的位置有可能不同)。
圖 3-2 組件設(shè)計器視圖中的所有“活動”組件
現(xiàn)在設(shè)置顯示用戶得分的組件,即,顯示命中與失敗次數(shù)的組件。
1. 找到Palette->Layout->HorizontalArrangement,拖動組件放在“重新啟動”按鈕的下方,保留HorizontalArrangement1的默認(rèn)名稱;
2. 從Palette->User Interface中拖動兩個Label組件到HorizontalArrangement1中;
將左側(cè)Label改名為HitsLabel,設(shè)置其Text屬性為“命中: ”(確保冒號后有一個空格);
3. 拖入第二個HorizontalArrangement,將其放在HorizontalArrangement1下面;
4. 將兩個Label拖放在HorizontalArrangement2中;
左側(cè)Label改名為MissesLabel,設(shè)置其Text屬性為“失?。?”(確保冒號后有一個空格);
你的屏幕看起來如圖3-3。
圖 3-3 組件設(shè)計器視圖中“打地鼠”應(yīng)用的所有組件
組件已經(jīng)創(chuàng)建完成,下面切換到塊編輯器來實現(xiàn)程序的行為。設(shè)置的目標(biāo):①讓地鼠每秒鐘在Canvas1上隨機移動一次;②用戶拍打這只隨機移動的地鼠,應(yīng)用顯示用戶命中或失敗的次數(shù)(注:建議用手指而不是木槌拍打?。?;按下“重新啟動”按鈕命中及失敗次數(shù)歸零。
在迄今為止完成的應(yīng)用中,曾經(jīng)調(diào)用過內(nèi)置過程 ,如HelloPurr中的Sound1.Vibrate(震動)。假如App Inventor中有一個內(nèi)置過程,可以將ImageSprite移動到屏幕上的某個隨機位置,那豈不是很好?可惜沒有,不過我們可以自己來創(chuàng)建過程!就像內(nèi)置過程一樣,自己創(chuàng)建的過程會顯示在Procedures抽屜中,需要時可以隨時調(diào)用它。
具體來說,創(chuàng)建一個名為MoveMole的過程,讓地鼠在屏幕上移動到某個隨機位置。游戲開始時調(diào)用一次MoveMole過程,當(dāng)用戶成功地點擊到地鼠后,每秒鐘執(zhí)行一次該過程。
要理解地鼠如何移動,需要了解Android的圖形定位機制。Canvas(以及Screen)可以看作是由x(水平)坐標(biāo)和y(垂直)坐標(biāo)織成的網(wǎng)格,其左上角的(x,y)坐標(biāo)為(0,0)。 x坐標(biāo)向右為增大, y坐標(biāo)向下為增大,如圖3-4所示。一個ImageSprite的x、y屬性表示它左上角的位置,因此當(dāng)?shù)厥笪挥谄聊蛔笊辖菚r,他的x和y值都是0。
圖 3-4 屏幕上Mole的位置——坐標(biāo)、高度和寬度信息,x坐標(biāo)及寬度以藍(lán)色表示,y坐標(biāo)和高度以橙色表示
為了將地鼠的移動限制在屏幕之內(nèi),要確定x和y的最大值,這要用到地鼠Mole和畫布Canvas1的Width(寬度)及Height(高度)屬性。(地鼠的Width和Height屬性值與上傳的圖片的大小相同,而在創(chuàng)建Canvas1時,你設(shè)置的高度是300像素,寬度為“Fill parent”,即等于它的“父”容器——屏幕的寬度。)如果地鼠圖片的寬度是36像素,畫布寬度是200像素,那么Mole的x坐標(biāo)最低可以為0(靠近屏幕左側(cè)邊緣),而最大為164(200 - 36,或Canvas1.Width - Mole.Width),這樣才能保證Mole不超出屏幕的右側(cè)邊緣。同樣,Mole頂部的y坐標(biāo)范圍可從0到Canvas1.Height - Mole.Height。
圖3-5顯示了創(chuàng)建的MoveMole過程,圖中標(biāo)有詳細(xì)注釋(可以有選擇地添加到過程中)。
為了隨機地放置Mole,x坐標(biāo)要在0到Canvas1.Width - Mole.Width的范圍內(nèi)選擇,同樣,y坐標(biāo)要在0到Canvas1.Height - Mole.Height的范圍內(nèi)。使用Math抽屜里的內(nèi)置過程random integer生成一個隨機整數(shù),將“from”參數(shù)從改默的1改為0,同樣修改“to”參數(shù),如圖3-5所示。
圖 3-5A MoveMole過程,用于將Mole放在一個隨機的位置上
按如下步驟創(chuàng)建過程:
1. 找到Procedures:單擊塊編輯器中的Procedures抽屜;
2. 得到to procedure:在Procedures抽屜中點擊to procedure塊(不帶result的to procedure);
3. 設(shè)置過程名稱:單擊塊中的文字“procedure”并輸入“MoveMole”;
4. 移動Mole:單擊Mole抽屜,將call Mole.MoveTo塊拖到procedure塊中“do”的右側(cè);注意:我們還需要提供x和y的坐標(biāo);
5. 設(shè)定Mole的x坐標(biāo):如前所述,x坐標(biāo)范圍在0與Canvas1.Width - Mole.Width之間:
點擊Math抽屜;
拖出random integer from塊,將左側(cè)插頭(突起)插入call Mole.MoveTo塊的“x”插槽;
點選from之后的數(shù)字1并輸入0;
丟棄數(shù)字100:點擊該塊,再按鍵盤上的Del或Delete鍵,或直接拖入垃圾箱;
點擊Math抽屜,將一個減法塊(-)拖入to插槽;
點擊Canvas1抽屜,向下滾動直到看見Canvas1.BackgroundColor ,將其拖入到減法塊“-”的左側(cè),然后從BackgroundColor所在的下拉菜單中選擇Width選項;
6. 按類似步驟設(shè)定y坐標(biāo),應(yīng)該是一個從0到Canvas1.Height - Mole.Height的隨機整數(shù);
7. 對圖3-5A(行內(nèi)輸入)或3-5B(外展輸入)檢查操作結(jié)果。
8. random integer from to塊的“external inputs”(外展輸入)方式:右鍵點擊random塊,選擇列表第三項external inputs;如果想恢復(fù)行內(nèi)輸入,右鍵點擊random塊,選擇inline inputs。
圖 3-5B MoveMole過程,用于將Mole放在一個隨機的位置上
已經(jīng)完成了MoveMole過程,現(xiàn)在該調(diào)用它了。對于程序員來說,最熟悉的事情就是在應(yīng)用啟動的同時執(zhí)行某些指令,塊Screen1.Initialize就是專為這個目的而設(shè)計的:
1. 點擊Screen1抽屜,并拖出Screen1.Initialize塊;
2. 單擊Procedures抽屜,你會看到一個call MoveMole塊(這很有趣:你自己創(chuàng)建了一個新塊,不是嗎?!)。把它拖入Screen1.Initialize,如圖3-6所示。
圖 3-6 在應(yīng)用啟動時調(diào)用MoveMole過程
要讓地鼠每一秒移動一次,需要用到Clock組件。設(shè)置Clock1的TimerInterval屬性為其默認(rèn)值1000(毫秒),即1秒,我們稱每秒一次的計時為計時器的心跳。這意味著,在Clock1.Timer塊中,無論設(shè)定什么動作,它都會隨著計時器的心跳,每秒鐘執(zhí)行一次。以下是具體設(shè)置:
1. 單擊Clock1抽屜,并拖出Clock1.Timer;
2. 單擊Procedures抽屜,將call MoveMole塊拖到Clock1.Timer塊中,如圖3-7所示。
圖 3-7 計時器開始計時后,每次心跳(每秒)都會調(diào)用一次MoveMole過程
如果你覺得心跳得太快或太慢,可以在組件設(shè)計器中改變Clock1的TimerInterval屬性,來增加或減小地鼠的移動頻率。
剛才我們創(chuàng)建了兩個Label:初始值為0的HitsCountsLabel和MissesCountsLabel,希望以此來記錄用戶的成績:當(dāng)用戶命中Mole一次,或失敗一次(直接拍打到屏幕)時,對應(yīng)Label中的數(shù)字增加,為此要用到Canvas1.Touched塊,它表示Canvas被觸摸到,并記錄了觸摸點的x和y坐標(biāo)(我們不必關(guān)心),以及是否碰到了sprite(這是我們關(guān)心的)。圖3-8顯示了即將創(chuàng)建的代碼。
圖 3-8 觸碰到Canvas1時,讓命中(HitsCountLabel)或失?。∕issesCountLabel)次數(shù)遞增
圖3-8可以理解為:當(dāng)觸碰到canvas時,檢查sprite是否也被碰到。應(yīng)用中只有一個sprite,即Mole,如果碰到Mole,則HitsCountLabel.Text中的數(shù)字+1,否則,MissesCountLabel.Text中的數(shù)字+1(如果沒碰到sprite,則touchedSprite的值為false )。
下面介紹如何創(chuàng)建這些塊:
1. 點擊Canvas1抽屜,并拖出Canvas1.Touched;
2. 單擊Control抽屜,拖出Ifelse塊(先拖入if塊,然后為其添加else塊:點擊if左邊的藍(lán)色方塊,在彈出框中將else塊拖入if塊),并放入Canvas1.Touched塊中;
3. 從Variables抽屜中拖出get塊,放入ifelse的if插槽內(nèi),選擇下拉菜單中的touchedSprite選項;或者將鼠標(biāo)懸停在when Canvas.Touched塊的參數(shù)touchedSprite上,從中獲取get touchedSprite塊;
4. 按照我們的設(shè)想,如果if檢測成功(即Mole被觸摸到),則HitsCountLabel.Text遞增:
從HitsCountLabel抽屜里拖出set HitsCountLabel.Text to塊并放入“then”的右邊;
點擊Math抽屜,拖出一個加號(+),將其放在“to”插槽中;
點擊HitsCountLabel抽屜,拖動HitsCountLabel.Text塊到“+”的左邊;
5. 在ifelse塊的“else”部分,對MissesCountLabel塊重復(fù)步驟4。
測試:測試你的新代碼:在設(shè)備上觸摸Canvas,命中或錯過地鼠,看看分?jǐn)?shù)有什么變化。
計算機科學(xué)的重要手段之一,就是命名然后調(diào)用一組指令(如MoveMole),這種能力被稱為過程抽象。之所以叫做“抽象”,是因為過程的調(diào)用者(在實際項目中,很有可能不是過程的開發(fā)者)只需要知道過程的功能(如移動地鼠),而不需要知道過程的實現(xiàn)方法(生成兩個隨機整數(shù))。如果沒有過程抽象,不可能實現(xiàn)那些大型程序,因為它們的代碼量太大,對個人來說是力所不及的,這一點與現(xiàn)實世界中的勞動分工相類似。例如,不同的工程師設(shè)計出汽車的不同部件,沒有人了解所有的細(xì)節(jié),而司機只需要了解接口(例如,踩下制動踏板把車停下來),而無需了解如何實現(xiàn)這些接口。
與復(fù)制和粘貼代碼相比,過程抽象的優(yōu)勢在于:
由于過程的代碼獨立于其它部分的程序,因此更易于對過程的測試;
如果代碼中有錯誤,只需要對局部進行修改;
如果需要改變過程的實現(xiàn) (或功能),如確保地鼠不連續(xù)出現(xiàn)在同一個位置,只需要修改一處的代碼;
可以將過程匯集到一個程序庫中,以便在不同的程序中使用。(遺憾的是App Inventor暫時不支持這項功能。)
將大塊代碼拆分成代碼片段,有助于對應(yīng)用做深入剖析,并加以實現(xiàn)(“分而治之”)。
在后面的章節(jié)中,還將學(xué)到過程更加強大的功能:添加參數(shù),提供返回值,以及調(diào)用過程本身。有關(guān)內(nèi)容請參見第21章。
朋友看到你玩MoleMash,他可能也想試試身手,所以最好能讓成績歸零。根據(jù)前面學(xué)過的內(nèi)容,不經(jīng)提示你也有能力把它做出來。閱讀之前動腦筋試試看。
我們要在ResetButton.Click塊中設(shè)置HitsCountLabel.Text和MissesCountLabel.Text的值為0。如圖3-9所示。
圖 3-9 按下Reset按鈕讓命中次數(shù)(HitsCountLabel)和失敗次數(shù)(MissesCountLabel)歸零
此處提供一個技巧,來快速建立ResetButton.Click的事件處理程序:在工作區(qū)直接輸入0并回車,將生成數(shù)字塊0,等同于從Math抽屜中拖出。(這種輸入方式對其他塊也同樣有效。)
測試:開始游戲,嘗試多次命中及錯過地鼠,然后按下“重新啟動”按鈕。
我們希望在觸摸到地鼠時,設(shè)備能夠振動,這要用到Sound1.Vibrate塊。如圖3-10所示。
圖 3-10 碰到地鼠時讓設(shè)備短暫振動(100毫秒)
更多建議: