您的位置:首页 > 移动开发 > Android开发

Android SurfaceView实战 带你玩转flabby bird (上)

2015-01-22 01:06 399 查看
Android SurfaceView实战 带你玩转flabby bird (上)

标签:
flabby birdSurfaceView游戏

2015-01-22 01:06
31296人阅读 评论(36)
收藏
举报

本文章已收录于:


分类:
【Android 原生开发游戏】(2)




作者同类文章X

【Android 精彩案例】(36)




作者同类文章X

【Android 自定义控件实战】(28)




作者同类文章X

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[+]
概述
分析
SurfaceView的一般写法
绘制
绘制背景
绘制bird
绘制地板
绘制管道
绘制分数

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/42965779 ,本文出自:【张鸿洋的博客】

1、概述

哈,记得以前写过Android
SurfaceView实战 打造抽奖转盘 , 同属于SurfaceView系列,基本可以从这篇博文中学习到SurfaceView的用法,以及利用SurfaceView做抽奖转盘。但是其中缺少一部分的知识点,就是与用户交互时界面的改变,所以今天给大家再带来本篇博文教大家如何做flabby bird这款游戏,这游戏虽然不难,但是也为其作者挣了不少钱,大家在学会以后,可以尽可能发挥自己的创意,做属于自己的游戏,说不定下一个火的奏是你。

ok,那么首先上下效果图:



再来张动态的:



由于上传图片最大限制为2M,所以做了压缩处理,凑合看吧 ~~~

2、分析

仔细观察游戏,需要绘制的有:背景、地板、鸟、管道、分数;

游戏开始时:

地板给人一种想左移动的感觉;

管道与地板同样的速度向左移动;

鸟默认下落;

当用户touch屏幕时,鸟上升一段距离后,下落;

运动过程中需要判断管道和鸟之间的位置关系,是否触碰,是否穿过等,需要计算分数。

好了,大概就这么多,那我们首先开始考虑绘制~~~

3、SurfaceView的一般写法

接下来,我们首先编写下SurfaceView的一般写法:

[java]
view plain
copy

print?





package com.zhy.view;  
  
import android.content.Context;  
import android.graphics.Canvas;  
import android.graphics.PixelFormat;  
import android.util.AttributeSet;  
import android.view.SurfaceHolder;  
import android.view.SurfaceHolder.Callback;  
import android.view.SurfaceView;  
  
public class GameFlabbyBird extends SurfaceView implements Callback, Runnable  
{  
  
    private SurfaceHolder mHolder;  
    /** 
     * 与SurfaceHolder绑定的Canvas 
     */  
    private Canvas mCanvas;  
    /** 
     * 用于绘制的线程 
     */  
    private Thread t;  
    /** 
     * 线程的控制开关 
     */  
    private boolean isRunning;  
  
    public GameFlabbyBird(Context context)  
    {  
        this(context, null);  
    }  
  
    public GameFlabbyBird(Context context, AttributeSet attrs)  
    {  
        super(context, attrs);  
  
        mHolder = getHolder();  
        mHolder.addCallback(this);  
  
        setZOrderOnTop(true);// 设置画布 背景透明  
        mHolder.setFormat(PixelFormat.TRANSLUCENT);  
  
        // 设置可获得焦点  
        setFocusable(true);  
        setFocusableInTouchMode(true);  
        // 设置常亮  
        this.setKeepScreenOn(true);  
  
    }  
  
    @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);  
        }  
    }  
}  



package com.zhy.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;

