Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 模仿flabby bird游戲開發

Android 模仿flabby bird游戲開發

編輯:關於Android編程

一、示意圖:

1)開始畫面:
這裡寫圖片描述
2)游戲中畫面:
這裡寫圖片描述
3)結束畫面:
這裡寫圖片描述

二、分析:

1、游戲中的每個元素都可封裝成對象,
1)開始按鈕與結束按鈕可封裝成GameButton對象:
屬性有:有坐標x,y;有原圖與按下後的圖片;另外還有判斷是否點擊了的屬性
方法有:draw方法,用來繪制自己; isClick判斷是否被點擊了
另外提供點擊的監聽事件OnButtonClickListener
2)Bird對象:
屬性有圖片,坐標,位置,大小等
方法有draw方法,resetHeigt方法(用於在游戲結束後恢復其高度)
3)Floor地板對象:
屬性有:坐標,BitmapShader填充物
方法有:draw方法,游戲運行時不斷繪制,看起來想不斷的移動
4)Grade分數對象
屬性有:分數圖片,寬高,單個分數的矩陣
方法有:draw方法,繪制分數從左到右繪制,每繪制一個分數,移動到下個分數,寬度是單個分數的寬度
5)管道對象
屬性有:上管道高度,上管道與下管道之間的距離,圖片
方法有:draw方法,根據隨機數繪制管道;touchBird方法,判斷小鳥是否觸碰到了管道

2、游戲繪制在SurfaceView界面上
1)創建類FlyBirdView並繼承SurfaceView 實現接口Callback, Runnable
2)在子線程裡繪制繪制上面的對象
3)在onSizeChanged方法裡初始化所有的對象,因為在這個方法裡控件的寬高固定了下來
4)在構造函數裡初始化圖片等基本屬性
3、除了繪制之外,游戲是有狀態的,一般來說,游戲有三種狀態:等待狀態、運行狀態和結束狀態
在這裡我們使用emum來設值,並且進入游戲時默認是等待狀態
1)在等待狀態裡最主要繪制開始按鈕
3)運行狀態主要是對管道、地板等對象的不斷繪制
3)結束狀態繪制gameovew和重新開始的按鈕

三、實體類代碼:

1)Bird類:

/**
 * 鳥的實體類
 * 
 * @Project App_View
 * @Package com.android.view.flybird
 * @author chenlin
 * @version 1.0
 * @Date 2014年5月6日
 */
public class Bird {
    public static final float RADIO_POS_HEIGHT = 1 / 3f;// 鳥所在的默認屏幕高度
    private static final int BIRD_SIZE = 30; // 鳥的寬度 30dp
    private Bitmap mBirdBitmap;// 鳥圖片
    private int mHeight;// 鳥高度
    private int mWidth;// 鳥寬度
    private RectF mBirdRectF; // 鳥所在的范圍
    private int x, y;// 所在坐標
    private int mGameHeight;

    public Bird(Context context, Bitmap bitmap, int gameWidth, int gameHeight) {
        this.mBirdBitmap = bitmap;
        this.mWidth = UITools.dip2px(context, BIRD_SIZE);
        this.mHeight = (int) (mWidth * 1.0f / bitmap.getWidth() * bitmap.getHeight());
        // 給坐標賦值
        this.x = gameWidth / 2 - bitmap.getWidth() / 2;
        this.y = (int) (gameHeight * RADIO_POS_HEIGHT);
        this.mBirdRectF = new RectF();

        this.mGameHeight = gameHeight;
    }

    /**
     * 繪制鳥
     * 
     * @param canvas
     */
    public void draw(Canvas canvas) {
        mBirdRectF.set(x, y, x + mWidth, y + mHeight);
        canvas.drawBitmap(mBirdBitmap, null, mBirdRectF, null);
    }

    public void resetHeigt() {
        y = (int) (mGameHeight * RADIO_POS_HEIGHT);
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getWidth() {
        return mWidth;
    }

    public int getHeight() {
        return mHeight;
    }

}

2)Floor類

/**
 * 地板
 * 
 * @Project App_View
 * @Package com.android.view.flybird
 * @author chenlin
 * @version 1.0
 * @Date 2014年5月6日
 */
public class Floor {
    // 地板位置游戲面板高度的4/5到底部
    private static final float FLOOR_Y_POS_RADIO = 4 / 5F; // height of 4/5
    private int x, y;// 坐標
    private BitmapShader mBitmapShader;// 填充物
    private int mGameWidth;// 地板寬高
    private int mGameHeight;

    public Floor(int gameWidth, int gameHeight, Bitmap bgBitmap) {
        this.mGameHeight = gameHeight;
        this.mGameWidth = gameWidth;
        this.y = (int) (mGameHeight * FLOOR_Y_POS_RADIO);
        mBitmapShader = new BitmapShader(bgBitmap, TileMode.CLAMP, TileMode.CLAMP);
    }

