Android应用开发之Bird fly游戏制作(一)

好久没有更新博客了,最近试着做了一下之前火过一段时间的小游戏Bird fly,来跟大家分享一下我的经验。
首先上一个效果图:
这里写图片描述

我们来简单做一下第一步分析:

1、绘制游戏背景。
2、绘制水管。
3、绘制地板的背景。
4、绘制那只小鸟。
5、绘制分数

接下来第二步的分析:

1、底板的移动
2、水管默认的向左移动
3、小鸟的自动下落,以及碰到水管或地板之后游戏over
4、水管的自动生成以及移除

接下来可以敲代码了。
一、SurfaceView的大众写法:

package com.jp.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 BaseGameBirdextends 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);  

    }  

    @Override  
    public void surfaceCreated(SurfaceHolder holder)  
    {  

        // 开启线程  
        isRunning = true;  
        t = new Thread(this);  
        t.start();  
    }  

    @Override  
    public void surfaceChanged(SurfaceHolder holder, int format, int width,  
            int height)  
    {  
        // TODO Auto-generated method stub  

    }  

    @Override  
    public void surfaceDestroyed(SurfaceHolder holder)  
    {  
        // 通知关闭线程  
        isRunning = false;  
    }  

    @Override  
    public 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  
        {  
            // 获得canvas  
            mCanvas = mHolder.lockCanvas();  
            if (mCanvas != null)  
            {  
                // drawSomething..  
            }  
        } catch (Exception e)  
        {  
        } finally  
        {  
            if (mCanvas != null)  
                mHolder.unlockCanvasAndPost(mCanvas);  
        }  
    }  
}  

一般 SurfaceView的基础都是这么写的,大家可以拷过去直接用;

二、背景图的绘制:
背景其实也就是一张图片。

   /** 
     * 当前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);  
    }  

    /** 
     * 初始化尺寸相关 
     */  
    @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);  
    }  

    /** 
     * 根据resId加载图片 
     *  
     * @param resId 
     * @return 
     */  
    private Bitmap loadImageByResId(int resId)  
    {  
        return BitmapFactory.decodeResource(getResources(), resId);  
    }  

通过mCanvas.drawBitmap(),设置背景。这个也是BaseGameBird类里的内容

三、绘制小鸟

小鸟处于屏幕正中央,所以x为屏幕的一般,高度么,这个其实可以看个人爱好,我这边设置的是1/3f。

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RectF;

/**
 * Created by Administrator on 2016/1/5.
 */
public class Bird
{
    /**
     * 鸟在屏幕的2/3的位置
     */
    private static final float BIRD_HIGHT = 1 / 3f;
    /**
     * 鸟的宽度
     */
    private static final int BIRD_WIDTH_SIZE = 30;
    /**
     * 鸟的高宽度
     */
    private int mBirdHeight, mBirdWidth;
    /**
     * 鸟的横竖坐标
     */
    private int x, y;
    /**
     * 鸟的绘制范围
     */
    private RectF mBirdRecf = new RectF();

    /**
     * 鸟的bitmap
     */
    private Bitmap bitmap;

    public Bird(Context context,int height, int width, Bitmap bitmap)
    {
        this.bitmap=bitmap;
        //鸟的位置
        System.out.println(width);
        System.out.println(bitmap.getWidth());
        x=width/2-bitmap.getWidth()/2;
        y= (int) (height*BIRD_HIGHT);

        mBirdWidth=Util.dp2px(context,BIRD_WIDTH_SIZE);
        mBirdHeight= (int) (mBirdWidth*1.0f/bitmap.getWidth()*bitmap.getHeight());

    }

    /**
     * 绘制自己
     * @param canvas
     */
    public void draw(Canvas canvas){
        mBirdRecf.set(x,y,x+mBirdWidth,y+mBirdHeight);
        canvas.drawBitmap(bitmap,null,mBirdRecf,null);
    }

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

    public int getHeight()
    {
        return mBirdHeight;
    }
}

接下来只需要在我们BaseGameBird,初始化一下,调用draw()传入需要的属性即可;
筛选后代码:

 /** 
     * *********鸟相关********************** 
     */  
    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);  
    }  

    /** 
     * 初始化尺寸相关 
     */  
    @Override  
    protected void onSizeChanged(int w, int h, int oldw, int oldh)  
    {  
        // 初始化mBird  
        mBird = new Bird(getContext(), mWidth, mHeight, mBirdBitmap);  

    }  

在MainActivity中先调用一下,看一下效果图:

package com.jp.birddemo.activity;


import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.Window;
import android.view.WindowManager;

import tool.BaseGameBird;

public class MainActivity extends FragmentActivity
{
    private BaseGameBird baseGameBird;

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

三、底板绘制

import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Shader;

/**
 * Created by Administrator on 2016/1/7.
 */
public class Floor
{
    /**
     * 地板的位置
     */
    private static final float FLOOR_Y = 3 / 4F;
    /**
     * 地板的坐标
     */
    private int x, y;
    /**
     * 地板的图形
     */
    private BitmapShader mBitmapSharder;

    private int mFloorHeight, mFloorWidth;

    public Floor(int floorHeight, int floorWidth, Bitmap bitmap)
    {
        this.mFloorHeight = floorHeight;
        this.mFloorWidth = floorWidth;

        y = (int) (floorHeight * FLOOR_Y);
        mBitmapSharder = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
    }

    public void draw(Canvas mCanvas, Paint mPaint)
    {
        if (-x > mFloorWidth) {
            x = x % mFloorWidth;
        }
        mCanvas.save(Canvas.MATRIX_SAVE_FLAG);
        mCanvas.translate(x, y);
        mPaint.setShader(mBitmapSharder);
        mCanvas.drawRect(x, 0, -x + mFloorWidth, mFloorHeight - y, mPaint);
        mCanvas.restore();
        mPaint.setShader(null);
    }

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


    public int getX()
    {
        return x;
    }
    public int getY()
    {
        return y;
    }
}

定义了一堆成员变量,核心就在于,我们传入地板背景的填充物,然后初始化我们的mFloorShader,横向重复,纵向拉伸(这里的拉伸是指,纵向的最后一个像素不断重复)。
我们对外公布了draw方法,传入Canvas,我们首先调用canvas.save(),然后将canvas移动到指定的位置,然后绘制我们的矩形,矩形的填充就是我们的地板了
这里,注意一下,我们这里使用了一个变量x,而不是0;为什么呢?因为我们的地板需要利用这个x运动。
那么现在我们如何才能动呢?
首先我们在BaseGameBird定义一个变量,表示移动速度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,贴下筛检后BaseGameBird代码:

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

    @Override  
    protected void onSizeChanged(int w, int h, int oldw, int oldh)  
    {  
        // 初始化地板  
        mFloor = new Floor(mWidth, mHeight, mFloorBg);  

    }  

通过mFloor.setX(mFloor.getX() - mSpeed)的设置,表现出地板在移动的效果。

四、水管的绘制
水管的特点:

1、上下水管的距离都是固定的,这里我们设置为1 / 5F
2、上下水管出现的时候长短不一

好了,简单先这样

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RectF;

import java.util.Random;

/**
 * Created by Administrator on 2016/1/7.
 */
public class Pipe
{
    /**
     * 上下管道的距离
     */
    private static final float PIPE_LONG_SIZE = 1 / 5F;
    /**
     * 上管道的最大高度
     */
    private static final float PIPE_TOP_SIZE = 2 / 5F;
    /**
     * 下管道的最大高度
     */
    private static final float PIPE_BOTTOM_SIZE = 1 / 5f;
    /**
     * 管道的横坐标
     */
    private int x;
    /**
     * 上管道的高度
     */
    private int mHeight;
    /**
     * 两个管道的距离
     */
    private int margin;
    /**
     * 上下管道图片
     */
    private Bitmap mTopBitmap, mBottomBitmap;
    /**
     * 用于产生随机高度
     */
    private static Random random = new Random();

    public Pipe(Context context, int mPipeHeight, int mPipeWeight, Bitmap mTopBitmap, Bitmap mBottomBitmap)
    {
        margin = (int) (mPipeHeight * PIPE_LONG_SIZE);
        x = mPipeWeight;

        this.mTopBitmap = mTopBitmap;
        this.mBottomBitmap = mBottomBitmap;

        getRandomHeight(mPipeHeight);
    }

    private void getRandomHeight(int mPipeHeight)
    {
        mHeight = random.nextInt((int) (mPipeHeight * (PIPE_TOP_SIZE - PIPE_BOTTOM_SIZE)));
        mHeight = (int) (mHeight + mPipeHeight * PIPE_BOTTOM_SIZE);
    }

    public void draw(Canvas canvas, RectF rectF)
    {
        canvas.save(Canvas.MATRIX_SAVE_FLAG);
        canvas.translate(x, -(rectF.bottom - mHeight));
        canvas.drawBitmap(mTopBitmap, null, rectF, null);

        canvas.translate(0, (rectF.bottom - mHeight) + mHeight + margin);
        canvas.drawBitmap(mBottomBitmap, null, rectF, null);
        canvas.restore();
    }

    public int getX()
    {
        return x;
    }

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

BaseGameBird筛检后的代码:


    /** 
     * *********管道相关********************** 
     */  
    /** 
     * 管道 
     */  
    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);  
        }  
    }  

    /** 
     * 初始化尺寸相关 
     */  
    @Override  
    protected 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);  

    }  

五、分数绘制:

/** 
     * 分数 
     */  
    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 one  
        for (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();  

    }  


    /** 
     * 初始化尺寸相关 
     */  
    @Override  
    protected 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);  

    }  

这里还要说一下,我上面用到转换px的方法:

public static int dp2px(Context context, float dp)
    {
        int px = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()));
        return px;
    }

好了先这样,明天更新全部教程,以及提供源代码


版权声明:本文为jiapeng2b原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。