編輯:Android開發實例
在這一講中,我們介紹如何搭建游戲界面,在游戲界面中加入靜態如片,如何移動游戲場景。
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: 定義畫筆。 擁有了畫布,我們需要一個畫筆在畫布上進行圖畫。
首先要在資源文件中提取圖片文件。
- //背景圖片
- 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);
- }
這樣就能保證屏幕不會超出游戲場景的范圍。
再次運行程序。可以發現,運行正常。
下一章中,我們將要介紹整個游戲的核心部門---》創建游戲世界!
可以顯示在的Android任務,通過加載進度條的進展。進度條有兩種形狀。加載欄和加載微調(spinner)。在本章中,我們將討論微調(spinner)。Spinner 用
Android應用程序可以在許多不同地區的許多設備上運行。為了使應用程序更具交互性,應用程序應該處理以適合應用程序將要使用的語言環境方面的文字,數字,文件等。在本章中,我
Android提供了許多方法來控制播放的音頻/視頻文件和流。其中該方法是通過一類稱為MediaPlayer。Android是提供MediaPlayer類訪問內置的媒體播放
這篇文章主要介紹了Android使用ImageView 制作透明圓弧實例代碼的相關資料,需要的朋友可以參考下 這幾天因為項目需求,需要在