Android 模仿flabby bird游戏开发

一、示意图:

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<Pipe> 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<Pipe>();

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

        // 初始化分数图片
        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<Pipe> 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<Pipe>();

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

        // 初始化分数图片
        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<Pipe> mNeedRemovePipe = new ArrayList<Pipe>();
    /** 记录要移动的距离 */
    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

—————————————————————————————————————————————————–

java架构师项目实战,高并发集群分布式,大数据高可用视频教程,共760G

下载地址:

https://item.taobao.com/item.htm?id=555888526201

01.高级架构师四十二个阶段高
02.Java高级系统培训架构课程148课时
03.Java高级互联网架构师课程
04.Java互联网架构Netty、Nio、Mina等-视频教程
05.Java高级架构设计2016整理-视频教程
06.架构师基础、高级片
07.Java架构师必修linux运维系列课程
08.Java高级系统培训架构课程116课时
+
hadoop系列教程,java设计模式与数据结构, Spring Cloud微服务, SpringBoot入门

内容详情:

【入门篇】
J2SE的Socket网络编程应用
J2SE的反射机制高级应用
J2SE高深讲解
JAVA编程思想 中级教程
JAVA编程思想 初级教程
JAVA编程思想 高级教程
基于J2SE的QQ聊天工具开发
我来说说面向对象
【进阶篇】
CRM项目
Eclipse
Hibernate
JAVA WEB开发视频
JAVAWEB开发实战经典-高级案例篇
JAVAWEB
JAVA线程并发教程
java网上在线支付实战视频
java设计模式
jdbc
junit
mybatis
spring mvc
SpringMvc+Spring+MyBatis+Maven整合视频
SpringMVC
Spring
Struts2 开发实战讲解
Struts2+Spring3+Hibernate4+Maven+EasyUI整合入门视频
Struts
SVN
tomcat
weblogic
WebService从入门到上手企业开发
企业系统OA办公自动化
手机进销存系统
数据结构和算法视频
设计模式系列
【高级篇】
Cas单点登录
Extjs4.1+Spring3.2+hibernate4.1+MySql5商城
Git权威指南
groovy入门视频
Java 8新特性
Lucene
Mongodb
node.js
Nutch相关框架
OA办公自动化系统
Quartz Job定时任务
Solr高级应用视频
Spring Security权限控制
Spring源码解读与设计详析
Struts2源码剖析与架构指导
大型CMS内容管理系统项目
微信入门视频
深入JVM内核—原理、诊断与优化
深入浅出微信公众平台实战开发(微网站、LBS云、Api接口调用、服务号高级接口)
银行接口资料
【架构篇】
ActiveMQ实战
Apache-Tomcat集群搭建
Linux集群
Linux高级架构架构方案及实现指南
Memcached分布式集群
Mysql特级优化课程
Nginx+Tomcat+Memcached群集配置软件包
Nginx服务器搭建
Nginx网站架构实战(Web服务器负载均衡与反向代理)
SOA Dubbo
storm入门到精通
storm集群的搭建
storm项目实战
UML建模
互联网公司技术架构系列
京东B2C平台推荐搜索的实践和思考
京东大数据分析与创新应用
京东大规模存储持续研发
京东电商海量订单处理OFC系统的关键技术
优米网架构设计方案
基于SOA 思想下的百万数据架构
大型网站提速之MySQL优化
大型网站架构设计
大数据高并发架构实战案例
数据优化技术Redis
数据库高并发原理
深入Java程序性能调优
深入浅出MongoDB应用实战集群及系统架构
深度揭秘服务器端内幕
电商网站之Solr应用
系统架构设计师
阿里分布式数据库服务实践
—————————————————————————————————————————————————–

高清版 Book Description “Wolfenstein 3D”-like and ”Doom”-like game apps are some of the classic Android games presented in the original edition of this book. Since their release, Android has progressed with the debut of Android 4.0, adding better fonts, new User Interface and Experience (UI/UX) APIs, tablet considerations, multi-touch capabilities, multi-tasking, faster performance, and much more to the Android game app development repertoire. Multi-touch code gives these games and their players dynamic input and exchange ability, for a more realistic arcade game experience. Faster and better performance offers game players a more seamless, fun arcade experience like never before on Android. There is also improved native C/C++ integration with Android’s NDK as well, which makes coding, compiling, and converting both productive and efficient with gains in app performance. With actionable real-world source, Advanced Android 4 Games shows you how to build more sophisticated and addictive Android games, harnessing the power of these recent advancements. * Coverage of the new UI, UX, multi-touch and multi-tasking features available with Android 4.0. * Learn other techniques for improving the game playing experience including Wi-Fi tethering, better multi-tasking, new and better streaming Web video using WebM, and more. * By combining the elegant object-oriented features of Java and the raw power of C, there is no limit to the types of games that you can build for the platform, such as the “Quake 3D”-like game app case study in this book. You’ll definitely have fun, and perhaps you’ll even make some money. Enjoy! What you’ll learn * Key advanced Android gaming techniques using the new Android SDK 4 to help you earn more money in the app stores * How to compile native code (C) in Android using the NDK * How to add and integrate multi-touch * How to use Bluetooth controllers (Zeemote) * More gaming tricks and tips, such as hybrid 3D graphics with OpenGL and JNI * How to port and augment a 2D shooter game app similar to “Wolfenstein” for Android * How to port and augment another 2D shooter “Doom”-like game app for Android using OpenGL * How to build a 3D shooter game like “Quake” * How and where to best deploy these game apps Who this book is for This book is for savvy Android app developers who are looking for professional or advanced techniques for porting, augmenting and building 2D and 3D game apps that are complex, fun and lucrative. Table of Contents 1. Welcome to Android Gaming 2. Gaming Tricks: 3. More Gaming Tricks: Hybrid 3D Graphics with OpenGL and JNI 4. 2D Shooters: Wolfenstein-like App for Android 5. 2D Shooters with OpenGL: Doom-like App for Android 6. 3D Shooters Part I: Quake-like App 7. 3D Shooters Part II: Quake II 8. Appendix: Deployment and Compilation Tips
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lovoo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值