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