(轉)原始碼微信飛機大戰講解(一):基礎儲備 (二):繪製動態文字和圖片

qq272803220發表於2014-03-14

其實,遊戲有很多分類,在此我就不囉嗦了,baidu搜尋就有答案。我最終以大戰飛機(雷電)遊戲為實現目標和講解案例。我不採用任何的遊戲引擎,全部以android原生態的功能來實現。


關於遊戲開發要掌握的一些基礎:

1、座標系

Android系統中,螢幕的座標原點(0,0)跟電腦螢幕的一樣,在螢幕的左上角,橫向代表x軸,向右延生為正方向,縱向代表y軸,向下延生為正方向,如下圖:


2、螢幕的寬度和高度

為了能讓遊戲角色顯示在螢幕的合適位置,我們需要知道螢幕的寬度和高度作為參考。在Android獲取螢幕的高寬很簡單,只需要在Activity中獲取WindowManager物件,然後從WindowManager物件中獲取Display物件,再從Display物件中獲取高寬,程式碼如下:

[java] view plaincopy
  1. WindowManager windowManager = getWindowManager();  
  2. Display display = windowManager.getDefaultDisplay();  
  3. int screenWidth = display.getWidth();  
  4. int screenHeight = display.getHeight();  

3、邊界問題

幾乎所有的遊戲都需要做邊界判斷,比如我們要實現的飛機大戰,我們需要判斷玩家、敵人和子彈等檢視的邊界是否超過螢幕,或者他們之間有沒有發生碰撞。簡單來說就是判斷有沒有超出螢幕的邊界,或者遊戲角色之間有沒有重疊,一般都是通過座標(x,y)來判斷的。

1) 如果x<0,那麼超過了螢幕的左邊界,如果x>螢幕的寬度,那麼超過的螢幕的右邊界。

2) 如果y<0,那麼超過了螢幕的上邊界,如果y>螢幕的高度,那麼超過了螢幕的下邊界。


4、角色或場景的移動

遊戲中的角色移動其實也是簡單的,就是不斷的改變角色的x,y座標的值,然後不斷重新把他們繪製到螢幕裡,只是這個動作的非常快,以至於人的眼睛是無法分辨。但是,這種角色座標改變的邏輯是需要我們程式猿按照遊戲的邏輯的來控制的。常常會使用到執行緒的知識點,不過也很簡單。

1) 如果向左移動:x 的座標減小,向右移動:x 的座標增大;

2) 如果向上移動:y 的座標減小,想下移動:y 的座標增大;

一、概述

這一講我將帶著大家來實現文字和圖片的繪製,然後試著讓文字和圖片在螢幕裡動起來。雖然,離真正的遊戲還有一段距離,但是,這些都是遊戲的基礎,所以,大家都是需要掌握的。好的,不多說了,一起進入正題吧!

 

完成這一講的任務,我們需要掌握如下一些概念,然後我會分別進行講解。

層的概念
    文字層
    貼圖層
View物件:自定義顯示控制元件
    onDraw()方法:執行一系列繪製
Canvas物件:畫布,呈現資料
    Paint:畫筆物件
    drawText:繪製文字
    drawBitmap:繪製貼圖
SurfaceView物件
    SurfaceHolder.Callback
Thread:執行緒讓畫面動起來

二、層的概念

學習photoshop的朋友肯定都知道層的概念,用通俗的話來說,層就是一個透明的玻璃紙。在android遊戲中層的概念跟photoshop中也很相似,它可以用來呈現文字、圖片等元素。遊戲中一般都會有很多層組成,每個層中會有不同的元素,而且每個層中的元素是獨立可控的。比如:在打飛機遊戲中,背景是一層,玩家飛機是一層,敵機也是一層。大家要注意:層是有層次關係的,上面層會覆蓋下面的層。那麼,在打飛機遊戲中,背景肯定是最裡面一層,其他任何遊戲元素都呈現在背景的上面。

 

另外有一點要跟大家特別講一下,就是關於圖片素材問題,我們都知道圖片都是正規的矩形,而且有背景,所以在場景中肯定會有顏色塊,看起來很不逼真。但是PNG格式的圖片是可以做成透明背景,這樣就解決這個問題,這也就是為什麼android的圖片素材基本上都是PNG格式的原因。

 

文字層:顯示文字內容的層

貼圖層:顯示圖片元素的層

但是,常常文字層和貼圖層分的不是很清楚,文字層也可以繪製貼圖,貼圖層也可以繪製文字。

 

三、View物件

在普通的應用開發中似乎很難直接接觸到View類,但實際上幾乎所有的Android顯示元件都是繼承View類,TextView, EidtView, ImageView等等都是繼承View類。開發中我們常常在XML檔案中使用這些元件,但是如果要讓元件具有更多獨特的功能就需要自定義View類來擴充套件我們的需求了。

 

在Android遊戲當中充當主要的除了控制類外就是顯示類,在J2ME中我們用Display和Canvas來實現這些,而在Android中涉及到顯示的為View類,Android遊戲開發中比較重要和複雜的就是顯示和遊戲邏輯的處理。那麼,我們首先研究顯示的問題。

 

首先建立一個遊戲主戰場:GameView 類,並繼承View類,結構如下:

[java] view plaincopy
  1. package cn.zkyc.android.game;  
  2.   
  3. import android.content.Context;  
  4. import android.view.View;  
  5.   
  6. public class GameView extends View {  
  7.   
  8.     public GameView(Context context) {  
  9.         super(context);  
  10.     }  
  11.   
  12. }  

接下來我們要將上面建立的GameView類顯示到手機螢幕上。需要在入口Activity中進行呼叫。專案建立的時候我就已經設定了一個主Activity,名稱為:GameStartActivity,程式碼結構如下:


[java] view plaincopy
  1. package cn.zkyc.android.game;  
  2. import android.app.Activity;  
  3. import android.os.Bundle;  
  4. public class GameStartActivity extends Activity {  
  5.     @Override  
  6.     public void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.           
  9.         // 預設是載入XML配置檔案,顯示的也是XML檔案中的檢視元件  
  10.         //setContentView(R.layout.main);  
  11.         //顯示自定義的View,只需要將XML檔案換成自定義的View物件就即可,如下:  
  12.         GameView gameView = new GameView(this);  
  13.         setContentView(gameView);   
  14.     }  
  15. }  

執行Application,效果如下:


很遺憾,頁面中除了title什麼也看不到。實際上,我只是測試自定義View是否能夠正確顯示,只要程式沒有bug,就算是成功。(請看程式碼中的註釋)

 

好的,接下來我們就在View裡面展現一些內容,這個時候就要用到View物件中的onDraw方法,在自定義的GameView物件中必須覆蓋父類View中的onDraw方法。接下來,你想展現任何內容都可以在此方法中進行了。假如,我想在螢幕的(100,100)處繪製藍色文字:“飛機大戰”,在螢幕的(100,200)處繪製一個半徑10畫素的紅色圓。

[java] view plaincopy
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.         super.onDraw(canvas);  
  4. // 畫筆物件,可以控制顏色和文字大小  
  5.         Paint paint = new Paint();  
  6.         // 給畫筆設定系統內建的顏色:藍色  
  7.         paint.setColor(Color.BLUE);   
  8.         // 文字左上角的座標為(100,100)  
  9.         canvas.drawText("飛機大戰",100100, paint);  
  10.           
  11.         //將畫筆顏色調成紅色  
  12.         paint.setColor(Color.RED);   
  13.         // 圓心點座標為(100,200)  
  14.         canvas.drawCircle(10020010, paint);   
  15. }  

執行效果如下圖:



到目前為止,你已經可以在自定義的GameView中繪製文字和各種圖形了,但遊戲中都是大量的圖片素材,對於圖片如何繪製呢?也很簡單,Canvas類也提供了相應的drawBitmap方法。現在,我來繪製螢幕的(100,300)處繪製一個飛機圖片。只需要在ondraw方法中新增如下程式碼即可:

[java] view plaincopy
  1. //載入資源圖片圖片  
  2. Bitmap heroBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.hero1);  
  3. // 在(100,300)處繪製圖片  
  4. canvas.drawBitmap(heroBitmap, 100300, paint);  

執行效果如下圖:


對於程式碼中還有Canvas和Paint兩個類沒有細講,我們可以這樣打個比方吧,假如Canvas是一個畫家,那麼Paint就是畫家手中的筆。畫家能夠畫出各種景象(文字,形狀,貼圖等等),就要用到不同的畫筆和不同的顏色。通過畫筆類Paint就可以調整顏色,字型樣式,字型大小等等。

我們發現繪製貼圖canvas.drawBitmap(heroBitmap, 100, 300, paint);也會用到paint物件,但實際上paint起到的作用不大,我們完全可以忽略。

這種寫法也是對的:canvas.drawBitmap(heroBitmap, 100, 300, null);

具體的用法程式碼裡面已經有了,我就不再多說了,大家可以親自查詢下Android SDK API。


四、Thread:讓畫面動起來

 

上面我們已經實現了自定義的View中繪製了文字、形狀和貼圖,但是一切都是靜止的,跟遊戲還差的很遠,意義不是很大。那麼,接下來我就帶著大家一起來讓畫面動起來。

 

實現這個目標,我們需要用到一個在遊戲開發中非常重要的機制,就是多執行緒機制。具體多執行緒實現方式,不是我們現在討論的問題,如果還不是很明白就需要自己補補執行緒方面的知識了。

 

在這裡我們採用GameView類直接實現Runnable介面的方式:

[java] view plaincopy
  1. public class GameView extends View implements Runnable { }  

預設必須實現run方法:

[java] view plaincopy
  1. public void run() {  
  2.     while(true){  
  3.         // 不斷的呼叫View中的postInvalidate方法,讓介面重新繪製  
  4.         this.postInvalidate();  
  5.         try {  
  6.             // 暫停0.5秒繼續  
  7.             Thread.sleep(500);  
  8.         } catch (InterruptedException e) {  
  9.             e.printStackTrace();  
  10.         }  
  11.     }  
  12. }  

postInvalidate() :此方法是View類中的方法,功能是觸發呼叫onDraw方法實現介面重繪。

只要在每次重繪之前對層中物件的位置、形狀、顏色或者透明度進行修改, 而且在一秒鐘之內完成幾十次的重繪,人的眼睛根本無法分辨,所以流暢的動畫效果就產生了。動畫片和電影也是這個原理。

現在我想讓上面場景中的小球每隔0.5秒鐘改變一次透明度和顏色,飛機垂直向上飛行10dp,效果如下:


完整的程式碼如下:

[java] view plaincopy
  1. package cn.zkyc.android.game;  
  2.   
  3. import java.util.Random;  
  4.   
  5. import android.content.Context;  
  6. import android.graphics.Bitmap;  
  7. import android.graphics.BitmapFactory;  
  8. import android.graphics.Canvas;  
  9. import android.graphics.Color;  
  10. import android.graphics.Paint;  
  11. import android.view.View;  
  12.   
  13. public class GameView extends View implements Runnable{  
  14.   
  15.     Random rand = new Random();   
  16.     private int dx = 0 ;   // x軸移動畫素  
  17.     private int dy = 0 ;   // y軸移動畫素  
  18.     /** 
  19.      * 必須要覆蓋View中一個構造方法 
  20.      * @param context 
  21.      */  
  22.     public GameView(Context context) {  
  23.         super(context);  
  24.           
  25.         //啟動執行緒  
  26.         new Thread(this).start();  
  27.     }  
  28.   
  29.     @Override  
  30.     protected void onDraw(Canvas canvas) {  
  31.         super.onDraw(canvas);  
  32.         // 畫筆物件,可以控制顏色和文字大小  
  33.         Paint paint = new Paint();  
  34.           
  35.         // 給畫筆設定系統內建的顏色:藍色  
  36.         paint.setColor(Color.BLUE);   
  37.         // 文字左上角的座標為(100,100)  
  38.         canvas.drawText("飛機大戰",100100, paint);  
  39.           
  40.         //將畫筆顏色調成紅色  
  41.         //paint.setColor(Color.RED);   
  42.         //ARGB : A:透明度 、R:紅色、G:綠色、B:藍色。取值範圍都在:0 ~ 255  
  43.         paint.setARGB(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255), rand.nextInt(255));  
  44.         // 圓心點座標為(100,200)  
  45.         canvas.drawCircle(10020010, paint);   
  46.           
  47.         //載入資源圖片圖片  
  48.         Bitmap heroBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.hero1);  
  49.         // 在(100,300)處繪製圖片,dy值控制在y軸上的位置  
  50.         canvas.drawBitmap(heroBitmap, 100300+dy, null);  
  51.     }  
  52.   
  53.     @Override  
  54.     public void run() {  
  55.         while(true){  
  56.             //修改下dy的座標,方向向上,所以dy不斷減,每次上移10dp  
  57.             dy -= 10;   
  58.             // 不斷的呼叫View中的postInvalidate方法,讓介面重新繪製  
  59.             this.postInvalidate();  
  60.             //this.invalidate(); 此方法要求在UI主執行緒呼叫  
  61.             try {  
  62.                 // 暫停0.5秒繼續  
  63.                 Thread.sleep(500);  
  64.             } catch (InterruptedException e) {  
  65.                 // TODO Auto-generated catch block  
  66.                 e.printStackTrace();  
  67.             }  
  68.         }  
  69.     }  
  70. }  

到此,實際上我們已經完成了我們的任務,但是並沒有考慮到系統執行效率等問題。下面我將帶著大家學習一下高效且更適合做遊戲開發的SurfaceView類。

 

五、SurfaceView物件

 

Surfaceview類是View類的一個子類,我們來看看API的層級關係:



1、SurfaceView的特點

可以在主執行緒之外的執行緒中向螢幕繪圖上。這樣可以避免畫圖任務繁重的時候造成主執行緒阻塞,從而提高了程式的反應速度

 

2、實現方式

定義一個遊戲場景類繼承SurefaceView ,同事實現SurfaceHolder.Callback介面。因為使用SurfaceView有一個原則,所有的繪圖工作必須在Surface 被建立之後才能開始(Surface這個概念在 圖形程式設計中常常被提到,基本上我們可以把它當作視訊記憶體的一個對映,寫入到Surface 的內容可以被直接複製到視訊記憶體從而顯示出來,這使得顯示速度會非常快),而在Surface 被銷燬之前必須結束。所以Callback 中的surfaceCreated 和surfaceDestroyed 就成了繪圖處理程式碼的邊界。

 

3、需要重寫的幾個方法:

//在surface的大小發生改變時激發

1)  public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}

//在建立時激發,一般在這裡呼叫畫圖的執行緒。

2)  public void surfaceCreated(SurfaceHolder holder){} 

//銷燬時激發,一般在這裡將畫圖的執行緒停止、釋放。

3)  public void surfaceDestroyed(SurfaceHolder holder) {}

 

4、整個程式碼過程邏輯:

-->繼承SurfaceView並實現SurfaceHolder.Callback介面 

--> SurfaceView.getHolder()獲得SurfaceHolder物件 

-->SurfaceHolder.addCallback(callback) 新增回撥函式

-->SurfaceHolder.lockCanvas()獲得Canvas物件並鎖定畫布

--> Canvas繪畫 

-->SurfaceHolder.unlockCanvasAndPost(Canvas canvas)結束鎖定,並提交改變,將圖形顯示。

 

5、SurfaceHolder

這裡用到了一個類SurfaceHolder,可以把它當成surface的控制器,用來操縱surface。處理Canvas上的效果和動畫,控制表面,大小,畫素等。
幾個需要注意的方法:

    // 給SurfaceView當前的持有者一個回撥物件。
1)  abstract void addCallback(SurfaceHolder.Callback callback);

// 鎖定畫布,一般在鎖定後就可以通過其返回的畫布物件Canvas,在其上面畫圖等操作了。
2)  abstract Canvas lockCanvas();
// 鎖定畫布的某個區域進行畫圖等,因為畫完圖後,會呼叫下面的unlockCanvasAndPost來改變顯示內容。
// 相對部分記憶體要求比較高的遊戲來說,可以不用重畫dirty外的其它區域的畫素,可以提高速度。

3)  abstract Canvas lockCanvas(Rect dirty);

// 結束鎖定畫圖,並提交改變。
4)  abstract void unlockCanvasAndPost(Canvas canvas);

6、我們把上面View的實現功能改為SurfaceView來重新實現

[java] view plaincopy
  1. package cn.zkyc.android.game;  
  2.   
  3. import java.util.Random;  
  4.   
  5. import android.content.Context;  
  6. import android.graphics.Bitmap;  
  7. import android.graphics.BitmapFactory;  
  8. import android.graphics.Canvas;  
  9. import android.graphics.Color;  
  10. import android.graphics.Paint;  
  11. import android.view.SurfaceHolder;  
  12. import android.view.SurfaceHolder.Callback;  
  13. import android.view.SurfaceView;  
  14.   
  15. public class GameSFView extends SurfaceView implements Callback, Runnable {  
  16.   
  17.     private SurfaceHolder surfaceHolder;  
  18.     private Random rand = new Random();  
  19.     private int dx = 0, dy = 0;  
  20.   
  21.       
  22.     public GameSFView(Context context) {  
  23.         super(context);  
  24.         surfaceHolder = this.getHolder(); // 獲取SurfaceHolder物件  
  25.         surfaceHolder.addCallback(this); // 新增回撥  
  26.     }  
  27.   
  28.     @Override  
  29.     public void run() {  
  30.           
  31.         while(true){  
  32.             dy -= 10//修改下dy的座標,方向向上,所以dy不斷減,每次上移10dp  
  33.             draw(); //呼叫重繪  
  34.             try {  
  35.                 Thread.sleep(500);  
  36.             } catch (InterruptedException e) {  
  37.                 // TODO Auto-generated catch block  
  38.                 e.printStackTrace();  
  39.             }  
  40.         }  
  41.     }  
  42.   
  43.     /** 
  44.      * 自定義繪製方法 
  45.      */  
  46.     public void draw() {  
  47.         synchronized(surfaceHolder){  
  48.             // 獲取Canvas物件  
  49.             Canvas canvas = surfaceHolder.lockCanvas(); // 鎖住Canvas  
  50.               
  51.             //清理背景,遊戲中將換成具體的背景貼圖  
  52.             canvas.drawColor(Color.BLACK);  
  53.               
  54.             // 畫筆物件,可以控制顏色和文字大小  
  55.             Paint paint = new Paint();  
  56.   
  57.             // 給畫筆設定系統內建的顏色:藍色  
  58.             paint.setColor(Color.BLUE);  
  59.             // 文字左上角的座標為(100,100)  
  60.             canvas.drawText("飛機大戰"100100, paint);  
  61.   
  62.             // 將畫筆顏色調成紅色  
  63.             // paint.setColor(Color.RED);  
  64.             // ARGB : A:透明度 、R:紅色、G:綠色、B:藍色。取值範圍都在:0 ~ 255  
  65.             paint.setARGB(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255),rand.nextInt(255));  
  66.             // 圓心點座標為(100,200)  
  67.             canvas.drawCircle(10020010, paint);  
  68.   
  69.             // 載入資源圖片圖片  
  70.             Bitmap heroBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.hero1);  
  71.             // 在(100,300)處繪製圖片,dy值控制在y軸上的位置  
  72.             canvas.drawBitmap(heroBitmap, 100300 + dy, null);  
  73.               
  74.             surfaceHolder.unlockCanvasAndPost(canvas);  // 解鎖Canvas,更新  
  75.         }  
  76.           
  77.     }  
  78.   
  79.     @Override  
  80.     public void surfaceChanged(SurfaceHolder holder, int arg1, int arg2,int arg3) {  
  81.           
  82.     }  
  83.   
  84.     @Override  
  85.     public void surfaceCreated(SurfaceHolder holder) {  
  86.         // Surface建立成功啟動執行緒  
  87.         new Thread(this).start();  
  88.     }  
  89.   
  90.     @Override  
  91.     public void surfaceDestroyed(SurfaceHolder holder) {  
  92.           
  93.     }  
  94.   
  95. }  

大家發現,飛機移動到頂部之後就不見了,請大家思考,如何讓飛機飛過頂部之後還能從底部出來呢?

 

下一講,我將帶著大家實現更炫的遊戲效果,敬請期待吧?

 

原始碼下載:

CSDNhttp://download.csdn.net/detail/jackome/6383395

百度雲盤:http://pan.baidu.com/s/1BW2qK

相關文章