public class GameFlabbyBird extends SurfaceView implements Callback, Runnable
{

private SurfaceHolder mHolder;
/**
* 与SurfaceHolder绑定的Canvas
*/
private Canvas mCanvas;
/**
* 用于绘制的线程
*/
private Thread t;
/**
* 线程的控制开关
*/
private boolean isRunning;

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

public GameFlabbyBird(Context context, AttributeSet attrs)
{
super(context, attrs);

mHolder = getHolder();
mHolder.addCallback(this);

setZOrderOnTop(true);// 设置画布 背景透明
mHolder.setFormat(PixelFormat.TRANSLUCENT);

// 设置可获得焦点
setFocusable(true);
setFocusableInTouchMode(true);
// 设置常亮
this.setKeepScreenOn(true);

}

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


这个基础的类,在Android
SurfaceView实战 打造抽奖转盘已经出现过,就不多说了,大家以后写SurfaceView的相关程序,可以直接拷贝,在此类基础上编写。

4、绘制

1、绘制背景

最简单的当然是背景了,直接drawBitmap即可。

我们添加需要的成员变量,以及初始化一些参数,然后添加drawBg方法,最后在draw中调用drawBg;

[java]
view plain
copy

print?





public class CopyOfGameFlabbyBird extends SurfaceView implements Callback,  
        Runnable  
{  
    /** 
     * 当前View的尺寸 
     */  
    private int mWidth;  
    private int mHeight;  
    private RectF mGamePanelRect = new RectF();  
  
    /** 
     * 背景 
     */  
    private Bitmap mBg;  
  
    public CopyOfGameFlabbyBird(Context context, AttributeSet attrs)  
    {  
        //省略了很多代码  
        initBitmaps();  
    }  
  
    /** 
     * 初始化图片 
     */  
    private void initBitmaps()  
    {  
        mBg = loadImageByResId(R.drawable.bg1);  
    }  
  
    private void draw()  
    {  
        //省略了很多代码  
        drawBg();  
        //省略了很多代码  
    }  
  
    /** 
     * 绘制背景 
     */  
    private void drawBg()  
    {  
        mCanvas.drawBitmap(mBg, null, mGamePanelRect, null);  
    }  
  
    /** 
     * 初始化尺寸相关 
     */  
    @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);  
    }  
}  



public class CopyOfGameFlabbyBird extends SurfaceView implements Callback,
Runnable
{
/**
* 当前View的尺寸
*/
private int mWidth;
private int mHeight;
private RectF mGamePanelRect = new RectF();

/**
* 背景
*/
private Bitmap mBg;

public CopyOfGameFlabbyBird(Context context, AttributeSet attrs)
{
//省略了很多代码
initBitmaps();
}

/**
* 初始化图片
*/
private void initBitmaps()
{
mBg = loadImageByResId(R.drawable.bg1);
}

private void draw()
{
//省略了很多代码
drawBg();
//省略了很多代码
}

/**
* 绘制背景
*/
private void drawBg()
{
mCanvas.drawBitmap(mBg, null, mGamePanelRect, null);
}

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


基本就是添加成员变量,然后初始化,然后绘制,上述代码经过删减,贴出的都是与前面基础代码不同的部分,大家可以将代码对号入座进行填充。

好了,现在背景图绘制好了,接下来,我们绘制小鸟~~~

2、绘制bird

鸟在我们的屏幕中,初始化时需要一个位置,x上,肯定是居中,y上我们取2/3的高度;

关于bird,我们单独创建一个类:

[java]
view plain
copy

print?





package com.zhy.view;  
  
import android.content.Context;  
import android.graphics.Bitmap;  
import android.graphics.Canvas;  
import android.graphics.RectF;  
  
public class Bird  
{  
    /** 
     * 鸟在屏幕高度的2/3位置 
     */  
    private static final float RADIO_POS_HEIGHT = 2 / 3F;  
    /** 
     * 鸟的宽度 30dp 
     */  
    private static final int BIRD_SIZE = 30;  
  
    /** 
     * 鸟的横坐标 
     */  
    private int x;  
    /** 
     * 鸟的纵坐标 
     */  
    private int y;  
    /** 
     * 鸟的宽度 
     */  
    private int mWidth;  
    /** 
     * 鸟的高度 
     */  
    private int mHeight;  
  
    /** 
     * 鸟的bitmap 
     */  
    private Bitmap bitmap;  
    /** 
     * 鸟绘制的范围 
     */  
    private RectF rect = new RectF();  
  
    public Bird(Context context, int gameWith, int gameHeight, Bitmap bitmap)  
    {  
  
        this.bitmap = bitmap;  
        //鸟的位置  
        x = gameWith / 2 - bitmap.getWidth() / 2;  
        y = (int) (gameHeight * RADIO_POS_HEIGHT);  
  
        // 计算鸟的宽度和高度  
        mWidth = Util.dp2px(context, BIRD_SIZE);  
        mHeight = (int) (mWidth * 1.0f / bitmap.getWidth() * bitmap.getHeight());  
    }  
  
    /** 
     * 绘制自己 
     *  
     * @param canvas 
     */  
    public void draw(Canvas canvas)  
    {  
        rect.set(x, y, x + mWidth, y + mHeight);  
        canvas.drawBitmap(bitmap, null, rect, null);  
  
    }  
  
    public int getY()  
    {  
        return y;  
    }  
  
    public void setY(int y)  
    {  
        this.y = y;  
    }  
  
    public int getWidth()  
    {  
        return mWidth;  
    }  
  
    public int getHeight()  
    {  
        return mHeight;  
    }  
  
}  



package com.zhy.view;

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

public class Bird
{
/**
* 鸟在屏幕高度的2/3位置
*/
private static final float RADIO_POS_HEIGHT = 2 / 3F;
/**
* 鸟的宽度 30dp
*/
private static final int BIRD_SIZE = 30;

/**
* 鸟的横坐标
*/
private int x;
/**
* 鸟的纵坐标
*/
private int y;
/**
* 鸟的宽度
*/
private int mWidth;
/**
* 鸟的高度
*/
private int mHeight;

/**
* 鸟的bitmap
*/
private Bitmap bitmap;
/**
* 鸟绘制的范围
*/
private RectF rect = new RectF();

public Bird(Context context, int gameWith, int gameHeight, Bitmap bitmap)
{

this.bitmap = bitmap;
//鸟的位置
x = gameWith / 2 - bitmap.getWidth() / 2;
y = (int) (gameHeight * RADIO_POS_HEIGHT);

// 计算鸟的宽度和高度
mWidth = Util.dp2px(context, BIRD_SIZE);
mHeight = (int) (mWidth * 1.0f / bitmap.getWidth() * bitmap.getHeight());
}

/**
* 绘制自己
*
* @param canvas
*/
public void draw(Canvas canvas)
{
rect.set(x, y, x + mWidth, y + mHeight);
canvas.drawBitmap(bitmap, null, rect, null);

}

public int getY()
{
return y;
}

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

public int getWidth()
{
return mWidth;
}

public int getHeight()
{
return mHeight;
}

}


定义了一个类,代表我们的鸟,以及一堆成员变量,并且提供一个draw方法对外;

在GameFlabbyBird中,只需要,初始化我们的Bird,在draw里面调用bird.draw即可;

部分筛检后代码:

[java]
view plain
copy

print?





public class CopyOfGameFlabbyBird extends SurfaceView implements Callback,  
        Runnable  
{  
    /** 
     * *********鸟相关********************** 
     */  
    private Bird mBird;  
    private Bitmap mBirdBitmap;  
  
    /** 
     * 初始化图片 
     */  
    private void initBitmaps()  
    {  
        mBg = loadImageByResId(R.drawable.bg1);  
        mBirdBitmap = loadImageByResId(R.drawable.b1);  
  
    }  
  
    private void draw()  
    {  
        // drawSomething..  
  
        drawBg();  
        drawBird();  
  
    }  
  
    private void drawBird()  
    {  
        mBird.draw(mCanvas);  
    }  
  
    /** 
     * 初始化尺寸相关 
     */  
    @Override  
    protected void onSizeChanged(int w, int h, int oldw, int oldh)  
    {  
        // 初始化mBird  
        mBird = new Bird(getContext(), mWidth, mHeight, mBirdBitmap);  
  
    }  
  
}  



public class CopyOfGameFlabbyBird extends SurfaceView implements Callback,
Runnable
{
/**
* *********鸟相关**********************
*/
private Bird mBird;
private Bitmap mBirdBitmap;

/**
* 初始化图片
*/
private void initBitmaps()
{
mBg = loadImageByResId(R.drawable.bg1);
mBirdBitmap = loadImageByResId(R.drawable.b1);

}

private void draw()
{
// drawSomething..

drawBg();
drawBird();

}

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

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

}

}
是不是很简单,下面看下此时效果图:

Activity里面这么调用即可:

[java]
view plain
copy

print?





package com.zhy.surfaceViewDemo;  
  
import com.zhy.view.GameFlabbyBird;  
  
import android.app.Activity;  
import android.os.Bundle;  
import android.view.Window;  
import android.view.WindowManager;  
  
public class MainActivity extends Activity  
{  
    GameFlabbyBird mGame;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,  
                WindowManager.LayoutParams.FLAG_FULLSCREEN);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        mGame = new GameFlabbyBird(this);  
        setContentView(mGame);  
  
    }  
  
}  



package com.zhy.surfaceViewDemo;

