1、概述
哈,记得以前写过Android SurfaceView实战 打造抽奖转盘 , 同属于SurfaceView系列,基本可以从这篇博文中学习到SurfaceView的用法,以及利用SurfaceView做抽奖转盘。但是其中缺少一部分的知识点,就是与用户交互时界面的改变,所以今天给大家再带来本篇博文教大家如何做flabby bird这款游戏,这游戏虽然不难,但是也为其作者挣了不少钱,大家在学会以后,可以尽可能发挥自己的创意,做属于自己的游戏,说不定下一个火的奏是你。
ok,那么首先上下效果图:
由于上传图片最大限制为2M,所以做了压缩处理,凑合看吧 ~~~
2、分析
仔细观察游戏,需要绘制的有:背景、地板、鸟、管道、分数;
游戏开始时:
地板给人一种想左移动的感觉;
管道与地板同样的速度向左移动;
鸟默认下落;
当用户touch屏幕时,鸟上升一段距离后,下落;
运动过程中需要判断管道和鸟之间的位置关系,是否触碰,是否穿过等,需要计算分数。
好了,大概就这么多,那我们首先开始考虑绘制~~~
3、SurfaceView的一般写法
接下来,我们首先编写下SurfaceView的一般写法:
- 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;
2、绘制bird
鸟在我们的屏幕中,初始化时需要一个位置,x上,肯定是居中,y上我们取2/3的高度;
关于bird,我们单独创建一个类:
定义了一个类,代表我们的鸟,以及一堆成员变量,并且提供一个draw方法对外;
在GameFlabbyBird中,只需要,初始化我们的Bird,在draw里面调用bird.draw即可;
部分筛检后代码:
是不是很简单,下面看下此时效果图
Activity里面这么调用即可:
- 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
- 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;
- }
- }
我们对外公布了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 = x % mGameWidth;
}
如果x的正值大于宽度了,我们取余一下~~~
最终我们的绘制范围是:
mCanvas.drawRect(x, 0, -x + mGameWidth, mGameHeight - y, mPaint);
ok,贴下筛检后GameFlabbyBird代码:
- 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);
- }
- }
好了,最后剩下个管道了~~~
4、绘制管道
然后是写搞一个管道类Pipe,注意我们的管道分为上下,每个管道的高度可能不同,所以会多一些成员变量;
- 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>
筛检后的代码:
我们在onSizeChanged中初始化了一个Pipe,添加到了mPipes中,然后在draw里面,动态改变Pipe的x为pipe.setX(pipe.getX() - mSpeed);
下面来看下效果:
我们的管道从右侧进入界面,然后消失在左侧~
当然了,关于管道还有很多需要编写,比如管道每隔多远生成一个,也不能让无限生成,当管道从界面移除应该从mPipes中移出;
以及判断管道和鸟的碰撞,这些都放置到下一篇博客叙述~~
5、绘制分数
分数的绘制比较简单,我准备了10个图,对应于0-9
没有单独定义类了,直接写了~~
筛检后的代码:
沒有留言:
張貼留言