    /**
     * 繪制自己
     * 
     * @param canvas
     */
    public void draw(Canvas canvas, Paint paint) {
        // 進行平移,如果移出的部分超過屏幕的寬度,就重新讓坐標移動到源位置
        if (-x > mGameWidth) {
            x = x % mGameWidth;
        }
        /**
         * save() : 用來保存Canvas的狀態,save()方法之後的代碼,可以調用Canvas的平移、放縮、旋轉、裁剪等操作!
         */
        canvas.save(Canvas.MATRIX_SAVE_FLAG);
        //平移到指定位置
        canvas.translate(x, y);
        paint.setShader(mBitmapShader);
        canvas.drawRect(x, 0, -x + mGameWidth, mGameHeight - y, paint);
        /**
         * restore():用來恢復Canvas之前保存的狀態(可以想成是保存坐標軸的狀態),防止save()方法代碼之後對Canvas執行的操作,繼續對後續的繪制會產生影響,通過該方法可以避免連帶的影響
         */
        canvas.restore();
        paint.setShader(null);
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }
}

3)GameButton類

/**
 * 開始按鈕
 * @Project    App_View
 * @Package    com.android.view.flybird
 * @author     chenlin
 * @version    1.0
 * @Date       2014年5月19日
 */
public class GameButton {

    private int x;//所在坐標
    private int y;
    private Bitmap mBitmap;//原來按鈕圖片
    private Bitmap mPressBitmap;//按下的按鈕圖片
    private RectF mRectF; // 按鈕所在的范圍
    private boolean isClick = false;//判斷是否被點擊了

    public GameButton(Bitmap bitmap, Bitmap pressBitmap, int gameWidth, int gameHeight){
        this.mBitmap = bitmap;
        this.mPressBitmap = pressBitmap;
        this.x = gameWidth/2-mBitmap.getWidth()/2;//左邊距
        this.y = gameHeight;//初始的位置在屏幕最下端
        this.mRectF = new RectF();
    }

    /**
     * 繪制自己
     * @param canvas
     */
    public void draw(Canvas canvas){
        canvas.save(Canvas.MATRIX_SAVE_FLAG);
        mRectF.set(x, y, x + mBitmap.getWidth(), y + mBitmap.getHeight());
        if (isClick) {
            canvas.drawBitmap(mBitmap, null , mRectF, null);
        }else {
            canvas.drawBitmap(mPressBitmap, null , mRectF, null);
        }
        canvas.restore();
    }

    /**
     * 判斷按鈕是否可點擊
     * @return
     */
    public boolean isClick(int newX, int newY) {
        Rect rect = new Rect(x, y, x + mPressBitmap.getWidth(), y + mPressBitmap.getHeight());
        isClick = rect.contains(newX, newY);
        return isClick;
    }


    public void setClick(boolean isClick) {
        this.isClick = isClick;
    }

    /**
     * 提供向外的點擊事件
     */
    public void click(){
        if (mListener != null) {
            mListener.click();
        }
    }


    private OnButtonClickListener mListener;
    public interface OnButtonClickListener{
        void click();
    }

    public void setOnButtonClickListener(OnButtonClickListener listener){
        this.mListener = listener;
    }


    public int getX() {
        return x;
    }
    public void setX(int x) {
        this.x = x;
    }
    public int getY() {
        return y;
    }
    public void setY(int y) {
        this.y = y;
    }


}

4)分數Grade類:

/**
 * 分數
 * 
 * @Project App_View
 * @Package com.android.view.flybird
 * @author chenlin
 * @version 1.0
 * @Date 2016年5月16日
 */
public class Grade {
    private Bitmap[] mNumBitmap;//所有分數的圖片集合
    private RectF mSingleNumRectF;//單個分數的矩陣
    private int mSingleGradeWidth;//單個分數的寬度
    private int mGameWidth;
    private int mGameHeight;

    public Grade(Bitmap[] numBitmap, RectF rectF, int singleGradeWidth, int gameWidth, int gameHeight) {
        this.mNumBitmap = numBitmap;
        this.mSingleNumRectF = rectF;
        this.mSingleGradeWidth = singleGradeWidth;
        this.mGameWidth = gameWidth;
        this.mGameHeight = gameHeight;
    }

    /**
     * 繪制
     * 
     * @param mCanvas, int gameWidth
     */
    public void draw(Canvas canvas, int score) {
        String grade = score + "";
        canvas.save(Canvas.MATRIX_SAVE_FLAG);
        //移動屏幕的中間,1/8的高度
        canvas.translate(mGameWidth / 2 - grade.length() * mSingleGradeWidth / 2, 1f / 8 * mGameHeight);
        // 依次繪制分數
        for (int i = 0; i < grade.length(); i++) {
            //100,先繪制1,
            String numStr = grade.substring(i, i + 1);
            int num = Integer.valueOf(numStr);
            canvas.drawBitmap(mNumBitmap[num], null, mSingleNumRectF, null);
            //移動到下一個分數0
            canvas.translate(mSingleGradeWidth, 0);
        }
        canvas.restore();
    }
}

5)管道類Pipe:

/**
 * 管道實體
 * 
 * @Project App_View
 * @Package com.android.view.flybird
 * @author chenlin
 * @version 1.0
 * @Date 2014年5月7日
 */
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;// 管道x坐標
    private int mTopHeight;// 上管道高度
    private int mMargin;// 上下管道的距離
    private Bitmap mTopBitmap;// 上管道圖片
    private Bitmap mBottomBitmap;// 下管道圖片
    private static Random random = new Random();

    public Pipe(Context context, int gameWidth, int gameHeight, Bitmap topBitmap, Bitmap bottomBitmap) {
        mMargin = (int) (gameHeight * RADIO_BETWEEN_UP_DOWN);
        // 默認從最左邊出現 ,小鳥往前飛時,管道往左移動
        x = gameWidth;
        mTopBitmap = topBitmap;
        mBottomBitmap = bottomBitmap;

        // 高度隨機
        randomHeight(gameHeight);
    }
    /**
     * 隨機生成一個高度
     */
    private void randomHeight(int gameHeight) {
        mTopHeight = random.nextInt((int) (gameHeight * (RADIO_MAX_HEIGHT - RADIO_MIN_HEIGHT)));
        mTopHeight = (int) (mTopHeight + gameHeight * RADIO_MIN_HEIGHT);
    }

    public void draw(Canvas canvas, RectF rect) {
        canvas.save(Canvas.MATRIX_SAVE_FLAG);
        // rect為整個管道,假設完整管道為100,需要繪制20,則向上偏移80 rect.bottom管的實際高度
        canvas.translate(x, -(rect.bottom - mTopHeight));
        // 繪制上管道
        canvas.drawBitmap(mTopBitmap, null, rect, null);
        // 下管道,偏移量為,上管道高度+margin
        canvas.translate(0, rect.bottom + mMargin);
        //canvas.translate(0, mTopHeight + mMargin);
        //繪制下管道
        canvas.drawBitmap(mBottomBitmap, null, rect, null);
        canvas.restore();
    }

    /**
     * 判斷和鳥是否觸碰
     * @param bird
     * @return
     */
    public boolean touchBird(Bird bird){
        /**
         * 如果bird已經觸碰到管道
         */
        if (bird.getX() + bird.getWidth() > x && (bird.getY() < mTopHeight || bird.getY() + bird.getHeight() > mTopHeight + mMargin)) {
            return true;
        }
        return false;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }




}

四、具體實現:

1)我們先從簡單的開始,實現游戲最基本的配置

public class FlyBirdView extends SurfaceView implements Callback, Runnable {
    private SurfaceHolder mHolder;
    // private Thread mThread;
    private ExecutorService mPool;
    private Canvas mCanvas;
    private boolean isRunnging;// 是否運行

    // 二.設置背景
    private Bitmap mBgBitmap;
    //當前View的尺寸
    private int mWidth;
    private int mHeight;
    private RectF mGamePanelRect = new RectF();

    // ----構造函數處理---------------------------------------------
    public FlyBirdView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FlyBirdView(Context context) {
        this(context, null);
    }

    public FlyBirdView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        // -初始化holer-----------------------
        mHolder = getHolder();
        mHolder.addCallback(this);
        setZOrderOnTop(true);
        // 設置畫布 背景透明
        mHolder.setFormat(PixelFormat.TRANSLUCENT);

        // --焦點設置----------------------------
        setFocusable(true);
        // 設置觸屏
        setFocusableInTouchMode(true);
        // 設置常亮
        setKeepScreenOn(true);

        // --背景設置--------------------------------
        mGamePanelRect = new RectF();
        mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bgbird);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mWidth = w;
        mHeight = h;
        mGamePanelRect.set(0, 0, w, h);
        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    public void run() {
        while (isRunnging) {
            long start = System.currentTimeMillis();
            draw();
            long end = System.currentTimeMillis();
            if (start - end < 50) {
                SystemClock.sleep(50 - (start - end));
            }
        }
    }

    private void draw() {
        try {
            if (mHolder != null) {
                mCanvas = mHolder.lockCanvas();

                if (mCanvas != null) {
                    //繪制背景
                    drawBg();  
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (mHolder != null && mCanvas != null) {
                mHolder.unlockCanvasAndPost(mCanvas);
            }

        }

    }

    private void drawBg() {
        mCanvas.drawBitmap(mBgBitmap,null, mGamePanelRect, null);
    }

    // ---callback監聽------------------------------------------------------
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // -線程處理--------------------------
        isRunnging = true;
        mPool = Executors.newFixedThreadPool(5);
        // mThread = new Thread(this);
        // mThread.start();
        mPool.execute(this);
    }

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

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 通知關閉線程
        isRunnging = false;
    }

}

2)在畫布上添加對象

/**
 * 游戲主界面的繪制
 * 
 * @Project App_View
 * @Package com.android.view.flybird
 * @author chenlin
 * @version 1.0
 * @Date 2014年5月7日
 */
public class FlyBirdView1 extends SurfaceView implements Callback, Runnable {
    private SurfaceHolder mHolder;
    // private Thread mThread;
    private ExecutorService mPool;
    private Canvas mCanvas;
    private boolean isRunnging;// 是否運行

    // 二.設置背景
    private Bitmap mBgBitmap;
    // 當前View的尺寸
    private int mWidth;
    private int mHeight;
    private RectF mGamePanelRect = new RectF();

    // 三、設置鳥
    private Bird mBird;
    private Bitmap mBirdBitmap;

    // 四、添加地板
    private Floor mFloor;
    private Bitmap mFloorBitmap;

    // 五、添加管道
    /** 管道的寬度 60dp */
    private static final int PIPE_WIDTH = 60;
    private Pipe mPipe;
    /** 上管道的圖片 */
    private Bitmap mPipeTopBitmap;
    /** 下管道的圖片 */
    private Bitmap mPipeBotBitmap;
    /** 管道的寬度 */
    private int mPipeWidth;
    /** 管道矩陣 */
    private RectF mPipeRectF;
    /** 管道集合 */
    private List mPipeList;

    // 六、添加分數
    /** 分數 */
    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 Grade mGrade;
    /** 分數圖片組 */
    private Bitmap[] mNumBitmap;
    /** 分值 */
    private int mScore = 100;
    /** 單個數字的高度的1/15 */
    private static final float RADIO_SINGLE_NUM_HEIGHT = 1 / 15f;
    /** 單個數字的寬度 */
    private int mSingleGradeWidth;
    /** 單個數字的高度 */
    private int mSingleGradeHeight;
    /** 單個數字的范圍 */
    private RectF mSingleNumRectF;

    // ----構造函數處理---------------------------------------------
    public FlyBirdView1(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public FlyBirdView1(Context context) {
        super(context);
        init();
    }

    public FlyBirdView1(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        // -初始化holer-----------------------
        mHolder = getHolder();
        mHolder.addCallback(this);
        setZOrderOnTop(true);
        // 設置畫布 背景透明
        mHolder.setFormat(PixelFormat.TRANSLUCENT);

        // --焦點設置----------------------------
        setFocusable(true);
        // 設置觸屏
        setFocusableInTouchMode(true);
        // 設置常亮
        setKeepScreenOn(true);

        // --背景設置--------------------------------
        mGamePanelRect = new RectF();
        mBgBitmap = loadImageByResId(R.drawable.bg1);

        // --添加鳥的圖片---
        mBirdBitmap = loadImageByResId(R.drawable.b1);
        // --添加地板---
        mFloorBitmap = loadImageByResId(R.drawable.floor_bg2);

        // --管道的寬度初始化--
        mPipeWidth = UITools.dip2px(getContext(), PIPE_WIDTH);
        // --添加管道圖片--
        mPipeTopBitmap = loadImageByResId(R.drawable.g2);
        mPipeBotBitmap = loadImageByResId(R.drawable.g1);
        mPipeList = new ArrayList();

        // -------------------------------------------------------

        // 初始化分數圖片
        mNumBitmap = new Bitmap[mNums.length];
        for (int i = 0; i < mNums.length; i++) {
            mNumBitmap[i] = loadImageByResId(mNums[i]);
        }
    }

    @Override
    protected 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);

        // 初始化鳥
        mBird = new Bird(getContext(), mBirdBitmap, mWidth, mHeight);
        // 初始化地板
        mFloor = new Floor(mWidth, mHeight, mFloorBitmap);

        // 初始化管道范圍
        mPipeRectF = new RectF(0, 0, mPipeWidth, mHeight);
        // 初始化 管道
        mPipe = new Pipe(getContext(), mWidth, mHeight, mPipeTopBitmap, mPipeBotBitmap);
        mPipeList.add(mPipe);

        // 初始化分數
        mSingleGradeHeight = (int) (h * RADIO_SINGLE_NUM_HEIGHT);// 屏幕的1/15
        mSingleGradeWidth = (int) (mNumBitmap[0].getWidth() * (1.0f * mSingleGradeHeight / mNumBitmap[0].getHeight()));
        mSingleNumRectF = new RectF(0, 0, mSingleGradeWidth, mSingleGradeHeight);
        mGrade = new Grade(mNumBitmap, mSingleNumRectF, mSingleGradeWidth, mWidth, mHeight);
    }

    @Override
    public void run() {
        while (isRunnging) {
            long start = System.currentTimeMillis();
            draw();
            long end = System.currentTimeMillis();
            if (end - start < 50) {
                SystemClock.sleep(50 - (end - start));
            }
        }
    }

    private void draw() {
        try {
            Logger.i("bird", "mHolder==" + mHolder);
            if (mHolder != null) {
                mCanvas = mHolder.lockCanvas();
                Logger.i("bird", "mCanvas==" + mCanvas);

                if (mCanvas != null) {
                    drawBg(); // 繪制背景
                    drawBird();// 繪制鳥
                    drawFloor();// 繪制地板
                    drawPipes();// 繪制管道
                    drawGrades();// 繪制分數
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (mHolder != null && mCanvas != null) {
                mHolder.unlockCanvasAndPost(mCanvas);
            }

        }

    }

    /**
     * 繪制分數
     */
    private void drawGrades() {
        mGrade.draw(mCanvas, mScore);
    }

    private int mSpeed = UITools.dip2px(getContext(), 2);

    private void drawFloor() {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(true);
        mFloor.draw(mCanvas, paint);
        // 更新我們地板繪制的x坐標
        mFloor.setX(mFloor.getX() - mSpeed);
    }

    private void drawBird() {
        mBird.draw(mCanvas);
    }

    private void drawBg() {
        mCanvas.drawBitmap(mBgBitmap, null, mGamePanelRect, null);
    }

    private void drawPipes() {
        for (Pipe pipe : mPipeList) {
            // 先設定x坐標
            pipe.setX(pipe.getX() - mSpeed);
            pipe.draw(mCanvas, mPipeRectF);
        }
    }

    // ---callback監聽------------------------------------------------------
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // -線程處理--------------------------
        isRunnging = true;
        mPool = Executors.newFixedThreadPool(5);
        mPool.execute(this);
    }

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

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 通知關閉線程
        isRunnging = false;
    }

    /**
     * 根據resId加載圖片
     * 
     * @param resId
     * @return
     */
    private Bitmap loadImageByResId(int resId) {
        return BitmapFactory.decodeResource(getResources(), resId);
    }

}

3)在畫布上增加狀態信息和開始與結束界面,游戲主界面就完成了

/**
 * 游戲主界面
 * 
 * @Project App_View
 * @Package com.android.view.flybird
 * @author chenlin
 * @version 1.0
 * @Date 2014年5月7日
 */
public class FlyBirdView extends SurfaceView implements Callback, Runnable {
    //private static final String TAG = "bird";
    private SurfaceHolder mHolder;
    private ExecutorService mPool;
    private Canvas mCanvas;
    private boolean isRunnging;// 是否運行

    // 二.設置背景
    private Bitmap mBgBitmap;
    // 當前View的尺寸
    private int mWidth;
    private int mHeight;
    private RectF mGamePanelRect = new RectF();

    // 三、設置鳥
    private Bird mBird;
    private Bitmap mBirdBitmap;

    // 四、添加地板
    private Floor mFloor;
    private Bitmap mFloorBitmap;

    // 五、添加管道
    /** 管道的寬度 60dp */
    private static final int PIPE_WIDTH = 60;
    private Pipe mPipe;
    /** 上管道的圖片 */
    private Bitmap mPipeTopBitmap;
    /** 下管道的圖片 */
    private Bitmap mPipeBotBitmap;
    /** 管道的寬度 */
    private int mPipeWidth;
    /** 管道矩陣 */
    private RectF mPipeRectF;
    /** 管道集合 */
    private List mPipeList;

    /** 管道移動的速度 */
    private int mSpeed = UITools.dip2px(getContext(), 5);

    // 六、添加分數
    /** 分數 */
    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 Grade mGrade;
    /** 分數圖片組 */
    private Bitmap[] mNumBitmap;
    /** 分值 */
    private int mScore = 0;
    /** 單個數字的高度的1/15 */
    private static final float RADIO_SINGLE_NUM_HEIGHT = 1 / 15f;
    /** 單個數字的寬度 */
    private int mSingleGradeWidth;
    /** 單個數字的高度 */
    private int mSingleGradeHeight;
    /** 單個數字的范圍 */
    private RectF mSingleNumRectF;

    // --七、添加游戲的狀態-------------------------------------------------------------------------
    /** 剛進入游戲時是等待靜止的狀態 */
    private GameStatus mStatus = GameStatus.WAITING;

    private enum GameStatus {
        WAITING, RUNNING, OVER
    }

    /** 觸摸上升的距離,因為是上升,所以為負值 */
    private static final int TOUCH_UP_SIZE = -16;
    /** 將上升的距離轉化為px;這裡多存儲一個變量,變量在run中計算 */
    private final int mBirdUpDis = UITools.dip2px(getContext(), TOUCH_UP_SIZE);
    /** 跳躍的時候的臨時距離 */
    private int mTmpBirdDis;

    // --八、按鈕----------------------------------------
    private GameButton mStart;
    private Bitmap mStartBitmap;
    private Bitmap mStartPressBitmap;// 開始按下圖片

    private GameButton mRestart;
    private Bitmap mRestartBitmap;
    private Bitmap mRestartPressBitmap;// 從新開始按下圖片

    // --九、游戲中的變量---------------------------
    /** 兩個管道間距離 **/
    private final int PIPE_DIS_BETWEEN_TWO = UITools.dip2px(getContext(), 300);
    /** 鳥自動下落的距離 */
    private final int mAutoDownSpeed = UITools.dip2px(getContext(), 2);
    //private Handler mHandler = new Handler();

    // ----構造函數處理---------------------------------------------
    public FlyBirdView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public FlyBirdView(Context context) {
        super(context);
        init();
    }

    public FlyBirdView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    // ---初始化開始 ----------------------------------------------------------
    private void init() {
        // -初始化holer-----------------------
        mHolder = getHolder();
        mHolder.addCallback(this);
        setZOrderOnTop(true);
        // 設置畫布 背景透明
        mHolder.setFormat(PixelFormat.TRANSLUCENT);

        // --焦點設置----------------------------
        setFocusable(true);
        // 設置觸屏
        setFocusableInTouchMode(true);
        // 設置常亮
        setKeepScreenOn(true);

        // --背景設置--------------------------------
        mGamePanelRect = new RectF();
        mBgBitmap = loadImageByResId(R.drawable.bg1);

        // --添加鳥的圖片---
        mBirdBitmap = loadImageByResId(R.drawable.b1);
        // --添加地板---
        mFloorBitmap = loadImageByResId(R.drawable.floor_bg2);

        // --管道的寬度初始化--
        mPipeWidth = UITools.dip2px(getContext(), PIPE_WIDTH);
        // --添加管道圖片--
        mPipeTopBitmap = loadImageByResId(R.drawable.g2);
        mPipeBotBitmap = loadImageByResId(R.drawable.g1);
        mPipeList = new ArrayList();

        // -------------------------------------------------------

        // 初始化分數圖片
        mNumBitmap = new Bitmap[mNums.length];
        for (int i = 0; i < mNums.length; i++) {
            mNumBitmap[i] = loadImageByResId(mNums[i]);
        }

        // ---初始化按鈕圖片-------------------------------------
        mStartBitmap = BitmapUtil.getImageFromAssetsFile(getContext(), "start.png");
        mStartPressBitmap = BitmapUtil.getImageFromAssetsFile(getContext(), "start2.png");
        mRestartBitmap = BitmapUtil.getImageFromAssetsFile(getContext(), "restart1.png");
        mRestartPressBitmap = BitmapUtil.getImageFromAssetsFile(getContext(), "restart2.png");

    }

    @Override
    protected 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);

        // 初始化鳥
        mBird = new Bird(getContext(), mBirdBitmap, mWidth, mHeight);
        // 初始化地板
        mFloor = new Floor(mWidth, mHeight, mFloorBitmap);

        // 初始化管道范圍
        mPipeRectF = new RectF(0, 0, mPipeWidth, mHeight);
        // 初始化 管道
        mPipe = new Pipe(getContext(), mWidth, mHeight, mPipeTopBitmap, mPipeBotBitmap);
        mPipeList.add(mPipe);

        // 初始化分數
        mSingleGradeHeight = (int) (h * RADIO_SINGLE_NUM_HEIGHT);// 屏幕的1/15
        mSingleGradeWidth = (int) (mNumBitmap[0].getWidth() * (1.0f * mSingleGradeHeight / mNumBitmap[0].getHeight()));
        mSingleNumRectF = new RectF(0, 0, mSingleGradeWidth, mSingleGradeHeight);
        mGrade = new Grade(mNumBitmap, mSingleNumRectF, mSingleGradeWidth, mWidth, mHeight);

        // 初始化按鈕
        mStart = new GameButton(mStartBitmap, mStartPressBitmap, mWidth, mHeight);
        // 從新開始按鈕
        mRestart = new GameButton(mRestartBitmap, mRestartPressBitmap, mWidth, mHeight);

        if (mStatus == GameStatus.WAITING && mStart != null) {
            ObjectAnimator anim = ObjectAnimator.ofInt(mStart, "Y", mHeight, mHeight / 2);
            anim.setDuration(2000);
            anim.start();
        }

        // 添加事件
        mStart.setOnButtonClickListener(new OnButtonClickListener() {
            @Override
            public void click() {
                if (mStatus == GameStatus.WAITING) {
                    // 按下的時候,游戲進入運行狀態
                    mStatus = GameStatus.RUNNING;
                }
            }
        });
        mRestart.setOnButtonClickListener(new OnButtonClickListener() {
            @Override
            public void click() {
                mStatus = GameStatus.WAITING;
                resetBirdStatus();
            }
        });

    }

    // ---初始化結束 ----------------------------------------------------------

    // --處理觸碰事件------------------------------------------------------------------------
    private int mDownX = 0;
    private int mDownY = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:// 按下
            mDownX = (int) event.getX();
            mDownY = (int) event.getY();
            if (mStatus == GameStatus.WAITING) {
                if (mStart.isClick(mDownX, mDownY)) {
                    mStart.click();
                }
            } else if (mStatus == GameStatus.RUNNING) {
                // 記錄臨時跳躍的高度
                mTmpBirdDis = mBirdUpDis;

                // --增加難度---
                if (mScore > 20) {
                    mSpeed += UITools.dip2px(getContext(), 1);
                } else if (mScore > 40) {
                    mSpeed += UITools.dip2px(getContext(), 2);
                } else if (mScore > 60) {
                    mSpeed += UITools.dip2px(getContext(), 3);
                } else if (mScore > 80) {
                    mSpeed += UITools.dip2px(getContext(), 4);
                }


            } else if (mStatus == GameStatus.OVER) {// 游戲結束時
                // 判斷是否點擊了重新開始圖片
                if (mRestart.isClick(mDownX, mDownY)) {
                    mRestart.click();
                }
            }

            break;
        case MotionEvent.ACTION_MOVE:// 移動
            int moveX = (int) event.getX();
            int moveY = (int) event.getY();
            AnimatorSet set = new AnimatorSet();
            ObjectAnimator animatorX = ObjectAnimator.ofInt(mBird, "X", mDownX, moveX);
            ObjectAnimator animatorY = ObjectAnimator.ofInt(mBird, "Y", mDownY, moveY);
            set.playTogether(animatorX, animatorY);
            set.setDuration(2000);
            set.start();

            mDownX = (int) event.getX();
            mDownY = (int) event.getY();
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP: // 抬起
            if (mStart != null) {
                mStart.setClick(false);
            }
            if (mRestart != null) {
                mRestart.setClick(false);
            }
            break;
        }

        return true;
    }

    private void resetBirdStatus() {
        // 設置鳥的高度
        mBird.setY((int) (mHeight * Bird.RADIO_POS_HEIGHT));
        // 重置下落速度
        mTmpBirdDis = 0;
    }

    // --處理邏輯事物------------------------------------------------------------------------
    /** 記錄要移除的管道 為什麼不用CopyOnWriteArrayList,因為其是線程安全的 */
    private List mNeedRemovePipe = new ArrayList();
    /** 記錄要移動的距離 */
    private int mTmpMoveDistance = 0;
    /** 記錄要移除的管的個數 */
    private int mRemovedPipe = 0;

    /**
     * 處理邏輯事物
     */
    private void logic() {
        switch (mStatus) {
        case WAITING:// 剛進入游戲的狀態

            break;
        case RUNNING:// 正在玩的狀態]
            mScore = 0;

            // ---.移動地板-----------
            mFloor.setX(mFloor.getX() - mSpeed);

            // ---不斷移動管道--------
            logicPipe();

            // ----處理鳥邏輯----
            mTmpBirdDis += mAutoDownSpeed;
            mBird.setY(mBird.getY() + mTmpBirdDis);

            // ---處理分數---
            mScore += mRemovedPipe;
            for (Pipe pipe : mPipeList) {
                if (pipe.getX() + mPipeWidth < mBird.getX()) {
                    mScore++;
                }
            }

            // ----判斷游戲是否結束----
            checkGameOver();

            break;

        case OVER:// 鳥落下
            // 如果鳥還在空中,先讓它掉下來
            if (mBird.getY() < mFloor.getY() - mBird.getHeight()) {
                mTmpBirdDis += mAutoDownSpeed;
                mBird.setY(mBird.getY() + mTmpBirdDis);
            } else {
                // 清除生成的管道
                clearAndInit();
            }
            break;
        }
    }

    /**
     * 重置鳥的位置等數據
     */
    private void clearAndInit() {
        // 清除生成的管道
        mPipeList.clear();
        // 需要移除的管道集合
        mNeedRemovePipe.clear();
        // 清除移動的距離
        mTmpMoveDistance = 0;
        // 管道的個數
        mRemovedPipe = 0;
    }

    /**
     * 處理管道邏輯
     */
    private void logicPipe() {
        // 1.遍歷所有的管道
        for (Pipe pipe : mPipeList) {
            // 2.如果管子已經在屏幕外
            if (pipe.getX() < -mPipeWidth) {
                mNeedRemovePipe.add(pipe);
                mRemovedPipe++;
                continue;
            }
            pipe.setX(pipe.getX() - mSpeed);
        }
        // 3.移除管道
        mPipeList.removeAll(mNeedRemovePipe);
        // 4.記錄移動距離
        mTmpMoveDistance += mSpeed;
        // 5.生成一個管道
        if (mTmpMoveDistance >= PIPE_DIS_BETWEEN_TWO) {
            Pipe pipe = new Pipe(getContext(), getWidth(), getHeight(), mPipeTopBitmap, mPipeBotBitmap);
            mPipeList.add(pipe);
            mTmpMoveDistance = 0;
        }
    }

    /**
     * 判斷游戲是否結束
     */
    private void checkGameOver() {
        // 判斷小鳥是否觸碰到了地板
        if (mBird.getY() > mFloor.getY() - mBird.getHeight()) {
            mStatus = GameStatus.OVER;
        }
        // 判斷是否觸碰到了管道
        for (Pipe pipe : mPipeList) {
            // 已經穿過的
            if (pipe.getX() + mPipeWidth < mBird.getX()) {
                continue;
            }
            // 如果是碰到了,游戲結束
            if (pipe.touchBird(mBird)) {
                mStatus = GameStatus.OVER;
                break;
            }
        }

    }

    // ---游戲引擎------------------------------------------------------------

    @Override
    public void run() {
        while (isRunnging) {
            long start = System.currentTimeMillis();
            logic();
            draw();
            long end = System.currentTimeMillis();
            if (end - start < 50) {
                SystemClock.sleep(50 - (end - start));
            }
        }
    }

    // ----繪制開始-------------------------------------------------------------------
    private void draw() {
        try {
            if (mHolder != null) {
                mCanvas = mHolder.lockCanvas();

                if (mCanvas != null) {
                    drawBg(); // 繪制背景
                    drawBird();// 繪制鳥
                    drawFloor();// 繪制地板
                    drawGrades();// 繪制分數
                    if (mStatus == GameStatus.WAITING) {
                        drawStart();
                    }
                    if (mStatus == GameStatus.RUNNING) {
                        drawPipes();// 繪制管道
                    }
                    if (mStatus == GameStatus.OVER) {
                        drawGameOver();
                        drawRestart();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (mHolder != null && mCanvas != null) {
                mHolder.unlockCanvasAndPost(mCanvas);
            }

        }
    }

    private FontMetrics fm;
    private int mTextHeight = 0;// 游戲結束時文本的高度

    private void drawGameOver() {
        String mGameOver = "GAME OVER";
        Typeface typeface = Typeface.createFromAsset(getContext().getAssets(), "BRITANIC.TTF");
        Paint paint = new Paint();
        paint.setAntiAlias(true); // 是否抗鋸齒
        paint.setTypeface(typeface);
        paint.setStrokeWidth(3);
        paint.setColor(Color.RED);
        paint.setTextSize(50);
        // paint.setShader(shader);//設置字體
        paint.setShadowLayer(5, 3, 3, 0xFFFF00FF);// 設置陰影
        paint.setTextAlign(Paint.Align.CENTER);
        // paint.setStyle(Paint.Style.STROKE); //空心
        paint.setStyle(Paint.Style.FILL); // 實心
        paint.setDither(true);
        fm = paint.getFontMetrics();
        mTextHeight = (int) (Math.ceil(fm.descent - fm.ascent) + UITools.dip2px(getContext(), 4));
        mCanvas.drawText(mGameOver, mWidth / 2, mHeight / 2, paint);
    }

    /**
     * 繪制開始按鈕
     */
    private void drawStart() {
        mStart.draw(mCanvas);
    }

    /**
     * 繪制重新開始按鈕
     */
    private void drawRestart() {
        mRestart.setY(mHeight/2 + mTextHeight);
        mRestart.draw(mCanvas);
//      Logger.i(TAG, "aaaa");
//      mHandler.postDelayed(new Runnable() {
//          @Override
//          public void run() {
//              if (mRestart != null) {
//                  Logger.i(TAG, "kkkk");
//                  ObjectAnimator anim = ObjectAnimator.ofInt(mRestart, "Y", mHeight, mHeight / 2 + mTextHeight);
//                  anim.setDuration(2000);
//                  anim.start();
//              }
//          }
//      }, 0);

    }

    /**
     * 繪制分數
     */
    private void drawGrades() {
        mGrade.draw(mCanvas, mScore);
    }

    private void drawFloor() {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(true);
        mFloor.draw(mCanvas, paint);
        // 更新我們地板繪制的x坐標
        mFloor.setX(mFloor.getX() - mSpeed);
    }

    private void drawBird() {
        mBird.draw(mCanvas);
    }

    private void drawBg() {
        mCanvas.drawBitmap(mBgBitmap, null, mGamePanelRect, null);
    }

    private void drawPipes() {
        for (Pipe pipe : mPipeList) {
            // 先設定x坐標
            pipe.setX(pipe.getX() - mSpeed);
            pipe.draw(mCanvas, mPipeRectF);
        }
    }

    // ----繪制結束-------------------------------------------------------------------

    // ---callback監聽------------------------------------------------------
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // -線程處理--------------------------
        isRunnging = true;
        mPool = Executors.newFixedThreadPool(5);
        mPool.execute(this);
    }

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

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 通知關閉線程
        isRunnging = false;
    }

    /**
     * 根據resId加載圖片
     * 
     * @param resId
     * @return
     */
    private Bitmap loadImageByResId(int resId) {
        return BitmapFactory.decodeResource(getResources(), resId);
    }

}

五、做完後我們的運行,要通過activity來實現

public class FlyBirdActivity extends Activity{

    FlyBirdView mBirdView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        mBirdView = new FlyBirdView(this);
        setContentView(mBirdView);
    }
}

六、代碼下載

鏈接:http://pan.baidu.com/s/1bpzAsgv 密碼:ol82

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved