第 3 章 打地鼠

2018-02-24 15:51 更新

作者介紹

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>

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

如圖3-1所示的“打地鼠”應(yīng)用將實現(xiàn)以下功能:

  • 一只地鼠隨機出現(xiàn)在屏幕上,每秒鐘移動一次;

  • 如果手指觸碰到地鼠,則讓設(shè)備震動,顯示的命中數(shù)加1,地鼠隨機移動到一個新位置;

  • 如果手指直接觸摸到屏幕但沒點擊中地鼠,則顯示失敗數(shù)加1;

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

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

本章內(nèi)容覆蓋了以下的組件及概念:

  • ImageSprite組件:具有觸感的可移動圖像;

  • Canvas組件:容納ImageSprite的平臺;

  • Clock組件:用來計時,讓sprite隨即移動;

  • Sound組件:擊中地鼠時產(chǎn)生震動;

  • Button組件:開始新游戲;

  • Procedures:用來實現(xiàn)一系列的指令,可以重復(fù)調(diào)用,如移動地鼠;

  • 產(chǎn)生隨機數(shù);

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

準(zhǔn)備開始

登陸App Inventor網(wǎng)站,開始新項目“MoleMash ”,將屏幕標(biāo)題(title)設(shè)為“打地鼠”,并連接到測試設(shè)備。

下載地鼠圖片mole.png。下載方法:控制鍵+單擊(Mac)或單擊右鍵(Windows)并選擇“圖片另存為”或類似選項。下載成功后,在設(shè)計器組件列表下方的Media部分,單擊“Upload file…”,找到剛下載的文件mole.png并上傳到App Inventor中。

設(shè)計組件

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

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

  • ImageSprite組件:用來顯示地鼠圖片,隨機移動,并具有觸感;

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

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

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

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

  • Clock組件:使地鼠每秒鐘隨機移動一次。

表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

設(shè)置活動組件

本節(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è)計器視圖中的所有“活動”組件

布置Label組件

現(xiàn)在設(shè)置顯示用戶得分的組件,即,顯示命中與失敗次數(shù)的組件。

1. 找到Palette->Layout->HorizontalArrangement,拖動組件放在“重新啟動”按鈕的下方,保留HorizontalArrangement1的默認(rèn)名稱;

2. 從Palette->User Interface中拖動兩個Label組件到HorizontalArrangement1中;

  • 將左側(cè)Label改名為HitsLabel,設(shè)置其Text屬性為“命中: ”(確保冒號后有一個空格);

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

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

4. 將兩個Label拖放在HorizontalArrangement2中;

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

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

你的屏幕看起來如圖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í)行一次該過程。

創(chuàng)建MoveMole過程

要理解地鼠如何移動,需要了解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選項;

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

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放在一個隨機的位置上

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

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

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

  • 點擊Math抽屜,并拖動一個“0”塊到“+”的右邊,將0改為1 ;

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章。

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

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號