標籤:flabby bird surfaceview 遊戲
轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/42965779 ,本文出自:【張鴻洋的部落格】1、概述
哈,記得以前寫過Android SurfaceView實戰 打造抽獎轉盤 , 同屬於SurfaceView系列,基本可以從這篇博文中學習到SurfaceView的用法,以及利用SurfaceView做抽獎轉盤。但是其中缺少一部分的知識點,就是與使用者互動時介面的改變,所以今天給大家再帶來本篇博文教大家如何做flabby bird這款遊戲,這遊戲雖然不難,但是也為其作者掙了不少錢,大家在學會以後,可以儘可能發揮自己的創意,做屬於自己的遊戲,說不定下一個火的奏是你。
ok,那麼首先上下:
再來張動態:
由於上傳圖片最大限制為2M,所以做了壓縮處理,湊合看吧 ~~~
2、分析
仔細觀察遊戲,需要繪製的有:背景、地板、鳥、管道、分數;
遊戲開始時:
地板給人一種想左移動的感覺;
管道與地板同樣的速度向左移動;
鳥預設下落;
當使用者touch螢幕時,鳥上升一段距離後,下落;
運動過程中需要判斷管道和鳥之間的位置關係,是否觸碰,是否穿過等,需要計算分數。
好了,大概就這麼多,那我們首先開始考慮繪製~~~
3、SurfaceView的一般寫法
接下來,我們首先編寫下SurfaceView的一般寫法:
package com.zhy.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.PixelFormat;import android.util.AttributeSet;import android.view.SurfaceHolder;import android.view.SurfaceHolder.Callback;import android.view.SurfaceView;public class GameFlabbyBird extends SurfaceView implements Callback, Runnable{private SurfaceHolder mHolder;/** * 與SurfaceHolder綁定的Canvas */private Canvas mCanvas;/** * 用於繪製的線程 */private Thread t;/** * 線程的控制開關 */private boolean isRunning;public GameFlabbyBird(Context context){this(context, null);}public GameFlabbyBird(Context context, AttributeSet attrs){super(context, attrs);mHolder = getHolder();mHolder.addCallback(this);setZOrderOnTop(true);// 設定畫布 背景透明mHolder.setFormat(PixelFormat.TRANSLUCENT);// 設定可獲得焦點setFocusable(true);setFocusableInTouchMode(true);// 設定常亮this.setKeepScreenOn(true);}@Overridepublic void surfaceCreated(SurfaceHolder holder){// 開啟線程isRunning = true;t = new Thread(this);t.start();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,int height){// TODO Auto-generated method stub}@Overridepublic void surfaceDestroyed(SurfaceHolder holder){// 通知關閉線程isRunning = false;}@Overridepublic void run(){while (isRunning){long start = System.currentTimeMillis();draw();long end = System.currentTimeMillis();try{if (end - start < 50){Thread.sleep(50 - (end - start));}} catch (InterruptedException e){e.printStackTrace();}}}private void draw(){try{// 獲得canvasmCanvas = mHolder.lockCanvas();if (mCanvas != null){// drawSomething..}} catch (Exception e){} finally{if (mCanvas != null)mHolder.unlockCanvasAndPost(mCanvas);}}}
這個基礎的類,在Android SurfaceView實戰 打造抽獎轉盤已經出現過,就不多說了,大家以後寫SurfaceView的相關程式,可以直接拷貝,在此類基礎上編寫。
4、繪製
1、繪製背景
最簡單的當然是背景了,直接drawBitmap即可。
我們添加需要的成員變數,以及初始化一些參數,然後添加drawBg方法,最後在draw中調用drawBg;
public class CopyOfGameFlabbyBird extends SurfaceView implements Callback,Runnable{/** * 當前View的尺寸 */private int mWidth;private int mHeight;private RectF mGamePanelRect = new RectF();/** * 背景 */private Bitmap mBg;public CopyOfGameFlabbyBird(Context context, AttributeSet attrs){//省略了很多代碼initBitmaps();}/** * 初始化圖片 */private void initBitmaps(){mBg = loadImageByResId(R.drawable.bg1);}private void draw(){//省略了很多代碼drawBg();//省略了很多代碼}/** * 繪製背景 */private void drawBg(){mCanvas.drawBitmap(mBg, null, mGamePanelRect, null);}/** * 初始化尺寸相關 */@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh){super.onSizeChanged(w, h, oldw, oldh);mWidth = w;mHeight = h;mGamePanelRect.set(0, 0, w, h);}/** * 根據resId載入圖片 * * @param resId * @return */private Bitmap loadImageByResId(int resId){return BitmapFactory.decodeResource(getResources(), resId);}}
基本就是新增成員變數,然後初始化,然後繪製,上述代碼經過刪減,貼出的都是與前面基礎代碼不同的部分,大家可以將代碼對號入座進行填充。
好了,現在背景圖繪製好了,接下來,我們繪製小鳥~~~
2、繪製bird
鳥在我們的螢幕中,初始化時需要一個位置,x上,肯定是置中,y上我們取2/3的高度;
關於bird,我們單獨建立一個類:
package com.zhy.view;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.RectF;public class Bird{/** * 鳥在螢幕高度的2/3位置 */private static final float RADIO_POS_HEIGHT = 2 / 3F;/** * 鳥的寬度 30dp */private static final int BIRD_SIZE = 30;/** * 鳥的橫座標 */private int x;/** * 鳥的縱座標 */private int y;/** * 鳥的寬度 */private int mWidth;/** * 鳥的高度 */private int mHeight;/** * 鳥的bitmap */private Bitmap bitmap;/** * 鳥繪製的範圍 */private RectF rect = new RectF();public Bird(Context context, int gameWith, int gameHeight, Bitmap bitmap){this.bitmap = bitmap;//鳥的位置x = gameWith / 2 - bitmap.getWidth() / 2;y = (int) (gameHeight * RADIO_POS_HEIGHT);// 計算鳥的寬度和高度mWidth = Util.dp2px(context, BIRD_SIZE);mHeight = (int) (mWidth * 1.0f / bitmap.getWidth() * bitmap.getHeight());}/** * 繪製自己 * * @param canvas */public void draw(Canvas canvas){rect.set(x, y, x + mWidth, y + mHeight);canvas.drawBitmap(bitmap, null, rect, null);}public int getY(){return y;}public void setY(int y){this.y = y;}public int getWidth(){return mWidth;}public int getHeight(){return mHeight;}}
定義了一個類,代表我們的鳥,以及一堆成員變數,並且提供一個draw方法對外;
在GameFlabbyBird中,只需要,初始化我們的Bird,在draw裡面調用bird.draw即可;
部分篩檢後代碼:
public class CopyOfGameFlabbyBird extends SurfaceView implements Callback,Runnable{/** * *********鳥相關********************** */private Bird mBird;private Bitmap mBirdBitmap;/** * 初始化圖片 */private void initBitmaps(){mBg = loadImageByResId(R.drawable.bg1);mBirdBitmap = loadImageByResId(R.drawable.b1);}private void draw(){// drawSomething..drawBg();drawBird();}private void drawBird(){mBird.draw(mCanvas);}/** * 初始化尺寸相關 */@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh){// 初始化mBirdmBird = new Bird(getContext(), mWidth, mHeight, mBirdBitmap);}}是不是很簡單,下面看下此時:
Activity裡面這麼調用即可:
package com.zhy.surfaceViewDemo;import com.zhy.view.GameFlabbyBird;import android.app.Activity;import android.os.Bundle;import android.view.Window;import android.view.WindowManager;public class MainActivity extends Activity{GameFlabbyBird mGame;@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);requestWindowFeature(Window.FEATURE_NO_TITLE);mGame = new GameFlabbyBird(this);setContentView(mGame);}}
:
不管咋樣,我們的鳥已經在指定的位置了~~~有木有一點小激動~~
下面開始添加地板;
3、繪製地板
繪製地板相比來說會難一點,因為我們需要考慮怎麼讓地板運動,起初我截取了兩個大圖,希望通過兩張圖不斷變化,產生動畫效果,but,動畫的太卡,有跳躍感;
於是,我忽然想到了一個東西可以做,我就把基礎圖變成了這樣:
很小的一塊圖,先不考慮運動,如何填充成我們目標效果呢?
還記得有個類叫做BitmapShader嗎?我們可以利用它進行填充。
相關知識可以參考:Android BitmapShader 實戰 實現圓形、圓角圖片
首先我們依舊是定義一個地板類:Floor
package com.zhy.view;import java.util.concurrent.TimeUnit;import com.zhy.surfaceViewDemo.Config;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapShader;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Paint.Style;import android.graphics.Shader.TileMode;public class Floor{/* * 地板位置遊戲面板高度的4/5到底部 */private static final float FLOOR_Y_POS_RADIO = 4 / 5F; // height of 4/5/** * x座標 */private int x;/** * y座標 */private int y;/** * 填充物 */private BitmapShader mFloorShader;private int mGameWidth;private int mGameHeight;public Floor(int gameWidth, int gameHeight, Bitmap floorBg){mGameWidth = gameWidth;mGameHeight = gameHeight;y = (int) (gameHeight * FLOOR_Y_POS_RADIO);mFloorShader = new BitmapShader(floorBg, TileMode.REPEAT,TileMode.CLAMP);}/** * 繪製自己 * * @param mCanvas * @param mPaint */public void draw(Canvas mCanvas, Paint mPaint){if (-x > mGameWidth){x = x % mGameWidth;}mCanvas.save(Canvas.MATRIX_SAVE_FLAG);//移動到指定的位置mCanvas.translate(x, y);mPaint.setShader(mFloorShader);mCanvas.drawRect(x, 0, -x + mGameWidth, mGameHeight - y, mPaint);mCanvas.restore();mPaint.setShader(null);}public int getX(){return x;}public void setX(int x){this.x = x;}}
定義了一堆成員變數,核心就在於,我們傳入地板背景的填充物,然後初始化我們的mFloorShader,橫向重複,縱向展開(這裡的展開是指,縱向的最後一個像素不斷重複)。
我們對外公布了draw方法,傳入Canvas,我們首先調用canvas.save(),然後將canvas移動到指定的位置,然後繪製我們的矩形,矩形的填充就是我們的地板了~~;
這裡,注意一下,我們這裡使用了一個變數x,而不是0;為什麼呢?因為我們的地板需要利用這個x運動。
那麼現在我們如何才能動呢?
首先我們在GameFlabbyBird定義一個變數,表示移動速度mSpeed,然後在draw中不斷更新mFloor的x座標為:mFloor.setX(mFloor.getX() - mSpeed);
這樣的畫,每次繪製我們floor的起點,會向左移動mSpeed個位置,就形成了啟動並執行效果;但是呢?不能一直減下去,不然最終我們的x豈不是負無窮了,那得繪製多大?
所以我們:
if (-x > mGameWidth)
{
x = x % mGameWidth;
}
如果x的正值大於寬度了,我們取餘一下~~~
最終我們的繪製範圍是:
mCanvas.drawRect(x, 0, -x + mGameWidth, mGameHeight - y, mPaint);
ok,貼下篩檢後GameFlabbyBird代碼:
package com.zhy.view;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.PixelFormat;import android.graphics.RectF;import android.util.AttributeSet;import android.util.Log;import android.view.SurfaceHolder;import android.view.SurfaceHolder.Callback;import android.view.SurfaceView;import com.zhy.surfaceViewDemo.R;public class CopyOfGameFlabbyBird extends SurfaceView implements Callback,Runnable{private Paint mPaint;/** * 地板 */private Floor mFloor;private Bitmap mFloorBg;private int mSpeed;public CopyOfGameFlabbyBird(Context context, AttributeSet attrs){super(context, attrs);mPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setDither(true);initBitmaps();// 初始化速度mSpeed = Util.dp2px(getContext(), 2);}/** * 初始化圖片 */private void initBitmaps(){mFloorBg = loadImageByResId(R.drawable.floor_bg2);}private void draw(){// drawSomething..drawBg();drawBird();drawFloor();// 更新我們地板繪製的x座標mFloor.setX(mFloor.getX() - mSpeed);}private void drawFloor(){mFloor.draw(mCanvas, mPaint);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh){// 初始化地板mFloor = new Floor(mWidth, mHeight, mFloorBg);}}
其實很簡單,就是聲明幾個變數,初始化一下;記得在draw中更新mFloor的x即可。
現在的效果:
好了,最後剩下個管道了~~~
4、繪製管道
然後是寫搞一個管道類Pipe,注意我們的管道分為上下,每個管道的高度可能不同,所以會多一些成員變數;
package com.zhy.view;import java.util.Random;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.RectF;/** * 管道分為上下 * * @author zhy * */public class Pipe{/** * 上下管道間的距離 */private static final float RADIO_BETWEEN_UP_DOWN = 1 / 5F;/** * 上管道的最大高度 */private static final float RADIO_MAX_HEIGHT = 2 / 5F;/** * 上管道的最小高度 */private static final float RADIO_MIN_HEIGHT = 1 / 5F;/** * 管道的橫座標 */private int x;/** * 上管道的高度 */private int height;/** * 上下管道間的距離 */private int margin;/** * 上管道圖片 */private Bitmap mTop;/** * 下管道圖片 */private Bitmap mBottom;private static Random random = new Random();public Pipe(Context context, int gameWidth, int gameHeight, Bitmap top,Bitmap bottom){margin = (int) (gameHeight * RADIO_BETWEEN_UP_DOWN);// 預設從最左邊出現x = gameWidth;mTop = top;mBottom = bottom;randomHeight(gameHeight);}/** * 隨機產生一個高度 */private void randomHeight(int gameHeight){height = random.nextInt((int) (gameHeight * (RADIO_MAX_HEIGHT - RADIO_MIN_HEIGHT)));height = (int) (height + gameHeight * RADIO_MIN_HEIGHT);}public void draw(Canvas mCanvas, RectF rect){mCanvas.save(Canvas.MATRIX_SAVE_FLAG);// rect為整個管道,假設完整管道為100,需要繪製20,則向上位移80mCanvas.translate(x, -(rect.bottom - height));mCanvas.drawBitmap(mTop, null, rect, null);// 下管道,便宜量為,上管道高度+marginmCanvas.translate(0, (rect.bottom - height) + height + margin);mCanvas.drawBitmap(mBottom, null, rect, null);mCanvas.restore();}public int getX(){return x;}public void setX(int x){this.x = x;}}
我們直接看draw方法,我們的傳入的rect是固定的一個矩形,我們的上下管道都是完整的繪製在這個rect中;
然後根據height,去位移canvas的y,讓rect顯示出height部分,主要是因為,這樣可以保證每個管道樣子是一樣的(如果根據height,使用不同的rect,會產生縮放);
Pipe寫好了~~我們需要在GameFlabbyBird中去使用;但是考慮一下,遊戲中的管道不像鳥和地面,有很多個,且是在運行中不斷產生新的~~~
所以我們儲存Pipe最起碼是個List<Pipe>
篩檢後的代碼:
public class GameFlabbyBird extends SurfaceView implements Callback, Runnable{/** * *********管道相關********************** *//** * 管道 */private Bitmap mPipeTop;private Bitmap mPipeBottom;private RectF mPipeRect;private int mPipeWidth;/** * 管道的寬度 60dp */private static final int PIPE_WIDTH = 60;private List<Pipe> mPipes = new ArrayList<Pipe>();public GameFlabbyBird(Context context, AttributeSet attrs){super(context, attrs);mPipeWidth = Util.dp2px(getContext(), PIPE_WIDTH);}/** * 初始化圖片 */private void initBitmaps(){;mPipeTop = loadImageByResId(R.drawable.g2);mPipeBottom = loadImageByResId(R.drawable.g1);}private void draw(){drawBg();drawBird();drawPipes();drawFloor();}/** * 繪製管道 */private void drawPipes(){for (Pipe pipe : mPipes){pipe.setX(pipe.getX() - mSpeed);pipe.draw(mCanvas, mPipeRect);}}/** * 初始化尺寸相關 */@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh){super.onSizeChanged(w, h, oldw, oldh);// 初始化管道範圍mPipeRect = new RectF(0, 0, mPipeWidth, mHeight);Pipe pipe = new Pipe(getContext(), w, h, mPipeTop, mPipeBottom);mPipes.add(pipe);}}
我們在onSizeChanged中初始化了一個Pipe,添加到了mPipes中,然後在draw裡面,動態改變Pipe的x為pipe.setX(pipe.getX() - mSpeed);
下面來看下效果:
我們的管道從右側進入介面,然後消失在左側~
當然了,關於管道還有很多需要編寫,比如管道每隔多遠產生一個,也不能讓無限產生,當管道從介面移除應該從mPipes中移出;
以及判斷管道和鳥的碰撞,這些都放置到下一篇部落格敘述~~
5、繪製分數
分數的繪製比較簡單,我準備了10個圖,對應於0-9
沒有單獨定義類了,直接寫了~~
篩檢後的代碼:
public class GameFlabbyBird extends SurfaceView implements Callback,Runnable{/** * 分數 */private final int[] mNums = new int[] { R.drawable.n0, R.drawable.n1,R.drawable.n2, R.drawable.n3, R.drawable.n4, R.drawable.n5,R.drawable.n6, R.drawable.n7, R.drawable.n8, R.drawable.n9 };private Bitmap[] mNumBitmap;private int mGrade = 100;/** * 單個數位高度的1/15 */private static final float RADIO_SINGLE_NUM_HEIGHT = 1 / 15f;/** * 單個數位寬度 */private int mSingleGradeWidth;/** * 單個數位高度 */private int mSingleGradeHeight;/** * 單個數位範圍 */private RectF mSingleNumRectF;/** * 初始化圖片 */private void initBitmaps(){mNumBitmap = new Bitmap[mNums.length];for (int i = 0; i < mNumBitmap.length; i++){mNumBitmap[i] = loadImageByResId(mNums[i]);}}private void draw(){// drawSomething..drawBg();drawBird();drawPipes();drawFloor();drawGrades();}/** * 繪製分數 */private void drawGrades(){String grade = mGrade + "";mCanvas.save(Canvas.MATRIX_SAVE_FLAG);mCanvas.translate(mWidth / 2 - grade.length() * mSingleGradeWidth / 2,1f / 8 * mHeight);// draw single num one by onefor (int i = 0; i < grade.length(); i++){String numStr = grade.substring(i, i + 1);int num = Integer.valueOf(numStr);mCanvas.drawBitmap(mNumBitmap[num], null, mSingleNumRectF, null);mCanvas.translate(mSingleGradeWidth, 0);}mCanvas.restore();}/** * 初始化尺寸相關 */@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh){super.onSizeChanged(w, h, oldw, oldh);// 初始化分數mSingleGradeHeight = (int) (h * RADIO_SINGLE_NUM_HEIGHT);mSingleGradeWidth = (int) (mSingleGradeHeight * 1.0f/ mNumBitmap[0].getHeight() * mNumBitmap[0].getWidth());mSingleNumRectF = new RectF(0, 0, mSingleGradeWidth, mSingleGradeHeight);}}
我們定義了單個數位範圍,然後假設現在為100分,注意在繪製的時候,直接提取數字,把數字作為下標,找到對於的圖片進行繪製;
繪製前,根據數位位元,對畫布進行位移到中心位置,然後繪製;繪製過程中,每繪製完成一個數字則位移一個數位寬度;
現在的效果:
ok,到此為止,我們完成了所有需要繪製的東西~~由於篇幅原因,下一篇,將在此基礎上完善剩下的所有內容~~~
有興趣的,可以在此基礎上直接嘗試寫了~~~
源碼點擊下載
我建了一個QQ群,方便大家交流。群號:423372824
----------------------------------------------------------------------------------------------------------
博主部分視頻已經上線,如果你不喜歡枯燥的文本,請猛戳(初錄,期待您的支援):
視頻目錄位址:本人錄製的視頻教程
Android SurfaceView實戰 帶你玩轉flabby bird (上)