Android基於box2d開發彈弓類遊戲[二]-------------遊戲界面的搭建&移動遊戲場景

前面一講中,我們介紹了,遊戲開發的前期準備與如何創建項目。 

Android基於box2d開發彈弓類遊戲[一]-------------前期準備&創建項目

在這一講中,我們介紹如何搭建遊戲界面,在遊戲界面中加入靜態如片,如何移動遊戲場景。

呼呼呼!!那麼,我們開始吧!

三.創建遊戲界面

Android中用於顯示遊戲界面的視圖,常用的有View和SurfaceView。SurfaceView是從View基類中派生出來的顯示類SurfaceView和View最本質的區別在於,surfaceView是在一個新起的單獨線程中可以重新繪製畫面而View必須在UI的主線程中更新畫面。 

那麼在UI的主線程中更新畫面 可能會引發問題,比如你更新畫面的時間過長,那麼你的主UI線程會被你正在畫的函數阻塞。那麼將無法響應按鍵,觸屏等消息。

當使用surfaceView 由於是在新的線程中更新畫面所以不會阻塞你的UI主線程。但這也帶來了另外一個問題,就是事件同步。比如你觸屏了一下,你需要surfaceView中thread處理,一般就需要有一個event queue的設計來保存touch event,這會稍稍複雜一點,因爲涉及到線程同步。所以基於以上,根據遊戲特點,一般分成兩類。

1 被動更新畫面的。比如棋類,這種用view就好了。因爲畫面的更新是依賴於 onTouch 來更新,可以直接使用 invalidate。 因爲這種情況下,這一次Touch和下一次的Touch需要的時間比較長些,不會產生影響。

2 主動更新。比如一個人在一直跑動。這就需要一個單獨的thread不停的重繪人的狀態,避免阻塞main UI thread。所以顯然view不合適,需要surfaceView來控制。

3.Android中的SurfaceView類就是雙緩衝機制。因此,開發遊戲時儘量使用SurfaceView而不要使用View,這樣的話效率較高,而且SurfaceView的功能也更加完善。

 考慮以上幾點,所以我們選用SurfaceView 來進行遊戲開發。

 下面創建 基於SurfaceView的遊戲界面類MainView.java:

複製代碼
package com.catapultdemo;
 
import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
 
public class MainView extends SurfaceView implements Callback,Runnable {
 
    public MainView(Context context) {
       super(context);
    }
    @Override
    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
       // TODO Auto-generated method stub
      
    }
 
    @Override
    public void surfaceCreated(SurfaceHolder arg0) {
       // TODO Auto-generated method stub
      
    }
 
    @Override
    public void surfaceDestroyed(SurfaceHolder arg0) {
       // TODO Auto-generated method stub
      
    }
 
    @Override
    public void run() {
       // TODO Auto-generated method stub
      
    }
 
}
複製代碼

只要繼承SurfaceView類並實現SurfaceHolder.Callback接口和runnable接口就可以實現一個自定義的SurfaceView了。

SurfaceHolder.Callback在底層的Surface狀態發生變化的時候通知View。

Runnable用來實現多線程

 遊戲界面已經搭建完成,下面要做的就是讓項目啓動之後,顯示我們的遊戲界面,也就是MainView。

 自定義draw方法,用後之後話界面使用。爲什麼要實現這個方法,會在第四節進行說明。

public void draw()
{}

現在可以運行一下項目,看一下運行結果。

可能與想象的有些差別。因爲android項目創建完成之後,默認創建一個layout佈局文件,用於初始佈局。所以我們可以刪除這個佈局文件。

刪除佈局文件之後項目主文件MainActivity.java會報錯。因爲,默認情況下,向界面輸出剛剛刪除的佈局文件,然後佈局文件已經被我們刪除了。

複製代碼
package com.catapultdemo;
 
import android.os.Bundle;
import android.app.Activity;
 
public class MainActivity extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
複製代碼

此時將setContentView(R.layout.activity_main);改成        setContentView(new MainView(this));

此行代碼的意思是,讓程序運行,向界面輸出我們的遊戲場景界面。接下來再一次運行程序。

我們刪除了佈局文件後,顯示出了我們的遊戲場景,中間的“hello world”也已經消失的,但是與我們的想象的結果還是有些差距。我們接下來需要去除頭部的狀態欄和應用程序的名稱欄。還要試遊戲屏幕變成橫屏。

複製代碼
// 隱去電池等圖標和一切修飾部分(狀態欄部分)
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
// 隱去標題欄(程序的名字)
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
// 遊戲界面橫屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
複製代碼

此時屏幕顯示界面已經全部變黑了。此時我們的遊戲界面搭建完成了。

四.在遊戲場景中加入靜態圖片

現在遊戲界面還沒有任何的東西。接下來,我們在遊戲場景中加入背景圖片,和一些靜態的物體。由於這些背景和靜態的物體不需要模擬物理場景,所以,之需要在遊戲場景中畫出圖片即可。

此前已經創建了繼承自SurfaceView的MainView.java遊戲界面類。接下來對方法進行完善和介紹。

 

實現了CallBack接口,重寫了一下方法。

public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){} 
//看其名知其義,在surface的大小發生改變時激發 
public void surfaceCreated(SurfaceHolder holder){} 
//同上,在創建時激發,一般在這裏調用畫圖的線程。 
public void surfaceDestroyed(SurfaceHolder holder) {} 
//同上,銷燬時激發,一般在這裏將畫圖的線程停止、釋放。 

實現了Runnable接口,重寫了run方法。下面介紹一下爲什麼要實現Runnable接口並且要重寫run方法:surfaceView有onDraw方法,但是surfaceView不會自己去調用這個方法,所以我們要自己實現 draw方法,並放在run方法內。Runnable實現線程,run方法就是在開闢的線程中無限的去執行。所以我們自己完成的draw方法也可以不斷的執行。這個就是刷屏。

在類中定義一些必要的變量。

複製代碼
private Resources res;
private SurfaceHolder sfh;   
private Thread th;   
private Canvas canvas;   
private Paint paint; 
 
public MainView(Context context) {   
        super(context);
        res = this.getResources();
        sfh = this.getHolder();   
        sfh.addCallback(this);  
        paint = new Paint();   
        paint.setAntiAlias(true);   
        paint.setColor(Color.RED);
this.setKeepScreenOn(true);// 保持屏幕常亮  
} 
複製代碼

Resources資源變量。可以通過this.getResources()獲取項目中的資源。

 

SurfaceHolder: 它是一個用於控制surface的接口,它提供了控制surface 的大小,格式,上面的像素,即監視其改變的。SurfaceView的getHolder()函數可以獲取SurfaceHolder對象,Surface 就在SurfaceHolder對象內。雖然Surface保存了當前窗口的像素數據,但是在使用過程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或則Canvas lockCanvas()函數來獲取Canvas對象,通過在Canvas上繪製內容來修改Surface中的數據。如果Surface不可編輯或則尚未創建調用該函數會返回null,在 unlockCanvas() 和 lockCanvas()中Surface的內容是不緩存的,所以需要完全重繪Surface的內容,爲了提高效率只重繪變化的部分則可以調用lockCanvas(Rect rect)函數來指定一個rect區域,這樣該區域外的內容會緩存起來。在調用lockCanvas函數獲取Canvas後,SurfaceView會獲取Surface的一個同步鎖直到調用unlockCanvasAndPost(Canvas canvas)函數才釋放該鎖,這裏的同步機制保證在Surface繪製過程中不會被改變(被摧毀、修改)。

Thread:定義線程

Canvas: 定義遊戲展示的平臺,也就是一個畫布。所有的遊戲界面將會在畫布上展示。

Paint: 定義畫筆。 擁有了畫布,我們需要一個畫筆在畫布上進行圖畫。

1.     場景中加入背景

首先要在資源文件中提取圖片文件。

複製代碼
//背景圖片
background_top = BitmapFactory.decodeResource(res, R.drawable.bg);
background_bottom = BitmapFactory.decodeResource(res, R.drawable.fg);
//兩個松鼠圖片
squirrel_1 = BitmapFactory.decodeResource(res, R.drawable.squirrel_1);
squirrel_2 = BitmapFactory.decodeResource(res, R.drawable.squirrel_2);
//發射器底座圖片
catapult_base_1 = BitmapFactory.decodeResource(res, R.drawable.catapult_base_1);
catapult_base_2=BitmapFactory.decodeResource(res,R.drawable.catapult_base_2);
 
加載了圖片文件之後,定義一個常量FLOOR_HEIGHT。這個是地面的高度,爲了能夠更精確的擺放物體。這個高度就是手機屏幕下邊緣到遊戲中模擬的地圖的高度。
private static final float FLOOR_HEIGHT =82f;
 
接下來還要定義屏幕的高和寬。這個高和寬指的是手機屏幕可見區域的高和寬,並不是遊戲場景中的高和寬。請注意。
ScreenW = this.getWidth(); 
ScreenH = this.getHeight(); 
 
接下來在canvas上畫出這些圖片。
public void surfaceCreated(SurfaceHolder arg0) {
       ScreenH = this.getHeight();
       ScreenW = this.getWidth();
       thread_flag = true;
       th = new Thread(this); // 創建線程
       th.start();   //開啓線程
    }
 
此時需要注意。一定要把th.start()開啓線程這段代碼放到surfaceCreated最後,否則會出現啓動自動退出的bug.
 
private void draw() {   
        try {   
        canvas = sfh.lockCanvas(); // 得到一個canvas實例   
        if (canvas != null) {
                canvas.drawColor(Color.WHITE);// 刷屏
                canvas.drawBitmap(background_top, 0-w/2, 0, paint);
               
                canvas.drawBitmap(catapult_base_2,260-w,ScreenH-FLOOR_HEIGHT-catapult_base_2.getHeight()-catapult_base_2.getHeight()/4,paint);
                canvas.drawBitmap(catapult_base_1,265-w,ScreenH-FLOOR_HEIGHT-catapult_base_1.getHeight()-catapult_base_1.getHeight()/4,paint);
               
                canvas.drawBitmap(squirrel_1, 50-w, ScreenH-FLOOR_HEIGHT-squirrel_1.getHeight(), paint);
                canvas.drawBitmap(squirrel_2, 350-w, ScreenH-FLOOR_HEIGHT-squirrel_2.getHeight(), paint);
               
                canvas.drawBitmap(background_bottom, 0-w, ScreenH-background_bottom.getHeight(), paint);
         }
        } catch (Exception ex) {   
        } finally {  
            if (canvas != null)
                sfh.unlockCanvasAndPost(canvas);  // 將畫好的畫布提交   
        }   
}  
複製代碼

此時運行程序。就可以看到我們的遊戲場景了。但是現實的都是非物理模擬部分。

此時,是不是有些小小的興奮。。。。但是不要怪我潑涼水。現在我們只是把一些圖片拼湊在了一起。其他的什麼都沒有呢。手指滑動屏幕也沒有任何反應。

接下來我們使遊戲場景進行移動。

五.移動場景

現在的遊戲運行之後,只能顯示一半的場景,接下來實現用手指滑動屏幕移動場景。我們要在MainView.java主類中,複寫View中的onTouchEvent方法。此方法用於檢測觸摸屏事件。

複製代碼
@Override
    public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
    }
然後分別在onTouchEvent方法中,實現觸屏 按下,擡起,移動事件。
public boolean onTouchEvent(MotionEvent event) {
       if(event.getAction() == MotionEvent.ACTION_DOWN)
       {}
else if(event.getAction() == MotionEvent.ACTION_UP)
       {}
else if(event.getAction() == MotionEvent.ACTION_MOVE)
       {}
       return super.onTouchEvent(event);
    }
複製代碼

還需要定義兩個變量。position_X是當按下觸摸屏時,觸摸點在當前遊戲場景中的x軸位置,move_X是移動觸摸屏時,移動的偏移量。

private float position_X;
private float move_X;

當按下觸摸屏時計算當前的場景中的位置。event.getX()是獲取觸摸點的x軸座標,這個是相對於屏幕的座標,所以我們還需要加上move_X偏移量,這樣就能獲取當前觸摸點在遊戲場景中的位置。

複製代碼
if(event.getAction() == MotionEvent.ACTION_DOWN)
    {
       position_X =move_X+ event.getX();
    }
接下來就完成當手指移動時,move_X偏移量的值。偏移量應該是按下手指時的位置與移動時當前位置的差。
else if(event.getAction() == MotionEvent.ACTION_MOVE)
    {
       move_X = position_X-event.getX();
    }
複製代碼

得到了偏移量我之需要在draw方法中,畫遊戲界面時對每張圖片的x軸位置進行改變。這樣就能屏幕移動的效果。

例如話背景頭部圖片時,x軸的位置減去偏移量的位置就是移動之後的位置。其他圖片方法一樣。不明白的可以查看第二節,Android遊戲座標系一節。

canvas.drawBitmap(background_top, 0-move_X, 0, paint);

接下來我們可以運行程序查看一下效果。

此時的運行效果可能會很失望,移動觸屏,遊戲場景並沒有進行移動。原因是我們只是實現了觸屏方法,但是當前surfaceview不允許對觸屏進行點擊。所以我們需要在MainView構造方法中,添加以下代碼。

setClickable(true);

此時再次運行程序。遊戲界面可以進行移動了。

但是經過測試會發現,當移動場景的時候,可能會超出整個遊戲場景。如下圖。

這個bug是在有些尷尬啊!!

這是由於我們沒有爲偏移量進行限制的原因。

0<Move_X<遊戲場景寬度 – 屏幕寬度

遊戲場景的寬度也就是背景圖片的寬度。定義一個變量 gameWidth,它的值爲背景圖片的寬度。

複製代碼
private float gameWidth;
gameWidth = background_top.getWidth();
 
在觸屏移動時對move_X進行限制。
else if(event.getAction() == MotionEvent.ACTION_MOVE)
    {
       move_X = position_X-event.getX();
       move_X = move_X<0?0:(move_X>gameWidth-ScreenW?gameWidth-ScreenW:move_X);
    }
複製代碼

這樣就能保證屏幕不會超出遊戲場景的範圍。

再次運行程序。可以發現,運行正常。

下一章中,我們將要介紹整個遊戲的核心部門---》創建遊戲世界!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章