本章將討論創建另一類應用的方法,應用中使用了簡單的可移動的動畫對象。你將學習使用App Inventor創建二維游戲的基本知識,并熟練使用圖片精靈(image sprite)及處理兩個物體碰撞一類的事件。
當你在電腦屏幕上看到一個平滑移動的物體時,你實際上看到的是一連串快速移動的圖片,每次只移動一個極小的距離,它利用了人的視覺暫留,從這一點上,它無異于“手翻書”—— 一種通過快速翻頁來看到動畫效果的書(這也是那些精美絕倫的動畫電影的制作方法)。
在App Inventor中,通過在Canvas組件上放置物體,并讓這些物體隨時間在Canvas內移動,從而產生出動畫效果。本章將學習使用Canvas的坐標系統,學習利用Clock.Timer事件來觸發運動,以及如何控制運動速度、如何響應兩個物體的碰撞事件等等。

## **在應用中添加Canvas組件**
從組件面板的Drawing and Animation組中拖出Canvas組件,然后定義它的Width及Height屬性。通常我們希望Canvas與屏幕等寬,為此將寬度設為“Fill parent”,如圖17-1所示。

**圖 17-1 設置Canvas組件的Width屬性**
可以用同樣的方式設定Height屬性,但一般會將其設為一個數字(如300像素),以便為Canvas上面或下面的其他組件留出空間。
### **Canvas的坐標系統**
Canvas上的圖畫實際上是一個許多像素構成的表格,像素是手機(或其他設備)屏幕上能夠顯示的最小的色塊,每個像素都在Canvas上有它的位置(或者說單元格),位置由X-Y坐標系定義,如圖17-2所示,X定義了水平方向上的位置(方向是從左到右),Y定義了垂直方向的位置(從上到下)。

**圖 17-2 Canvas的坐標系統**
坐標軸的方向定義可能與你的經驗不一致,不過位于Canvas左上角的單元格的x、y坐標都為零,因此這個位置表示為(x=0,y=0)。(這與App Inventor列表中使用的索引值有所不同,索引值從1開始,看起來更容易理解。)向右移動時,x坐標增大;向下移動時,y值變大。位于左上角單元格右側的單元格坐標為(x=1,y=0)。右上角單元格的x坐標等于canvas的寬度減1,多數手機屏幕的寬度都在300左右,但這里例子中顯示的寬度是20,因此右上角的單元格坐標為(x=19,y=0)。
要改變canvas的外觀有兩種方法:①在上面繪畫,或者②在上面放置移動的物體,本章所涉及的是后者,但我們首先要討論如何繪畫,以及如何通過繪畫來創建動畫(這也是本書第二章油漆桶中的主要內容)。
Canvas中的每一個單元格都對應顯示為一個有顏色的像素。Canvas組件提供的Canvas.DrawLine及Canvas.Circle塊可以用來在canvas上以繪制像素組成的圖畫。首先需要將Canvas.PaintColor屬性設置為你需要的顏色,然后調用某個具體的繪畫塊來畫出顏色。其中的DrawCircle塊可以繪制直徑為任意大小的圓,但如果你將半徑設為1,如圖17-3所示,那么只能畫出一個單獨的像素。

