第 3 章 打地鼠

2018-02-24 15:51 更新

作者介紹

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ā)過程。】

學(xué)習(xí)目標(biāo)

如圖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;

  • 點(diǎn)擊“重新開始”按鈕,游戲重新開始,命中和失敗的計(jì)數(shù)歸零。

學(xué)習(xí)內(nèi)容

本章內(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ù);

  • 使用加法塊(+)及減法塊(-)。

準(zhǔn)備開始

登陸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中。

設(shè)計(jì)組件

創(chuàng)建“打地鼠”游戲需要以下組件:

  • Canvas組件:用來限定游戲中地鼠的活動(dòng)區(qū)域;

  • ImageSprite組件:用來顯示地鼠圖片,隨機(jī)移動(dòng),并具有觸感;

  • Sound組件:當(dāng)?shù)厥蟊挥|摸到時(shí),發(fā)出震動(dòng);

  • Label組件:用來顯示“命中: ”、“失?。?”以及命中、失敗的次數(shù);

  • HorizontalArrangements組件:用來放置Label組件,使組件的布局合理;

  • Button組件:用來將命中及失敗次數(shù)歸零(重新開始游戲);

  • Clock組件:使地鼠每秒鐘隨機(jī)移動(dòng)一次。

表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

設(shè)置活動(dòng)組件

本節(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)”組件

布置Label組件

現(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è)空格);

  • 將右側(cè)Label改名為HitsCountLabel,設(shè)置其Text屬性為“0”;

3. 拖入第二個(gè)HorizontalArrangement,將其放在HorizontalArrangement1下面;

4. 將兩個(gè)Label拖放在HorizontalArrangement2中;

  • 左側(cè)Label改名為MissesLabel,設(shè)置其Text屬性為“失敗: ”(確保冒號(hào)后有一個(gè)空格);

  • 右側(cè)Label改名為MissesCountLabel,設(shè)置其Text屬性為“0”。

你的屏幕看起來如圖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ù)歸零。

移動(dòng)地鼠

在迄今為止完成的應(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í)行一次該過程。

創(chuàng)建MoveMole過程

要理解地鼠如何移動(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);

  • 同樣,點(diǎn)擊Mole抽屜并拖入Mole.Enabled塊,然后從Enabled塊所在的下拉菜單中選擇Width選項(xiàng),并將它插入到“-”右側(cè)的插槽中;

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ī)的位置上

在應(yīng)用啟動(dòng)時(shí)調(diào)用MoveMole過程

已經(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過程

每秒鐘調(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塊到“+”的左邊;

  • 點(diǎn)擊Math抽屜,并拖動(dòng)一個(gè)“0”塊到“+”的右邊,將0改為1 ;

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)(“分而治之”)。

  • 給過程一個(gè)有意義的命名,將有助于提高代碼的可讀性,更易被別人(或一個(gè)月后的自己)讀懂;

在后面的章節(jié)中,還將學(xué)到過程更加強(qiáng)大的功能:添加參數(shù),提供返回值,以及調(diào)用過程本身。有關(guān)內(nèi)容請(qǐng)參見第21章。

重置分?jǐn)?shù)

朋友看到你玩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毫秒)

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)