import com.zhy.view.GameFlabbyBird;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity
{
GameFlabbyBird mGame;

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

}

}


效果图:



不管咋样,我们的鸟已经在指定的位置了~~~有木有一点小激动~~

下面开始添加地板;

3、绘制地板

绘制地板相比来说会难一点,因为我们需要考虑怎么让地板运动,起初我截取了两个大图,希望通过两张图不断变化,产生动画效果,but,动画的太卡,有跳跃感;

于是,我忽然想到了一个东西可以做,我就把基础图变成了这样:



很小的一块图,先不考虑运动,如何填充成我们目标效果呢?

还记得有个类叫做BitmapShader么?我们可以利用它进行填充。

相关知识可以参考:Android
BitmapShader 实战 实现圆形、圆角图片

首先我们依旧是定义一个地板类:Floor

[java]
view plain
copy

print?





package com.zhy.view;  
  
import java.util.concurrent.TimeUnit;  
  
import com.zhy.surfaceViewDemo.Config;  
  
import android.content.Context;  
import android.graphics.Bitmap;  
import android.graphics.BitmapShader;  
import android.graphics.Canvas;  
import android.graphics.Paint;  
import android.graphics.Paint.Style;  
import android.graphics.Shader.TileMode;  
  
public class Floor  
{  
    /* 
     * 地板位置游戏面板高度的4/5到底部 
     */  
    private static final float FLOOR_Y_POS_RADIO = 4 / 5F; // height of 4/5  
  
    /** 
     * x坐标 
     */  
    private int x;  
    /** 
     * y坐标 
     */  
    private int y;  
    /** 
     * 填充物 
     */  
    private BitmapShader mFloorShader;  
  
    private int mGameWidth;  
  
    private int mGameHeight;  
  
    public Floor(int gameWidth, int gameHeight, Bitmap floorBg)  
    {  
        mGameWidth = gameWidth;  
        mGameHeight = gameHeight;  
        y = (int) (gameHeight * FLOOR_Y_POS_RADIO);  
        mFloorShader = new BitmapShader(floorBg, TileMode.REPEAT,  
                TileMode.CLAMP);  
    }  
  
    /** 
     * 绘制自己 
     *  
     * @param mCanvas 
     * @param mPaint 
     */  
    public void draw(Canvas mCanvas, Paint mPaint)  
    {  
        if (-x > mGameWidth)  
        {  
            x = x % mGameWidth;  
        }  
        mCanvas.save(Canvas.MATRIX_SAVE_FLAG);  
        //移动到指定的位置  
        mCanvas.translate(x, y);  
        mPaint.setShader(mFloorShader);  
        mCanvas.drawRect(x, 0, -x + mGameWidth, mGameHeight - y, mPaint);  
        mCanvas.restore();  
        mPaint.setShader(null);  
    }  
  
    public int getX()  
    {  
        return x;  
    }  
  
    public void setX(int x)  
    {  
        this.x = x;  
    }  
  
}  



package com.zhy.view;

import java.util.concurrent.TimeUnit;

import com.zhy.surfaceViewDemo.Config;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Shader.TileMode;

public class Floor
{
/*
* 地板位置游戏面板高度的4/5到底部
*/
private static final float FLOOR_Y_POS_RADIO = 4 / 5F; // height of 4/5

/**
* x坐标
*/
private int x;
/**
* y坐标
*/
private int y;
/**
* 填充物
*/
private BitmapShader mFloorShader;

private int mGameWidth;

private int mGameHeight;

public Floor(int gameWidth, int gameHeight, Bitmap floorBg)
{
mGameWidth = gameWidth;
mGameHeight = gameHeight;
y = (int) (gameHeight * FLOOR_Y_POS_RADIO);
mFloorShader = new BitmapShader(floorBg, TileMode.REPEAT,
TileMode.CLAMP);
}

/**
* 绘制自己
*
* @param mCanvas
* @param mPaint
*/
public void draw(Canvas mCanvas, Paint mPaint)
{
if (-x > mGameWidth)
{
x = x % mGameWidth;
}
mCanvas.save(Canvas.MATRIX_SAVE_FLAG);
//移动到指定的位置
mCanvas.translate(x, y);
mPaint.setShader(mFloorShader);
mCanvas.drawRect(x, 0, -x + mGameWidth, mGameHeight - y, mPaint);
mCanvas.restore();
mPaint.setShader(null);
}

public int getX()
{
return x;
}

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

}


定义了一堆成员变量,核心就在于,我们传入地板背景的填充物,然后初始化我们的mFloorShader,横向重复,纵向拉伸(这里的拉伸是指,纵向的最后一个像素不断重复)。

我们对外公布了draw方法,传入Canvas,我们首先调用canvas.save(),然后将canvas移动到指定的位置,然后绘制我们的矩形,矩形的填充就是我们的地板了~~;

这里,注意一下,我们这里使用了一个变量x,而不是0;为什么呢?因为我们的地板需要利用这个x运动。

那么现在我们如何才能动呢?

首先我们在GameFlabbyBird定义一个变量,表示移动速度mSpeed,然后在draw中不断更新mFloor的x坐标为:mFloor.setX(mFloor.getX() - mSpeed);

这样的画,每次绘制我们floor的起点,会向左移动mSpeed个位置,就形成了运行的效果;但是呢?不能一直减下去,不然最终我们的x岂不是负无穷了,那得绘制多大?

所以我们:

if (-x > mGameWidth)
{
x = x % mGameWidth;
}

如果x的正值大于宽度了,我们取余一下~~~

最终我们的绘制范围是:

mCanvas.drawRect(x, 0, -x + mGameWidth, mGameHeight - y, mPaint);

ok,贴下筛检后GameFlabbyBird代码:

[java]
view plain
copy

print?





package com.zhy.view;  
  
import android.content.Context;  
import android.graphics.Bitmap;  
import android.graphics.BitmapFactory;  
import android.graphics.Canvas;  
import android.graphics.Paint;  
import android.graphics.PixelFormat;  
import android.graphics.RectF;  
import android.util.AttributeSet;  
import android.util.Log;  
import android.view.SurfaceHolder;  
import android.view.SurfaceHolder.Callback;  
import android.view.SurfaceView;  
  
import com.zhy.surfaceViewDemo.R;  
  
public class CopyOfGameFlabbyBird extends SurfaceView implements Callback,  
        Runnable  
{  
    private Paint mPaint;  
    /** 
     * 地板 
     */  
    private Floor mFloor;  
    private Bitmap mFloorBg;  
  
    private int mSpeed;  
  
    public CopyOfGameFlabbyBird(Context context, AttributeSet attrs)  
    {  
        super(context, attrs);  
  
        mPaint = new Paint();  
        mPaint.setAntiAlias(true);  
        mPaint.setDither(true);  
  
        initBitmaps();  
  
        // 初始化速度  
        mSpeed = Util.dp2px(getContext(), 2);  
  
    }  
  
    /** 
     * 初始化图片 
     */  
    private void initBitmaps()  
    {  
        mFloorBg = loadImageByResId(R.drawable.floor_bg2);  
  
    }  
  
    private void draw()  
    {  
  
        // drawSomething..  
  
        drawBg();  
        drawBird();  
        drawFloor();  
  
        // 更新我们地板绘制的x坐标  
        mFloor.setX(mFloor.getX() - mSpeed);  
  
    }  
  
    private void drawFloor()  
    {  
        mFloor.draw(mCanvas, mPaint);  
    }  
  
    @Override  
    protected void onSizeChanged(int w, int h, int oldw, int oldh)  
    {  
        // 初始化地板  
        mFloor = new Floor(mWidth, mHeight, mFloorBg);  
  
    }  
  
}  



package com.zhy.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;

import com.zhy.surfaceViewDemo.R;

public class CopyOfGameFlabbyBird extends SurfaceView implements Callback,
Runnable
{
private Paint mPaint;
/**
* 地板
*/
private Floor mFloor;
private Bitmap mFloorBg;

private int mSpeed;

public CopyOfGameFlabbyBird(Context context, AttributeSet attrs)
{
super(context, attrs);

mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);

initBitmaps();

// 初始化速度
mSpeed = Util.dp2px(getContext(), 2);

}

/**
* 初始化图片
*/
private void initBitmaps()
{
mFloorBg = loadImageByResId(R.drawable.floor_bg2);

}

private void draw()
{

// drawSomething..

drawBg();
drawBird();
drawFloor();

// 更新我们地板绘制的x坐标
mFloor.setX(mFloor.getX() - mSpeed);

}

private void drawFloor()
{
mFloor.draw(mCanvas, mPaint);
}

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

}

}


其实很简单,就是声明几个变量,初始化一下;记得在draw中更新mFloor的x即可。

现在的效果:



好了,最后剩下个管道了~~~

4、绘制管道

然后是写搞一个管道类Pipe,注意我们的管道分为上下,每个管道的高度可能不同,所以会多一些成员变量;

[java]
view plain
copy

print?





package com.zhy.view;  
  
import java.util.Random;  
  
import android.content.Context;  
import android.graphics.Bitmap;  
import android.graphics.Canvas;  
import android.graphics.RectF;  
  
/** 
 * 管道分为上下 
 *  
 * @author zhy 
 *  
 */  
public class Pipe  
{  
    /** 
     * 上下管道间的距离 
     */  
    private static final float RADIO_BETWEEN_UP_DOWN = 1 / 5F;  
    /** 
     * 上管道的最大高度 
     */  
    private static final float RADIO_MAX_HEIGHT = 2 / 5F;  
    /** 
     * 上管道的最小高度 
     */  
    private static final float RADIO_MIN_HEIGHT = 1 / 5F;  
    /** 
     * 管道的横坐标 
     */  
    private int x;  
    /** 
     * 上管道的高度 
     */  
    private int height;  
    /** 
     * 上下管道间的距离 
     */  
    private int margin;  
    /** 
     * 上管道图片 
     */  
    private Bitmap mTop;  
    /** 
     * 下管道图片 
     */  
    private Bitmap mBottom;  
  
    private static Random random = new Random();  
  
    public Pipe(Context context, int gameWidth, int gameHeight, Bitmap top,  
            Bitmap bottom)  
    {  
        margin = (int) (gameHeight * RADIO_BETWEEN_UP_DOWN);  
        // 默认从最左边出现  
        x = gameWidth;  
  
        mTop = top;  
        mBottom = bottom;  
  
        randomHeight(gameHeight);  
  
    }  
  
    /** 
     * 随机生成一个高度 
     */  
    private void randomHeight(int gameHeight)  
    {  
        height = random  
                .nextInt((int) (gameHeight * (RADIO_MAX_HEIGHT - RADIO_MIN_HEIGHT)));  
        height = (int) (height + gameHeight * RADIO_MIN_HEIGHT);  
    }  
  
    public void draw(Canvas mCanvas, RectF rect)  
    {  
        mCanvas.save(Canvas.MATRIX_SAVE_FLAG);  
        // rect为整个管道,假设完整管道为100,需要绘制20,则向上偏移80  
        mCanvas.translate(x, -(rect.bottom - height));  
        mCanvas.drawBitmap(mTop, null, rect, null);  
        // 下管道,便宜量为,上管道高度+margin  
        mCanvas.translate(0, (rect.bottom - height) + height + margin);  
        mCanvas.drawBitmap(mBottom, null, rect, null);  
        mCanvas.restore();  
    }  
  
    public int getX()  
    {  
        return x;  
    }  
  
    public void setX(int x)  
    {  
        this.x = x;  
    }  
  
}  



package com.zhy.view;

import java.util.Random;

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

/**
* 管道分为上下
*
* @author zhy
*
*/
public class Pipe
{
/**
* 上下管道间的距离
*/
private static final float RADIO_BETWEEN_UP_DOWN = 1 / 5F;
/**
* 上管道的最大高度
*/
private static final float RADIO_MAX_HEIGHT = 2 / 5F;
/**
* 上管道的最小高度
*/
private static final float RADIO_MIN_HEIGHT = 1 / 5F;
/**
* 管道的横坐标
*/
private int x;
/**
* 上管道的高度
*/
private int height;
/**
* 上下管道间的距离
*/
private int margin;
/**
* 上管道图片
*/
private Bitmap mTop;
/**
* 下管道图片
*/
private Bitmap mBottom;

private static Random random = new Random();

public Pipe(Context context, int gameWidth, int gameHeight, Bitmap top,
Bitmap bottom)
{
margin = (int) (gameHeight * RADIO_BETWEEN_UP_DOWN);
// 默认从最左边出现
x = gameWidth;

mTop = top;
mBottom = bottom;

randomHeight(gameHeight);

}

/**
* 随机生成一个高度
*/
private void randomHeight(int gameHeight)
{
height = random
.nextInt((int) (gameHeight * (RADIO_MAX_HEIGHT - RADIO_MIN_HEIGHT)));
height = (int) (height + gameHeight * RADIO_MIN_HEIGHT);
}

public void draw(Canvas mCanvas, RectF rect)
{
mCanvas.save(Canvas.MATRIX_SAVE_FLAG);
// rect为整个管道,假设完整管道为100,需要绘制20,则向上偏移80
mCanvas.translate(x, -(rect.bottom - height));
mCanvas.drawBitmap(mTop, null, rect, null);
// 下管道,便宜量为,上管道高度+margin
mCanvas.translate(0, (rect.bottom - height) + height + margin);
mCanvas.drawBitmap(mBottom, null, rect, null);
mCanvas.restore();
}

public int getX()
{
return x;
}

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

}


我们直接看draw方法,我们的传入的rect是固定的一个矩形,我们的上下管道都是完整的绘制在这个rect中;

然后根据height,去偏移canvas的y,让rect显示出height部分,主要是因为,这样可以保证每个管道样子是一样的(如果根据height,使用不同的rect,会产生缩放);

Pipe写好了~~我们需要在GameFlabbyBird中去使用;但是考虑一下,游戏中的管道不像鸟和地面,有很多个,且是在运行中不断生成新的~~~

所以我们保存Pipe最起码是个List<Pipe>

筛检后的代码:

[java]
view plain
copy

print?





public class GameFlabbyBird extends SurfaceView implements Callback, Runnable  
{  
  
    /** 
     * *********管道相关********************** 
     */  
    /** 
     * 管道 
     */  
    private Bitmap mPipeTop;  
    private Bitmap mPipeBottom;  
    private RectF mPipeRect;  
    private int mPipeWidth;  
    /** 
     * 管道的宽度 60dp 
     */  
    private static final int PIPE_WIDTH = 60;  
  
    private List<Pipe> mPipes = new ArrayList<Pipe>();  
  
    public GameFlabbyBird(Context context, AttributeSet attrs)  
    {  
        super(context, attrs);  
        mPipeWidth = Util.dp2px(getContext(), PIPE_WIDTH);  
  
    }  
  
    /** 
     * 初始化图片 
     */  
    private void initBitmaps()  
    {  
        ;  
        mPipeTop = loadImageByResId(R.drawable.g2);  
        mPipeBottom = loadImageByResId(R.drawable.g1);  
    }  
  
    private void draw()  
    {  
  
        drawBg();  
        drawBird();  
        drawPipes();  
        drawFloor();  
  
    }  
  
    /** 
     * 绘制管道 
     */  
    private void drawPipes()  
    {  
        for (Pipe pipe : mPipes)  
        {  
            pipe.setX(pipe.getX() - mSpeed);  
            pipe.draw(mCanvas, mPipeRect);  
        }  
    }  
  
    /** 
     * 初始化尺寸相关 
     */  
    @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);  
  
    }  
  
}  



public class GameFlabbyBird extends SurfaceView implements Callback, Runnable
{

/**
* *********管道相关**********************
*/
/**
* 管道
*/
private Bitmap mPipeTop;
private Bitmap mPipeBottom;
private RectF mPipeRect;
private int mPipeWidth;
/**
* 管道的宽度 60dp
*/
private static final int PIPE_WIDTH = 60;

private List<Pipe> mPipes = new ArrayList<Pipe>();

public GameFlabbyBird(Context context, AttributeSet attrs)
{
super(context, attrs);
mPipeWidth = Util.dp2px(getContext(), PIPE_WIDTH);

}

/**
* 初始化图片
*/
private void initBitmaps()
{
;
mPipeTop = loadImageByResId(R.drawable.g2);
mPipeBottom = loadImageByResId(R.drawable.g1);
}

private void draw()
{

drawBg();
drawBird();
drawPipes();
drawFloor();

}

/**
* 绘制管道
*/
private void drawPipes()
{
for (Pipe pipe : mPipes)
{
pipe.setX(pipe.getX() - mSpeed);
pipe.draw(mCanvas, mPipeRect);
}
}

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

}

}


我们在onSizeChanged中初始化了一个Pipe,添加到了mPipes中,然后在draw里面,动态改变Pipe的x为pipe.setX(pipe.getX() - mSpeed);

下面来看下效果:



我们的管道从右侧进入界面,然后消失在左侧~

当然了,关于管道还有很多需要编写,比如管道每隔多远生成一个,也不能让无限生成,当管道从界面移除应该从mPipes中移出;

以及判断管道和鸟的碰撞,这些都放置到下一篇博客叙述~~

5、绘制分数

分数的绘制比较简单,我准备了10个图,对应于0-9

没有单独定义类了,直接写了~~

筛检后的代码:

[java]
view plain
copy

print?





public class GameFlabbyBird extends SurfaceView implements Callback,  
        Runnable  
{  
    /** 
     * 分数 
     */  
    private final int[] mNums = new int[] { R.drawable.n0, R.drawable.n1,  
            R.drawable.n2, R.drawable.n3, R.drawable.n4, R.drawable.n5,  
            R.drawable.n6, R.drawable.n7, R.drawable.n8, R.drawable.n9 };  
    private Bitmap[] mNumBitmap;  
    private int mGrade = 100;  
    /** 
     * 单个数字的高度的1/15 
     */  
    private static final float RADIO_SINGLE_NUM_HEIGHT = 1 / 15f;  
    /** 
     * 单个数字的宽度 
     */  
    private int mSingleGradeWidth;  
    /** 
     * 单个数字的高度 
     */  
    private int mSingleGradeHeight;  
    /** 
     * 单个数字的范围 
     */  
    private RectF mSingleNumRectF;  
  
    /** 
     * 初始化图片 
     */  
    private void initBitmaps()  
    {  
  
        mNumBitmap = new Bitmap[mNums.length];  
        for (int i = 0; i < mNumBitmap.length; i++)  
        {  
            mNumBitmap[i] = loadImageByResId(mNums[i]);  
        }  
    }  
  
    private void draw()  
    {  
        // drawSomething..  
  
        drawBg();  
        drawBird();  
        drawPipes();  
        drawFloor();  
        drawGrades();  
  
    }  
    /** 
     * 绘制分数 
     */  
    private void drawGrades()  
    {  
        String grade = mGrade + "";  
        mCanvas.save(Canvas.MATRIX_SAVE_FLAG);  
        mCanvas.translate(mWidth / 2 - grade.length() * mSingleGradeWidth / 2,  
                1f / 8 * mHeight);  
        // draw single num one by 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);  
  
    }  
  
  
  
}  



public class GameFlabbyBird extends SurfaceView implements Callback,
Runnable
{
/**
* 分数
*/
private final int[] mNums = new int[] { R.drawable.n0, R.drawable.n1,
R.drawable.n2, R.drawable.n3, R.drawable.n4, R.drawable.n5,
R.drawable.n6, R.drawable.n7, R.drawable.n8, R.drawable.n9 };
private Bitmap[] mNumBitmap;
private int mGrade = 100;
/**
* 单个数字的高度的1/15
*/
private static final float RADIO_SINGLE_NUM_HEIGHT = 1 / 15f;
/**
* 单个数字的宽度
*/
private int mSingleGradeWidth;
/**
* 单个数字的高度
*/
private int mSingleGradeHeight;
/**
* 单个数字的范围
*/
private RectF mSingleNumRectF;

/**
* 初始化图片
*/
private void initBitmaps()
{

mNumBitmap = new Bitmap[mNums.length];
for (int i = 0; i < mNumBitmap.length; i++)
{
mNumBitmap[i] = loadImageByResId(mNums[i]);
}
}

private void draw()
{
// drawSomething..

drawBg();
drawBird();
drawPipes();
drawFloor();
drawGrades();

}
/**
* 绘制分数
*/
private void drawGrades()
{
String grade = mGrade + "";
mCanvas.save(Canvas.MATRIX_SAVE_FLAG);
mCanvas.translate(mWidth / 2 - grade.length() * mSingleGradeWidth / 2,
1f / 8 * mHeight);
// draw single num one by 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);

}

}


我们定义了单个数字的范围,然后假设现在为100分,注意在绘制的时候,直接提取数字,把数字作为下标,找到对于的图片进行绘制;

绘制前,根据数字的位数,对画布进行偏移到中心位置,然后绘制;绘制过程中,每绘制完成一个数字则偏移一个数字的宽度;

现在的效果:



ok,到此为止,我们完成了所有需要绘制的东西~~由于篇幅原因,下一篇,将在此基础上完善剩下的所有内容~~~

有兴趣的,可以在此基础上直接尝试写了~~~

源码点击下载

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: