本章將討論創(chuàng)建另一類應(yīng)用的方法,應(yīng)用中使用了簡單的可移動的動畫對象。你將學(xué)習(xí)使用App Inventor創(chuàng)建二維游戲的基本知識,并熟練使用圖片精靈(image sprite)及處理兩個物體碰撞一類的事件。
當(dāng)你在電腦屏幕上看到一個平滑移動的物體時,你實際上看到的是一連串快速移動的圖片,每次只移動一個極小的距離,它利用了人的視覺暫留,從這一點上,它無異于“手翻書”—— 一種通過快速翻頁來看到動畫效果的書(這也是那些精美絕倫的動畫電影的制作方法)。
在App Inventor中,通過在Canvas組件上放置物體,并讓這些物體隨時間在Canvas內(nèi)移動,從而產(chǎn)生出動畫效果。本章將學(xué)習(xí)使用Canvas的坐標(biāo)系統(tǒng),學(xué)習(xí)利用Clock.Timer事件來觸發(fā)運(yùn)動,以及如何控制運(yùn)動速度、如何響應(yīng)兩個物體的碰撞事件等等。
從組件面板的Drawing and Animation組中拖出Canvas組件,然后定義它的Width及Height屬性。通常我們希望Canvas與屏幕等寬,為此將寬度設(shè)為“Fill parent”,如圖17-1所示。
圖 17-1 設(shè)置Canvas組件的Width屬性
可以用同樣的方式設(shè)定Height屬性,但一般會將其設(shè)為一個數(shù)字(如300像素),以便為Canvas上面或下面的其他組件留出空間。
Canvas上的圖畫實際上是一個許多像素構(gòu)成的表格,像素是手機(jī)(或其他設(shè)備)屏幕上能夠顯示的最小的色塊,每個像素都在Canvas上有它的位置(或者說單元格),位置由X-Y坐標(biāo)系定義,如圖17-2所示,X定義了水平方向上的位置(方向是從左到右),Y定義了垂直方向的位置(從上到下)。
圖 17-2 Canvas的坐標(biāo)系統(tǒng)
坐標(biāo)軸的方向定義可能與你的經(jīng)驗不一致,不過位于Canvas左上角的單元格的x、y坐標(biāo)都為零,因此這個位置表示為(x=0,y=0)。(這與App Inventor列表中使用的索引值有所不同,索引值從1開始,看起來更容易理解。)向右移動時,x坐標(biāo)增大;向下移動時,y值變大。位于左上角單元格右側(cè)的單元格坐標(biāo)為(x=1,y=0)。右上角單元格的x坐標(biāo)等于canvas的寬度減1,多數(shù)手機(jī)屏幕的寬度都在300左右,但這里例子中顯示的寬度是20,因此右上角的單元格坐標(biāo)為(x=19,y=0)。
要改變canvas的外觀有兩種方法:①在上面繪畫,或者②在上面放置移動的物體,本章所涉及的是后者,但我們首先要討論如何繪畫,以及如何通過繪畫來創(chuàng)建動畫(這也是本書第二章油漆桶中的主要內(nèi)容)。
Canvas中的每一個單元格都對應(yīng)顯示為一個有顏色的像素。Canvas組件提供的Canvas.DrawLine及Canvas.Circle塊可以用來在canvas上以繪制像素組成的圖畫。首先需要將Canvas.PaintColor屬性設(shè)置為你需要的顏色,然后調(diào)用某個具體的繪畫塊來畫出顏色。其中的DrawCircle塊可以繪制直徑為任意大小的圓,但如果你將半徑設(shè)為1,如圖17-3所示,那么只能畫出一個單獨的像素。
圖 17-3 用1個像素畫圓,每次只能畫1個單獨的像素
在塊編輯器Built-in組的Colors抽屜中,App Inventor提供了13種常用的顏色,可以用來繪制像素圖(或設(shè)置組件背景色)。也可以使用顏色編碼方案來獲得更為豐富的顏色,顏色編碼方案的解釋請參見相關(guān)App Inventor文檔:http://appinventor.googlelabs.com/learn/reference/blocks/colors.html。
改變canvas外觀的第二種方法是在canvas上放置Ball和ImageSprite組件。sprite是一個被放置在場景中的圖形對象,所謂的場景這里指的就是canvas。Ball和ImageSprite組件都屬于sprites類型,只是外觀不同而已。Ball為圓形,只能通過改變顏色和半徑來改變它的外觀,而ImageSprite可以是任何形狀;ImageSprite和Ball都只能添加到Canvas中,不可能將它們拖入用戶界面中Canvas以外的區(qū)域。
在App Inventor中,為應(yīng)用添加動畫的方法之一就是讓物體對計時器事件做出響應(yīng),最常用的方法就是讓sprite按照設(shè)定的時間間隔,在canvas上進(jìn)行位置的移動。設(shè)定的時間間隔的方法是使用計時器事件最通用的方法。稍后我們還將討論另一種方法,即,利用ImageSprite及Ball組件的Speed(速度)及Heading(方向)屬性,通過編程來實現(xiàn)動畫效果。
點擊按鈕以及其他用戶觸發(fā)的事件理解起來非常簡單:用戶做動作,應(yīng)用通過執(zhí)行某些操作來進(jìn)行響應(yīng);但計時器事件則不然:這類事件不是由最終用戶發(fā)起,而是由時間的流動來觸發(fā)。你需要將應(yīng)用中的這類手機(jī)時鐘觸發(fā)的事件與用戶的行為區(qū)分開來。
定義計時器事件的第一步是在組件設(shè)計器中為應(yīng)用拖入一個Clock組件。Clock組件有一個關(guān)聯(lián)的TimerInterval(計時間隔)屬性,用來以毫秒為單位定義計時器的計時間隔(1秒=1000毫秒)。如果將TimerInterval設(shè)為500,就意味著每隔半秒鐘觸發(fā)一次計時器事件。計時間隔越小,物體的移動也就越快。
在設(shè)計器中完成Clock的添加以及TimerInterval的設(shè)定后,就可以在塊編輯器中拖出“when Clock.Timer”事件塊,并在其中加入任何你需要的塊,這些塊將每個一個計時間隔執(zhí)行一次。
要讓sprite隨時間移動,就需要用MoveTo函數(shù)。在塊編輯器的ImageSprite及Ball組件抽屜中可以找到這個函數(shù)。例如,要使一個球在水平方向上穿越屏幕,需要使用圖17-4中的塊。
圖 17-4 讓球在水平方向穿越屏幕
MoveTo的作用是在canvas上將物體移動到一個絕對位置,而不是相對位置。因此,為了移動到這個絕對位置,需要將MoveTo函數(shù)的參數(shù)設(shè)定為當(dāng)前位置與增量之和。這里我們要實現(xiàn)球的水平移動,只需要將參數(shù)x設(shè)定為當(dāng)前的x值與增量20之和,而y值保持不變(Ball1.Y)。
如果想讓球沿著對角線的方向移動,就需要同時設(shè)定x、y坐標(biāo)的增量,如圖17-5所示。
圖 17-5 設(shè)置x、y坐標(biāo)的增量,實現(xiàn)球在對角線方向的移動
在前面的例子中,球的移動有多快呢?速度取決于兩個因素:Clock組件的TimerInterval屬性值,以及MoveTo函數(shù)中的參數(shù)值。如果計時間隔設(shè)為1000毫秒,就意味著每秒鐘觸發(fā)一次計時事件,這樣會讓運(yùn)動變得不流暢。為了得到更為平滑的運(yùn)動,就需要縮短計時間隔。如果將TimerInterval設(shè)為100毫秒,則球每隔1/10秒移動20像素,或者每秒移動200像素,對于應(yīng)用的使用者來說,這個速度看起來會平滑得多。除了改變計時間隔之外,還有一種方法也可以改變速度,你能想到是什么方法嗎?(提示:速度與球移動的頻次以及每次的移動量相關(guān)。)在保持計時間隔100毫秒不變的情況下,改變MoveTo中的算式也可以改變移動的速度:讓球每次只移動2個像素,即2像素/100毫秒,這相當(dāng)于20像素/秒。
這種讓物體在屏幕上移動的能力,適合于那些飄來飄去的動畫類廣告,但要制作游戲或其他的動畫應(yīng)用,就需要更為復(fù)雜的功能。幸運(yùn)的是,App Inventor提供了幾個的高級塊,用于處理動畫類事件,如物體到達(dá)屏幕邊緣及兩個物體的碰撞。
在這種情況下,用高級塊來偵測兩個sprite之間的碰撞這類事件,表明App Inventor已經(jīng)深入到了程序的底層細(xì)節(jié)。其實你自己也可以利用Clock.Timer事件,通過檢查每個sprite的xy坐標(biāo)及Width、Height屬性來檢測到這類事件的發(fā)生,但這樣的程序涉及到非常復(fù)雜的邏輯。由于這類事件在許多游戲及其他應(yīng)用中很常見,因此App Inventor為你提供了這些功能。
圖 17-6 當(dāng)球到達(dá)邊緣時讓它重回左上角
重新考慮前面的動畫,物體在canvas上沿著對角線方向從左上角向右下角移動。依照前面的程序,物體沿對角線方向移動并將停在canvas的右下角(因為系統(tǒng)不允許sprite對象超出canvas的邊界)。
如果想讓物體在到達(dá)右下角后再重新出現(xiàn)在左上角,可以定義一個事件處理程序Ball.EdgeReached來響應(yīng)到達(dá)邊緣事件。
當(dāng)Ball碰到canvas的任何一個邊時,將觸發(fā)EdgeReached事件(到達(dá)邊緣事件,該事件只適用于Sprite及Ball組件)。這個事件,再加上前面提到的讓球沿斜線移動的定時器事件,兩個事件共同作用的結(jié)果就是,球從左上角向右下角移動,在到達(dá)彼岸猿猴再跳回到左上角,然后繼續(xù)移動,并再次跳回,循環(huán)往復(fù),永不停止(或者直到接到其他指令)。
注意到在EdgeReached事件中有一個參數(shù),edge1,它代表球碰到的那個邊,這里用數(shù)字來代表不同的方向:
North = 1
Northeast = 2
East = 3
Southeast = 4
South = -1
Southwest = -2
West = -3
射擊類、運(yùn)動類游戲以及其他類型的動畫應(yīng)用通常都會涉及到兩個或多個物體之間的碰撞(如,子彈擊中靶子)。
例如,考慮這樣一個游戲,當(dāng)其中的物體與其他物體發(fā)生碰撞時,會改變顏色,并發(fā)出爆炸聲,圖17-7中顯示了這樣一個事件處理程序。
圖 17-7 當(dāng)球與其他物體發(fā)生碰撞時,變色并發(fā)出爆炸聲
NoLongerCollidingWith事件是與CollidedWith相反的事件,當(dāng)兩個碰到一起的物體分開時,觸發(fā)該事件。而在游戲中,可能用到圖17-8中的塊。
圖 17-8 當(dāng)碰撞的物體離開時,球變黑色并停止爆炸聲
注意到CollidedWith及NoLongerCollidingWith事件都有一個參數(shù)other,它代表了被撞到的那個物體。這可以用來處理一個物體(如Ball1)與另一個指定物體之間的相互作用。如圖17-9所示。
圖 17-9 只有當(dāng)Ball1碰撞到ImageSprite1時才做相應(yīng)
之前我們沒有提到過這個“ImageSprite組件”塊。如果需要對兩個組件進(jìn)行比較(得知究竟是哪一個與之碰撞),如本例中的情形,就必須指定被比較的具體對象。為此,每個組件都有一個指向它自己的塊,而這個塊就在ImageSprite1的抽屜里,排在最后一個的就是。
到目前為止,我們所討論的動畫行為都沒有最終用戶的參與。毫無疑問,游戲都是交互的,最終用戶扮演著核心的角色,通常他們使用按鈕或其他界面對象來控制物體的速度及方向。
作為例子,我們來改變對角線移動的動畫,用戶可以讓移動停止然后再啟動??梢酝ㄟ^對Button.Click事件編程來實現(xiàn)這一點,具體方法是控制clock組件的啟用與禁用屬性。
在默認(rèn)情況下,Clock組件的timerEnabled屬性是被選中的,可以在事件處理程序中動態(tài)地設(shè)置它,如設(shè)為false。例如,在圖17-10的事件處理程序中,在用戶第一次點擊按鈕時,可以讓Clock的計時作用停止運(yùn)行。
圖 17-10 當(dāng)按鈕被第一次點擊時,停止計時
在Clock1.TimerEnabled屬性被設(shè)為false之后,Clock1.Timer事件不再被觸發(fā),因此球停止移動。
當(dāng)然,只是在第一次點擊時讓運(yùn)動停止,這樣的操作并不能為游戲帶來樂趣,需要在事件處理程序中添加一個ifelse塊來控制計時功能的啟用與禁用,從而實現(xiàn)對運(yùn)動的雙向控制(運(yùn)動及停止)。如圖17-11所示。
圖 17-11 添加ifelse塊,通過點擊按鈕來控制運(yùn)動的開始與停止
在點擊按鈕的事件處理程序中,第一次點擊按鈕,計時器停止計時,按鈕上的文字由“停止”變?yōu)椤伴_始”;第二次點擊按鈕,此時TimerEnabled的值為false,因此執(zhí)行“else”分支,于是計時器被置于啟用狀態(tài),使得物體重新開始移動,按鈕上的文字改回“停止”。關(guān)于ifelse快的詳細(xì)信息請參見第18章,另外,關(guān)于用方向傳感器創(chuàng)建交互動畫的例子,請參見第5章及第23章。
目前為止我們講述的動畫案例都是利用Clock組件的計時功能,計時器事件每觸發(fā)一次,物體就移動一次。采用Clock.Timer事件的方案是設(shè)定動畫最普遍的方案,除了可以移動物體,還可以隨時間改變物體的顏色,改變某些文字(好像應(yīng)用自己在輸入文字一樣),或者讓應(yīng)用以某個速度說話,等等。
App Inventor提供了另外一種不需要Clock組件而讓物體的移動的方法。你可能已經(jīng)注意到,ImageSprite及Ball組件都具有Heading(方向)、Speed(速度)及Interval(間隔)屬性。與Clock.Timer方案中定義事件處理程序相比,這里可以在組件設(shè)計器及塊編輯器中設(shè)置這些屬性,來實現(xiàn)對sprite運(yùn)動的控制。
為了便于描述,我們來重新考慮沿對角線移動的例子。Sprite或ball的Heading屬性的取值范圍為0-360度,如圖17-2所示。
圖 17-12 Heading屬性的取值范圍
如果Heading屬性設(shè)置為0,則球從左向右移動;如果設(shè)為90,則從底向上移動;如果設(shè)為180,則從右向左移動;如果設(shè)為270,則從上向下移動。
當(dāng)然,可以將Heading設(shè)定為0-360之間的任何值。要想讓球沿對角線從左上角向右下角移動,就需要將Heading設(shè)為315。
此外,還需要設(shè)置Speed屬性,它可以是0以外的任何值。此處Speed屬性對物體的移動作用與MoveTo函數(shù)的作用相同:定義了每個時間間隔(interval)物體移動的像素數(shù),而時間間隔由物體的Interval屬性來定義。
嘗試設(shè)置這些屬性,用Canvas及Ball創(chuàng)建一個測試應(yīng)用,并點擊“Connect AICompanion”,在手機(jī)(或設(shè)備)上查看應(yīng)用。修改Heading、Speed以及Interval屬性,看看球是如何運(yùn)動的。
如果你想通過編程來實現(xiàn)球在左上角與右下角之間做連續(xù)往復(fù)運(yùn)動,可以在組件設(shè)計器中將球的Heading屬性初始值設(shè)為315,然后在塊編輯器中添加Ball1.EdgeReached事件處理程序,當(dāng)球到達(dá)邊緣時,改變它的方向。如圖17-13所示。
圖 17-13 當(dāng)球到達(dá)邊緣時改變它的方向
動畫是物體隨時間的位置移動或某些屬性的變化,App Inventor為提供了幾個高級的組件及功能,讓動畫的實現(xiàn)變得簡單易行。通過對Clock組件的Timer事件進(jìn)行編程,可以創(chuàng)建任何類型的動畫,包括物體的移動——這是任何類型游戲中最基本的活動。
Canvas組件在設(shè)備的屏幕上定義了一個區(qū)域,物體可以在其中移動,并產(chǎn)生交互。Canvas內(nèi)部只接受兩種類型的組件,即ImageSprite組件及Ball組件。這些組件為處理碰撞及到達(dá)邊界這樣的事件提供了高級功能。此外,這些組件的Heading、Speed及Interval屬性也為運(yùn)動的實現(xiàn)提供了替代方法。
更多建議: