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);
    }

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

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

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

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM