Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> Android示例程序剖析之LunarLander游戲

Android示例程序剖析之LunarLander游戲

編輯:Android開發實例

       前面有幾篇文章寫的是對Android示例程序貪吃蛇Snake程序的剖析,本文繼續分析Android自帶的另一個小游戲LunarLander的程序。在貪吃蛇Snake程序中采用了“定時器+系統調用onDraw”的架構,而LunarLander程序采用的是“多線程+強制自行繪制”的架構思路,比前者更為實用。

       與貪吃蛇Snake程序的對比

       就界面Layout來說,這個程序其實和Snake沒有什麼不同,同樣是采用了FrameLayout,而且游戲的主界面由一個自定義的View來實現,這裡是LunarView。讀過貪吃蛇程序剖析文章的朋友也許會發現,Snake的架構是“定時器+系統調用onDraw”來實現的,這裡有一個最大的缺陷就是onDraw是由Android系統來調用的,我們只能依賴它,卻無法自行控制。這就好比一個黑盒,當然,總是能把我們要的東西給做出來,可卻無法控制其做事的細節,這對於游戲這樣高效率的東西可是不利的,因此最好的解決之道當然是把繪制這部分工作自己”承包“過來,告別吃大鍋飯的,進入”聯產承包制”時代。

       此外,由於游戲的本質就是連續兩幀圖片之間發生些許差異,那麼要不斷催生這種差異的發生,只要有某種連續不斷發生的事件在進行就可以,例如Snake中使用的定時器,就是在不斷地產生這種“差異源”,與此類似,一個線程也是不斷在運行中,通過它也是可以不斷產生這種“差異源”的。

  SurfaceView初探

       如果說Snake中使用的Layout加自定義View是一把小型武器的話,那在SurfaceView對於android中游戲的開發來說就算是重型武器了。我們使用前者時總是容易把游戲中某個對象(比如上文的每一個方格)當做一個小組件來處理,而後者則根本沒有這種劃分的概念,在它眼中,所有東西都是在Canvas(畫布)中自行繪制出來的(背景,人物等)。

  SurfaceView提供直接訪問一個可畫圖的界面,可以控制在界面頂部的子視圖層。SurfaceView是提供給需要直接畫像素而不是使用窗體部件的應用使用的。Android圖形系統中一個重要的概念和線索是surface。View及其子類(如TextView, Button)要畫在surface上。每個surface創建一個Canvas對象(但屬性時常改變),用來管理view在surface上的繪圖操作,如畫點畫線。還要注意的是,使用它的時候,一般都是出現在最頂層的:The view hierarchy will take care of correctly compositing with the Surface any siblings of the SurfaceView that would normally appear on top of it. 使用的SurfaceView的時候,一般情況下還要對其進行創建、銷毀、改變時的情況進行監視,這就要用到SurfaceHolder.Callback。

Java代碼
  1. class LunarView extends SurfaceView implements SurfaceHolder.Callback   
  2. {   
  3.     public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}   
  4. //在surface的大小發生改變時激發   
  5.     public void surfaceCreated(SurfaceHolder holder){}   
  6. //在創建時激發,一般在這裡調用畫圖的線程。   
  7.     public void surfaceDestroyed(SurfaceHolder holder) {}   
  8. //銷毀時激發,一般在這裡將畫圖的線程停止、釋放。   
  9. }  

       surfaceCreated會首先被調用,然後是surfaceChanged,當程序結束時會調用surfaceDestroyed。下面來看看LunarView最重要的成員變量,也就是負責這個View所有處理的線程。

Java代碼
  1. private LunarThread thread; // 實際工作線程   
  2. thread = new LunarThread(holder, context, new Handler() {   
  3.      @Override  
  4.      public void handleMessage(Message m)    
  5.      {   
  6.           mStatusText.setVisibility(m.getData().getInt("viz"));   
  7.           mStatusText.setText(m.getData().getString("text"));   
  8.      }   
  9. });   

  這個線程由私有類LunarThread實現,它裡面還有一個自己的消息隊列處理器,用來接收游戲狀態消息,並在屏幕上顯示當前狀態(而這個功能在Snake中是通過View自己控制其包含的TextView是否顯示來實現的,相比之下,LunarThread的消息處理機制更為高效)。由於有了LunarThread這個負責具體工作的對象,所以LunarView的大部分工作都委托給後者去執行。

Java代碼
  1. public void surfaceChanged(SurfaceHolder holder, int format, int width,int height){   
  2.      thread.setSurfaceSize(width, height);   
  3. }   
  4. public void surfaceCreated(SurfaceHolder holder)   
  5. {//啟動工作線程結束   
  6.       thread.setRunning(true);   
  7.      thread.start();   
  8. }   
  9. public void surfaceDestroyed(SurfaceHolder holder)   
  10. {   
  11.      boolean retry = true;   
  12.      thread.setRunning(false);   
  13.      while (retry)    
  14.      {   
  15.          try  
  16.          {//等待工作線程結束,主線程才結束   
  17.                thread.join();   
  18.              retry = false;   
  19.          }    
  20.          catch (InterruptedException e)    
  21.          {   
  22.          }   
  23.      }   
  24. }  

  工作線程LunarThread

  由於SurfaceHolder是一個共享資源,因此在對其操作時都應該實行“互斥操作“,即需要使用synchronized進行”封鎖“機制。

       再來討論下為什麼要使用消息機制來更新界面的文字信息呢?其實原因是這樣的,渲染文字的工作實際上是主線程(也就是LunarView類)的父類View的工作,而並不屬於工作線程LunarThread,因此在工作線程中式無法控制的。所以我們改為向主線程發送一個Message來代替,讓主線程通過Handler對接收到的消息進行處理,從而更新界面文字信息。再回顧Android示例程序剖析之Snake貪吃蛇(三:界面UI、游戲邏輯和Handler)中SnakeView裡的文字信息更新,由於是SnakeView自己(就這一個線程)對其包含的TextView做控制,當然沒有這樣的問題了。

Java代碼
  1. public void setState(int mode, CharSequence message)    
  2. {   
  3.      synchronized (mSurfaceHolder)   
  4.      {   
  5.           mMode = mode;   
  6.           if (mMode == STATE_RUNNING)   
  7.           {//運行中,隱藏界面文字信息   
  8.                Message msg = mHandler.obtainMessage();   
  9.                Bundle b = new Bundle();   
  10.                b.putString("text", "");   
  11.                b.putInt("viz", View.INVISIBLE);   
  12.                msg.setData(b);   
  13.                mHandler.sendMessage(msg);   
  14.           }    
  15.           else    
  16.           {//根據當前狀態設置文字信息   
  17.                mRotating = 0;   
  18.                mEngineFiring = false;   
  19.                Resources res = mContext.getResources();   
  20.                CharSequence str = "";   
  21.                if (mMode == STATE_READY)   
  22.                   str = res.getText(R.string.mode_ready);   
  23.                else if (mMode == STATE_PAUSE)   
  24.                   str = res.getText(R.string.mode_pause);   
  25.                else if (mMode == STATE_LOSE)   
  26.                   str = res.getText(R.string.mode_lose);   
  27.                else if (mMode == STATE_WIN)   
  28.                   str = res.getString(R.string.mode_win_prefix)   
  29.                                 + mWinsInARow + " "  
  30.                                 + res.getString(R.string.mode_win_suffix);   
  31.                if (message != null) {   
  32.                    str = message + "\n" + str;   
  33.                }   
  34.                if (mMode == STATE_LOSE)    
  35.                     mWinsInARow = 0;   
  36.                Message msg = mHandler.obtainMessage();   
  37.                Bundle b = new Bundle();   
  38.                b.putString("text", str.toString());   
  39.                b.putInt("viz", View.VISIBLE);   
  40.                msg.setData(b);   
  41.                mHandler.sendMessage(msg);   
  42.           }   
  43.      }   
  44. }  

    下面就是LunaThread這個工作線程的執行函數了,它一直不斷在重復做一件事情:鎖定待繪制區域(這裡是整個屏幕),若游戲還在進行狀態,則更新底層的數據,然後直接強制界面重新繪制。

Java代碼
  1. public void run()    
  2. {   
  3.       while (mRun)    
  4.       {   
  5.            Canvas c = null;   
  6.            try    
  7.            {   
  8.                //鎖定待繪制區域   
  9.                c = mSurfaceHolder.lockCanvas(null);   
  10.                synchronized (mSurfaceHolder)   
  11.                {   
  12.                     if (mMode == STATE_RUNNING)    
  13.                         updatePhysics();//更新底層數據,判斷游戲狀態   
  14.                         doDraw(c);//強制重繪制   
  15.                   }   
  16.            }    
  17.            finally    
  18.            {   
  19.                 if (c != null) {   
  20.                     mSurfaceHolder.unlockCanvasAndPost(c);   
  21.                 }   
  22.            }   
  23.      }   
  24. }  

       這裡要注意的是最後要調用unlockCanvasAndPost來結束鎖定畫圖,並提交改變。

  強制自行繪制

       doDraw這段代碼就是在自己的Canvas上進行繪制,具體的繪制就不解釋了,主要就是用drawBitmap,drawRect,drawLine。值得注意的一段代碼是下面這個:

Java代碼
  1. canvas.save();   
  2. canvas.rotate((float) mHeading, (float) mX, mCanvasHeight   
  3.                     - (float) mY);   
  4. if (mMode == STATE_LOSE) {   
  5.       mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop   
  6.                         + mLanderHeight);   
  7.       mCrashedImage.draw(canvas);   
  8. } else if (mEngineFiring) {   
  9.       mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop   
  10.                         + mLanderHeight);   
  11.       mFiringImage.draw(canvas);   
  12. } else {   
  13.       mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop   
  14.                         + mLanderHeight);   
  15.       mLanderImage.draw(canvas);   
  16. }   
  17. canvas.restore();   

       在繪制火箭的前後,調用了save()和restore(),它是先保存當前矩陣,將其復制到一個私有堆棧上。然後接下來對rotate的調用還是在原有的矩陣上進行操作,但當restore調用後,以前保存的設置又重新恢復。不過,在這裡還是看不出有什麼用處。

  暫停/繼續機制

       LunarLancher的暫停其實並沒有不再強制重繪制,而是沒有對底層的數據做任何修改,依然繪制同一幀畫面,而繼續則是把mLastTime設置為當前時間+100毫秒的時間點,因為以前暫停時mLastTime就不再更新了,這樣做事為了與當前時間同步起來。

Java代碼
  1. public void pause()   
  2. {//暫停   
  3.       synchronized (mSurfaceHolder)    
  4.      {   
  5.           if (mMode == STATE_RUNNING)   
  6.               setState(STATE_PAUSE);   
  7.      }   
  8. }   
  9. public void unpause()   
  10. {// 繼續   
  11.       // Move the real time clock up to now   
  12.      synchronized (mSurfaceHolder)   
  13.      {   
  14.            mLastTime = System.currentTimeMillis() + 100;   
  15.      }   
  16.      setState(STATE_RUNNING);   
  17. }  

       這樣做的目的是為了制造“延遲“的效果,都是因為updatePhysics函數裡這兩句:

Java代碼
  1. if (mLastTime > now) return;   
  2. double elapsed = (now - mLastTime) / 1000.0;   

       至於游戲的控制邏輯和判定部分就不介紹了,沒有多大意思。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved