目录
1.我们单独新建一个类,命名为Gamebird,在这里使用SurfaceView.. 1
2.在MainActivity中显示SurfaceView: 4
1.将背景图片放至在res/drawable文件目录下... 5
2.为背景初始化变量,从drawable中将图片读取出来... 5
1.我们先来看看flappy bird是什么样子的
可以在这个网站上看一下电脑版的http://flappybird.io/
将它总结为一个gif是这样的
观察上面的gif,在“绘制游戏”方面,应该绘制那些呢?
1>背景2>地面3>鸟4>管子5>分数
还能注意到什么?
1>鸟自动下落,点击屏幕之后会升高一段距离
2>鸟撞到管子后游戏会结束
本文借鉴自:
https://blog.csdn.net/lmj623565791/article/details/42965779
一、游戏用到的是SurfaceView
SurfaceView的窗口刷新的时候不需要重绘应用程序的窗口而android普通窗口的视图绘制机制是一层一层的,任何一个子元素或者是局部的刷新都会导致整个视图结构全部重绘一次。
1.我们单独新建一个类,命名为Gamebird,在这里使用SurfaceView
SurfaceView的一般写法为:
package com.example.flappybird;
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 Gamebird extends SurfaceView implements Callback, Runnable
{
private SurfaceHolder mHolder;
/**
* 与SurfaceHolder绑定的Canvas
*/
private Canvas mCanvas;
/**
* 用于绘制的线程
*/
private Thread t;
/**
* 线程的控制开关
*/
private boolean isRunning;
public Gamebird(Context context)
{
this(context, null);
}
public Gamebird(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);
}
}
}
2.在MainActivity中显示SurfaceView:
public class B_MainActivity extends AppCompatActivity {
Gamebird mGame;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
requestWindowFeature(Window.FEATURE_NO_TITLE);
mGame=new Gamebird(this);
setContentView(mGame);
}
3.效果
二、绘制背景
1.将背景图片放至在res/drawable文件目录下
2.为背景初始化变量,从drawable中将图片读取出来
Bitmap background_bitmap
=
BitmapFactory.decodeResource(getResources(),R.drawable.bg1);
3.绘制图像:
//在指定位置绘制指定大小的bitmap
* @param bitmap The bitmap to be drawn
//位图
* @param src May be null. The subset of the bitmap to be drawn
//bitmap需要绘制的面积,若src的面积小于bitmap时会对bitmap进行裁剪,一般来说需要绘制整个bitmap时可以为null
* @param dst The rectangle that the bitmap will be scaled/translated to fit into
//在画布中指定绘制bitmap的位置,当这个区域的面积与bitmap要显示的面积不匹配时,会进行缩放,不可为null
* @param paint May be null. The paint used to draw the bitmap
//画笔
*/
public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,
@Nullable Paint paint) {
super.drawBitmap(bitmap, src, dst, paint);
}
* 使用指定的矩阵绘制位图:
/*
* @param
* bitmap 位图
* matrix 当绘制位图时需要转变时使用的矩阵
* paint 画笔
*/
public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
super.drawBitmap(bitmap, matrix, paint);
}
4.分析成员变量
----这一部分代码在继承了SurfaceView的类中添加----
由上面的drawBitmap方法我们知道:
1>背景肯定是与当前的View绘制成一样大小,所以我们需要得到View的尺寸,width,height
2>需要一个RecF类成员来设置绘画区域
3>Bitmap背景图成员
//背景相关
private Bitmap mBackgroud_bitmap;
private int mWidth,mHeight;
private RectF mGamePanelRect=new RectF();
5.初始化Bitmap:
我们可以单独写方法加载、初始化图片
//加载图片
private Bitmap loadBitmapByResId(int resId){
return BitmapFactory.decodeResource(getResources(), resId);
}
//初始化图片
private void initBitmap(){
mBackgroud_bitmap=loadBitmapByResId(R.drawable.bg1);
}
6.在Surface构建时初始化Bitmap:
public Gamebird(Context context, AttributeSet attrs)
{
//....省略之前的代码
initBitmap();
}
7.设置背景长宽,初始化尺寸:
@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);
}
8.画背景
//画背景
private void drawBg(){
mCanvas.drawBitmap(mBackgroud_bitmap,null,mGamePanelRect,null);
}
private void draw()
{//省略一些代码
if (mCanvas != null)
{
// drawSomething..
drawBg();
}
//省略一些代码
}
9.效果:
三、绘制鸟:
1.同上面的背景一样,我们需要知道鸟的变量有
1>鸟这个图像的宽度、高度
2>鸟的位图Bitmap
3>RecF类对象(鸟绘制的范围)
4>由于鸟在游戏中的位置是在变化的,所以我们还需要知道鸟的图像的横纵坐标X,Y
----这一部分代码在新建的Bird类内添加----
//画背景
private void drawBg(){
mCanvas.drawBitmap(mBackgroud_bitmap,null,mGamePanelRect,null);
}
private void draw()
{//省略一些代码
if (mCanvas != null)
{
// drawSomething..
drawBg();
}
//省略一些代码
}
2.构造方法,初始化鸟的对象
//初始化鸟的位置、计算鸟的宽度和高度
----这一部分代码在新建的Bird类内添加----
public Bird(Context context,int gameWidth,int gameHeight,Bitmap bitmap){
this.bitmap=bitmap;
//鸟的位置
x=(gameWidth/2)-(bitmap.getWidth()/2);
y=(int)(gameHeight*RADIO_POS_HEIGHT);
//计算鸟的宽度和高度
/*Util是工具类,将dp转为px
dp:Density-independent pixels,以160PPI屏幕为标准,则1dp=1px,dp和px的换算公式 :
dp*ppi/160 = px。比如1dp x 320ppi/160 = 2px。
*/
mWidth=Util.dp2px(context,BIRD_SIZE);
mHeight=(int)(mWidth*1.0f/bitmap.getWidth()*bitmap.getHeight());
}
Util//新的类
package com.example.flappybird;
import android.content.Context;
import android.util.TypedValue;
/**
* 工具类
*/
public class Util {
/**
*
*/
public static int dp2px(Context context,float dp){
int px=Math.round(TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,dp,context.getResources().getDisplayMetrics()));
return px;
}
}
3.画鸟//还记得怎么画背景的吗
----这一部分代码在新建的Bird类内添加----
public void draw(Canvas canvas){
rect.set(x,y,x+mWidth,y+mHeight);
canvas.drawBitmap(bitmap,null,rect,null);
}
4.在Game_bird中加上鸟的相关
1>Bird类实例初始化
2>Bitmap初始化
3>drawBird()方法
4>在draw()中调用drawBird
//鸟相关
private Bird bird;
private Bitmap mBird_bitmap;
//初始化图片
private void initBitmap(){
mBackgroud_bitmap=loadBitmapByResId(R.drawable.bg1);
mBird_bitmap=loadBitmapByResId(R.drawable.b1);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh){
// 初始化mBird
bird = new Bird(getContext(), mWidth, mHeight, mBird_bitmap);
}
private void drawBird(){
bird.draw(mCanvas);
}
private void draw(){
if (mCanvas != null) {
// drawSomething..
drawBg();
drawBird();
}
}
5.效果
四、画管道
新建一个Pipe类:
1.管道都需要什么呢?
1>上管道高度
//这里考虑到管道长度是随机生成的,设置一个最大值、最小值
2>上管道最大高度4/5F
3>上管道最小高度1/5F
4>管道间隙长度
//下管道长度可以用
背景长度-(上管道高度+管道间隙长度)计算,所以不设置
5>上下管道的图片
6>管道横坐标
/*** 上下管道的距离 */
private static final float RADIO_BETWEEN_UP_DOWN=1/5F;
/** * 最大最小值*/
private static final float RADIO_MAX_HEIGHT=2/5F;
private static final float RASIO_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();
2.为管道随机生成一个高度:
private void randomHeight(int gameHeight){
height=random.nextInt((int)(gameHeight*(RADIO_MAX_HEIGHT-RASIO_MIN_HEIGHT)));
height=(int)(height+gameHeight*RASIO_MIN_HEIGHT);
}
3.构造函数:
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);
}
4.画自己:
public void draw(Canvas mCanvas, RectF rectF){
mCanvas.save();
//rect为整个管道
mCanvas.translate(x,-(rectF.bottom-height));
mCanvas.drawBitmap(mTop,null,rectF,null);
//下管道
mCanvas.translate(0,(rectF.bottom-height)+height+margin);
mCanvas.drawBitmap(mBottom,null,rectF,null);
mCanvas.restore();
}
5.像三中那样,在主类中初始化自己:
1>Pipe类实例初始化,这里考虑到Pipe不是一个,所以设置为一个List存放Pipe
2>Bitmap初始化
3>drawPipe()方法
4>在draw()中调用drawPipe
5>管道是在移动的,所以我们设置一个speed变量
//管道相关
private Bitmap mPipeTop;
private Bitmap mPipeBottom;
private RectF mPipeRect;
private int mPipeWidth;
//管道图像宽度
private static final int PIPE_WIDTH = 60;
private List<Pipe> mPipes = new ArrayList<Pipe>();
//初始化图片
private void initBitmap(){
//ADD THIS
mPipeTop=loadBitmapByResId(R.drawable.g2);
mPipeBottom=loadBitmapByResId(R.drawable.g1);
}
public Gamebird(Context context, AttributeSet attrs){
//ADD THIS
//初始化管道宽度
mPipeWidth=Util.dp2px(getContext(),PIPE_WIDTH);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh){
//ADD THIS
// 初始化速度
mSpeed = Util.dp2px(getContext(), 2);
// 初始化管道范围
mPipeRect = new RectF(0, 0, mPipeWidth, mHeight);
Pipe pipe = new Pipe(getContext(), w, h, mPipeTop, mPipeBottom);
mPipes.add(pipe);
}
//画管道
private void drawPipes(){
//画出管道List中的每一个
for(Pipe pipe:mPipes){
pipe.setX(pipe.getX()-mSpeed);
pipe.draw(mCanvas,mPipeRect);
}
}
private void draw(){
if (mCanvas != null) {
// Add this.
drawPipes();
}
}
6.效果