**圖 17-3 用1個像素畫圓,每次只能畫1個單獨的像素**
在塊編輯器Built-in組的Colors抽屜中,App Inventor提供了13種常用的顏色,可以用來繪制像素圖(或設置組件背景色)。也可以使用顏色編碼方案來獲得更為豐富的顏色,顏色編碼方案的解釋請參見相關App Inventor文檔:[http://appinventor.googlelabs.com/learn/reference/blocks/colors.html](http://appinventor.googlelabs.com/learn/reference/blocks/colors.html)。
改變canvas外觀的第二種方法是在canvas上放置Ball和ImageSprite組件。sprite是一個被放置在場景中的圖形對象,所謂的場景這里指的就是canvas。Ball和ImageSprite組件都屬于sprites類型,只是外觀不同而已。Ball為圓形,只能通過改變顏色和半徑來改變它的外觀,而ImageSprite可以是任何形狀;ImageSprite和Ball都只能添加到Canvas中,不可能將它們拖入用戶界面中Canvas以外的區域。
## **用計時事件制造動畫**
在App Inventor中,為應用添加動畫的方法之一就是讓物體對計時器事件做出響應,最常用的方法就是讓sprite按照設定的時間間隔,在canvas上進行位置的移動。設定的時間間隔的方法是使用計時器事件最通用的方法。稍后我們還將討論另一種方法,即,利用ImageSprite及Ball組件的Speed(速度)及Heading(方向)屬性,通過編程來實現動畫效果。
點擊按鈕以及其他用戶觸發的事件理解起來非常簡單:用戶做動作,應用通過執行某些操作來進行響應;但計時器事件則不然:這類事件不是由最終用戶發起,而是由時間的流動來觸發。你需要將應用中的這類手機時鐘觸發的事件與用戶的行為區分開來。
定義計時器事件的第一步是在組件設計器中為應用拖入一個Clock組件。Clock組件有一個關聯的TimerInterval(計時間隔)屬性,用來以毫秒為單位定義計時器的計時間隔(1秒=1000毫秒)。如果將TimerInterval設為500,就意味著每隔半秒鐘觸發一次計時器事件。計時間隔越小,物體的移動也就越快。
在設計器中完成Clock的添加以及TimerInterval的設定后,就可以在塊編輯器中拖出“when Clock.Timer”事件塊,并在其中加入任何你需要的塊,這些塊將每個一個計時間隔執行一次。
### **產生運動**
要讓sprite隨時間移動,就需要用MoveTo函數。在塊編輯器的ImageSprite及Ball組件抽屜中可以找到這個函數。例如,要使一個球在水平方向上穿越屏幕,需要使用圖17-4中的塊。

**圖 17-4 讓球在水平方向穿越屏幕**
MoveTo的作用是在canvas上將物體移動到一個絕對位置,而不是相對位置。因此,為了移動到這個絕對位置,需要將MoveTo函數的參數設定為當前位置與增量之和。這里我們要實現球的水平移動,只需要將參數x設定為當前的x值與增量20之和,而y值保持不變(Ball1.Y)。
如果想讓球沿著對角線的方向移動,就需要同時設定x、y坐標的增量,如圖17-5所示。

**圖 17-5 設置x、y坐標的增量,實現球在對角線方向的移動**
### **控制速度**
在前面的例子中,球的移動有多快呢?速度取決于兩個因素:Clock組件的TimerInterval屬性值,以及MoveTo函數中的參數值。如果計時間隔設為1000毫秒,就意味著每秒鐘觸發一次計時事件,這樣會讓運動變得不流暢。為了得到更為平滑的運動,就需要縮短計時間隔。如果將TimerInterval設為100毫秒,則球每隔1/10秒移動20像素,或者每秒移動200像素,對于應用的使用者來說,這個速度看起來會平滑得多。除了改變計時間隔之外,還有一種方法也可以改變速度,你能想到是什么方法嗎?(提示:速度與球移動的頻次以及每次的移動量相關。)在保持計時間隔100毫秒不變的情況下,改變MoveTo中的算式也可以改變移動的速度:讓球每次只移動2個像素,即2像素/100毫秒,這相當于20像素/秒。
### **高級動畫功能**
這種讓物體在屏幕上移動的能力,適合于那些飄來飄去的動畫類廣告,但要制作游戲或其他的動畫應用,就需要更為復雜的功能。幸運的是,App Inventor提供了幾個的高級塊,用于處理動畫類事件,如物體到達屏幕邊緣及兩個物體的碰撞。
在這種情況下,用高級塊來偵測兩個sprite之間的碰撞這類事件,表明App Inventor已經深入到了程序的底層細節。其實你自己也可以利用Clock.Timer事件,通過檢查每個sprite的xy坐標及Width、Height屬性來檢測到這類事件的發生,但這樣的程序涉及到非常復雜的邏輯。由于這類事件在許多游戲及其他應用中很常見,因此App Inventor為你提供了這些功能。
### **抵達邊界**

**圖 17-6 當球到達邊緣時讓它重回左上角**
重新考慮前面的動畫,物體在canvas上沿著對角線方向從左上角向右下角移動。依照前面的程序,物體沿對角線方向移動并將停在canvas的右下角(因為系統不允許sprite對象超出canvas的邊界)。
如果想讓物體在到達右下角后再重新出現在左上角,可以定義一個事件處理程序Ball.EdgeReached來響應到達邊緣事件。
當Ball碰到canvas的任何一個邊時,將觸發EdgeReached事件(到達邊緣事件,該事件只適用于Sprite及Ball組件)。這個事件,再加上前面提到的讓球沿斜線移動的定時器事件,兩個事件共同作用的結果就是,球從左上角向右下角移動,在到達彼岸猿猴再跳回到左上角,然后繼續移動,并再次跳回,循環往復,永不停止(或者直到接到其他指令)。
注意到在EdgeReached事件中有一個參數,edge1,它代表球碰到的那個邊,這里用數字來代表不同的方向:
* North = 1
* Northeast = 2
* East = 3
* Southeast = 4
* South = -1
* Southwest = -2
* West = -3
* Northwest = -4
### **CollidingWith事件與NoLongerCollidingWith事件**
射擊類、運動類游戲以及其他類型的動畫應用通常都會涉及到兩個或多個物體之間的碰撞(如,子彈擊中靶子)。
例如,考慮這樣一個游戲,當其中的物體與其他物體發生碰撞時,會改變顏色,并發出爆炸聲,圖17-7中顯示了這樣一個事件處理程序。

**圖 17-7 當球與其他物體發生碰撞時,變色并發出爆炸聲**
NoLongerCollidingWith事件是與CollidedWith相反的事件,當兩個碰到一起的物體分開時,觸發該事件。而在游戲中,可能用到圖17-8中的塊。

**圖 17-8 當碰撞的物體離開時,球變黑色并停止爆炸聲**
注意到CollidedWith及NoLongerCollidingWith事件都有一個參數other,它代表了被撞到的那個物體。這可以用來處理一個物體(如Ball1)與另一個指定物體之間的相互作用。如圖17-9所示。

**圖 17-9 只有當Ball1碰撞到ImageSprite1時才做相應**
之前我們沒有提到過這個“ImageSprite組件”塊。如果需要對兩個組件進行比較(得知究竟是哪一個與之碰撞),如本例中的情形,就必須指定被比較的具體對象。為此,每個組件都有一個指向它自己的塊,而這個塊就在ImageSprite1的抽屜里,排在最后一個的就是。
### **交互動畫**
到目前為止,我們所討論的動畫行為都沒有最終用戶的參與。毫無疑問,游戲都是交互的,最終用戶扮演著核心的角色,通常他們使用按鈕或其他界面對象來控制物體的速度及方向。
作為例子,我們來改變對角線移動的動畫,用戶可以讓移動停止然后再啟動。可以通過對Button.Click事件編程來實現這一點,具體方法是控制clock組件的啟用與禁用屬性。
在默認情況下,Clock組件的timerEnabled屬性是被選中的,可以在事件處理程序中動態地設置它,如設為false。例如,在圖17-10的事件處理程序中,在用戶第一次點擊按鈕時,可以讓Clock的計時作用停止運行。

**圖 17-10 當按鈕被第一次點擊時,停止計時**
在Clock1.TimerEnabled屬性被設為false之后,Clock1.Timer事件不再被觸發,因此球停止移動。
當然,只是在第一次點擊時讓運動停止,這樣的操作并不能為游戲帶來樂趣,需要在事件處理程序中添加一個ifelse塊來控制計時功能的啟用與禁用,從而實現對運動的雙向控制(運動及停止)。如圖17-11所示。

**圖 17-11 添加ifelse塊,通過點擊按鈕來控制運動的開始與停止**
在點擊按鈕的事件處理程序中,第一次點擊按鈕,計時器停止計時,按鈕上的文字由“停止”變為“開始”;第二次點擊按鈕,此時TimerEnabled的值為false,因此執行“else”分支,于是計時器被置于啟用狀態,使得物體重新開始移動,按鈕上的文字改回“停止”。關于ifelse快的詳細信息請參見第18章,另外,關于用方向傳感器創建交互動畫的例子,請參見第5章及第23章。
## **關于沒有計時器的sprite動畫**
目前為止我們講述的動畫案例都是利用Clock組件的計時功能,計時器事件每觸發一次,物體就移動一次。采用Clock.Timer事件的方案是設定動畫最普遍的方案,除了可以移動物體,還可以隨時間改變物體的顏色,改變某些文字(好像應用自己在輸入文字一樣),或者讓應用以某個速度說話,等等。
App Inventor提供了另外一種不需要Clock組件而讓物體的移動的方法。你可能已經注意到,ImageSprite及Ball組件都具有Heading(方向)、Speed(速度)及Interval(間隔)屬性。與Clock.Timer方案中定義事件處理程序相比,這里可以在組件設計器及塊編輯器中設置這些屬性,來實現對sprite運動的控制。
為了便于描述,我們來重新考慮沿對角線移動的例子。Sprite或ball的Heading屬性的取值范圍為0-360度,如圖17-2所示。

**圖 17-12 Heading屬性的取值范圍**
如果Heading屬性設置為0,則球從左向右移動;如果設為90,則從底向上移動;如果設為180,則從右向左移動;如果設為270,則從上向下移動。
當然,可以將Heading設定為0-360之間的任何值。要想讓球沿對角線從左上角向右下角移動,就需要將Heading設為315。
此外,還需要設置Speed屬性,它可以是0以外的任何值。此處Speed屬性對物體的移動作用與MoveTo函數的作用相同:定義了每個時間間隔(interval)物體移動的像素數,而時間間隔由物體的Interval屬性來定義。
嘗試設置這些屬性,用Canvas及Ball創建一個測試應用,并點擊“Connect AICompanion”,在手機(或設備)上查看應用。修改Heading、Speed以及Interval屬性,看看球是如何運動的。
如果你想通過編程來實現球在左上角與右下角之間做連續往復運動,可以在組件設計器中將球的Heading屬性初始值設為315,然后在塊編輯器中添加Ball1.EdgeReached事件處理程序,當球到達邊緣時,改變它的方向。如圖17-13所示。

**圖 17-13 當球到達邊緣時改變它的方向**
## **小結**
動畫是物體隨時間的位置移動或某些屬性的變化,App Inventor為提供了幾個高級的組件及功能,讓動畫的實現變得簡單易行。通過對Clock組件的Timer事件進行編程,可以創建任何類型的動畫,包括物體的移動——這是任何類型游戲中最基本的活動。
Canvas組件在設備的屏幕上定義了一個區域,物體可以在其中移動,并產生交互。Canvas內部只接受兩種類型的組件,即ImageSprite組件及Ball組件。這些組件為處理碰撞及到達邊界這樣的事件提供了高級功能。此外,這些組件的Heading、Speed及Interval屬性也為運動的實現提供了替代方法。
- 簡介
- 序言
- 前言
- 第 1 章 Hello 貓咪
- 第 2 章 油漆桶
- 第 3 章 打地鼠
- 第 4 章 開車不發短信
- 第 5 章 瓢蟲快跑
- 第 6 章 巴黎地圖旅游
- 第 7 章 安卓,我的車在哪?
- 第 8 章 總統測驗
- 第 9 章 木琴
- 第 10 章 出題及答題
- 第 11 章 廣播中心
- 第 12 章 遙控機器人
- 第 13 章 亞馬遜掌上書店
- 第 14 章 理解應用的結構
- 第 15 章 軟件工程與應用調試
- 第 16 章 應用中的存儲
- 第 17 章 創建動畫應用
- 第 18 章 程序中的決策:條件塊
- 第 19 章 數據列表編程
- 第 20 章 循環
- 第 21 章 定義過程
- 第 22 章 數據庫
- 第 23 章 傳感器
- 第 24 章 與Web API通信