編輯:Android開發實例
往往我們在程序設計的時候喜歡將界面與處理分開,這樣降低耦合性,易於維護擴展。在貪吃蛇Snake這個示例程序中同樣將界面UI和游戲邏輯進行了分離,它的實現方式就是,用父類TileView來實現比較基礎的界面UI部分,而TileView類的子類SnakeView類完成了游戲控制邏輯部分,這樣就成功的將兩者進行了分離,對後面的擴展和維護奠定了良好的基礎。
界面UI
首先來看界面UI部分,基本思想大家都非常清楚:把整個屏幕看做一個二維數組,每一個元素可以視為一個方塊,因此每個方格在游戲進行過程中可以處於不同的狀態,比如空閒,牆,蘋果,貪食蛇(蛇身或蛇頭)。我們在操作游戲的過程,其實就是不斷修改相應方格的狀態,然後再讓整個View去重繪制自身(當然,還需要加入一些游戲當前所處狀態(失敗或成功)的判定機制)。TileView的數據成員如下:
Java代碼那麼在游戲還未正式開始前,首先要做一些初始化工作,在View第一次加載時會首先調用onSizeChanged,這裡就是做這些事的最好時機。
Java代碼注意模擬器屏幕默認的像素是320×400,而代碼中默認的方格大小為12,因此屏幕上放置的方格數為26×40,把屏幕剖分成這麼大後,再設置一個相應的二維int型數組來記錄每一個方格的狀態,根據方格的狀態,可以從mTileArray保存的圖標文件中讀取對應的狀態圖標。
第一次調用完onSizeChanged後,會緊跟著第一次來調用onDraw來繪制View自身,當然,此時由於所有方格的狀態都是0,所以它在屏幕上等於什麼也不會去繪制。
Java代碼onDraw要做的工作非常簡單,就是掃描每一個方格,根據方格當前狀態,從圖標文件中選擇對應的圖標繪制到這個方格上。當然這個onDraw在游戲進行過程中,會不斷地被調用,從而界面不斷被更新。
游戲邏輯
再來看子類SnakeView是如何在父類TileView的基礎上,加入特定的游戲邏輯,從而完成Snake這個程序的。
Java代碼由於SnakeView從TileView繼承而來,則可以說它已經擁有這個二維方格地圖了(只是此時地圖裡的所有方格狀態都是0)。那麼它有了這麼一個二維方格地圖,如何去初始化這個地圖呢?這在initNewGame函數中實現。
Java代碼想象下對整個游戲屏幕拍張照,然後對其下一個狀態再拍張照,那麼兩張照片之間的區別是怎麼產生的呢?對於系統來說,它只知道不斷調用onDraw,後者負責對整個屏幕進行繪制,那要產生兩個屏幕之間的差異,肯定要通過一些手段對某些數據結構(比如這裡的二維方格地圖)進行調整(比如用戶的控制指令,定時器等),然後等到下一次onDraw時就會把這些更改在界面上反映出來。
這裡要著重說明下private long mMoveDelay = 600;這個成員變量,雖然很不起眼,但仔細考慮它的作用就會發現很有趣,那麼改變它的大小到底是如何讓我們感覺到游戲變快或變慢呢?
可以打個簡單的比方,在時刻0游戲啟動,首先把蛇和蘋果的位置都在方格地圖上作好了標記,然後我們在update函數中修改蛇身讓蛇向北前進一步,而這個改變此時還只是停留在內部的核心數據結構上(即二維方格地圖),還沒有在界面上顯示出來。當然,我們馬上想到要想讓這更改顯示出來,讓系統調用onDraw去繪制不就完了嗎?可是問題是我們不知道系統是隔多長時間去調用onDraw函數,於是mMoveDelay此時就發揮作用了,通過它就可以設置休眠的時間,等時間一到,馬上就會通知SnakeView去重繪制。你可以試試把mMoveDelay數值調大,就會看出我上面提到的“拍照“的效果。
Handler的使用
寫過JavaScript或者ActionScript的開發者,對於setInterval的用法會非常了解。那麼在Android中如何實現setInterval的方法呢?其中有兩種方法可以實現類似的功能,其中一個是在線程中調用Handler方法,另外一個是應用Timer。Snake中使用了前者。
Java代碼而實際調用的處理函數update就可以說是整個游戲的引擎,正是由於它的工作(修改蛇和蘋果的狀態到一個新的狀態,然後休眠自己,然後等到蘇醒後在Handler中就會讓系統區繪制上次修改過的二維方塊地圖,然後再次調用update,如此循環反復,生生不息),才使得游戲不斷被推進,因此,比做“引擎“不為過。
Java代碼既然update是游戲的動力,要讓游戲停止下來只要不再調用update就可以了(因為此時其實是畫面靜止了),因此游戲進入暫停(這個狀態還可以轉為“運行“,其實就是繼續可以修改,再繪制),若進入失敗(其實此時二維方塊地圖還停留在最後一個畫面處,這也是為什麼在開始時要首先清理掉整個地圖)【這一點,可以在游戲失敗後,再次開始新游戲,此時通過設置的斷點即可觀察到上次游戲運行時的底層數據】。
一點困惑
可是個人認為Snake下面這段代碼讀起來有點怪,有點像一個“先有雞,還是先有蛋?“的問題,導致我的思維邏輯上出現一個“怪圈“。
Java代碼按照這段代碼的意思來看,當休眠的時間已經到了,首先去調用update,即為下一次繪制做准備工作,再讓自己休眠起來,最後通知系統重繪制自己。
哎,這讓我難以理解,還是回到時刻0的例子來說,在時刻0時讓蛇身向北前進了一步(指的是底層的二維方格地圖的修改,不是界面),然後讓自己休眠0.6毫秒,當時間到了,首先去調用update方法,那麼就又會讓蛇身做出修改,也就是把上一次還沒繪制的覆蓋掉了(那麼上一次的修改豈不是白費,還沒畫上去呢),更何況在update中又會讓自己去休眠(還沒調用invalidate,怎麼又去休眠了?),又怎麼還能去通知系統調用我的onDraw方法呢?也就是說invalidate根本沒有執行???
按我的理解,應該把順序顛倒一下,先通知系統去調用onDraw方法重繪,使得上一次對底層二維方格地圖的修改顯示出來,然後再去為下一次修改做准備工作,最後讓自己進入休眠,等待蘇醒過來,如此循環反復。實驗證明,顛倒過來也是正確的,不過關於這一個迷惑我的地方,希望有朋友能指點我一下!
記得在javascript裡使用setInterval時,也是先寫處理邏輯,然後在末尾處寫上一句setInterval(這也是我習慣的思維方式了),難道google上面這種寫法有何深意?
此外,感覺每次繪制時都重新繪制牆壁,有點浪費時間,因為牆壁根本沒有任何變化的。還有就是mLastMove這個變量設置的初衷是保證當前時間點距上一次變化已經過去了mMoveDelay毫秒,可是既然已經用了sleep機制,再使用這個時間差看上去並無必要。
在Android程序中很多客戶端軟件和浏覽器軟件都喜歡用Tab分頁標簽來搭建界面框架。讀者也許會馬上想到使用TabHost 與 TabActivity的組合,其實
Android應用程序可以在許多不同地區的許多設備上運行。為了使應用程序更具交互性,應用程序應該處理以適合應用程序將要使用的語言環境方面的文字,數字,文件等。在本章中,我
Android模擬器報Failed To Allocate memory 8錯誤的解決辦法Android在啟動模擬器AVD時,出現下面的異常:“Failed to
Android應用程序可以在許多不同地區的許多設備上運行。為了使應用程序更具交互性,應用程序應該處理以適合應用程序將要使用的語言環境方面的文字,數字,文件等。在本章中,我