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

1、概述

在Android SurfaceView实战 带你玩转flabby bird (上)中,我们完成了在游戏所需的所有的元素的绘制,包括 Bird鸟、 Floor地板、Pipe 管道 、背景图以及分数等。

本篇博客将在上篇的基本上,继续带领大家向我们的目标进发,那么问题来了,我们的目标是:

就是这个效果图了。

首先我们明确下,当然我们的状态与上图的差距:

1、我们的管道只有一个,现需要动态生成,以及动态移除;

2、我们的鸟静止,现需要默认下落,按下屏幕上升一段距离;

3、现需要,判断鸟在飞翔过程中与管道以及地面的接触情况,判断是否Gameover;

4、现需要,当鸟每穿过一个管道的时候,我们能够进行计分+1 ;

ok,明确了目标~~~

那么首先,根据上述,我们发现我们的游戏缺少一个什么?

嗯,是状态,我们得知道游戏什么时候正在运行,什么时候准备运行,什么时候GameOver吧。所以引入一个枚举变量:

/*** 游戏的状态* * @author zhy* */private enum GameStatus{WAITING, RUNNING, OVER}

1、默认情况下,是WAITING状态,屏幕静止,上面就一只静止的鸟~~

2、当用户触摸屏幕时:进入RUNNING状态,游戏开始根据用户的触摸情况进行交互;

3、当鸟触碰到管道或者落到地上,那么进入GAMEOVER状态,OVER时,如果触碰的是管道,则让鸟落到地上以后,立即切换为WAITING状态。

好了,这样,我们的三个状态就搞定了~~这才像个游戏么~

有了状态 ,我们再考虑如何处理用户交互,我们的交互主要就是触摸了,那么我们去重写View的onTouchEvent方法即可。

我们在onTouchEvent里面,根据用户的触摸游戏的状态、一些变量等;改变了的变量,会在绘制的时候进行体现,这样才形成了交互效果。

然后呢?为了我们代码的清晰,我们本来在线程中只有一个draw()方法,现在我们增加一个方法:logic();处理在游戏过程中分数的计算、管道的生成移除等。

这样可以把逻辑分开,logic专心做一些逻辑上的事,draw只管绘制;

说了这么多,如果你木有消化,没事,下面我们开始进入代码阶段了~~

2、动态生成和移除管道

首先我们增加一个mStatus变量:private GameStatus mStatus = GameStatus.WAITING;

然后添加logic方法以及复写onTouchEvent方法。

经过筛检后的代码:

public class CopyOfGameFlabbyBird extends SurfaceView implements Callback,Runnable
{//省略了一些代码private enum GameStatus{WAITTING, RUNNING, STOP;}/*** 记录游戏的状态*/private GameStatus mStatus = GameStatus.WAITTING;/*** 触摸上升的距离,因为是上升,所以为负值*/private static final int TOUCH_UP_SIZE = -16;/*** 将上升的距离转化为px;这里多存储一个变量,变量在run中计算* */private final int mBirdUpDis = Util.dp2px(getContext(), TOUCH_UP_SIZE);private int mTmpBirdDis;/*** 鸟自动下落的距离*/private final int mAutoDownSpeed = Util.dp2px(getContext(), 2);/*** 处理一些逻辑上的计算*/private void logic(){switch (mStatus){case RUNNING:// 管道移动for (Pipe pipe : mPipes){pipe.setX(pipe.getX() - mSpeed);}// 更新我们地板绘制的x坐标,地板移动mFloor.setX(mFloor.getX() - mSpeed);break;case STOP: // 鸟落下break;default:break;}}@Overridepublic boolean onTouchEvent(MotionEvent event){int action = event.getAction();if (action == MotionEvent.ACTION_DOWN){switch (mStatus){case WAITTING:mStatus = GameStatus.RUNNING;break;case RUNNING:mTmpBirdDis = mBirdUpDis;break;}}return true;}@Overridepublic void run(){while (isRunning){// 省略了一些代码logic();draw();// 省略了一些代码}}}

可以看到,我们在run中增加调用了logic()方法,在onTouch中根据用户DOWN,改变状态或者设置mTmpBirdDis即为每次用户点击时,鸟上升的距离,接下来会实现。

还有一点,我们把更新管道的x坐标,从drawFloor中提取了出来;以及更新mFloor的x坐标从draw中提取到logic();draw目前,只管绘制,不管任何事。

现在我们的游戏,启动后画面静止,用户触摸后开始移动;

当然了,现在依旧是一个管道,接下来,我们来动态添加管道:

管道的添加:

对于管道的添加,我准备每隔300dp生成一个管道;

当管道移动出屏幕,我们将其从List中移除,避免不必要的绘制;

那么怎么做呢?

/*** 两个管道间距离*/private final int PIPE_DIS_BETWEEN_TWO = Util.dp2px(getContext(), 300);/*** 记录移动的距离,达到 PIPE_DIS_BETWEEN_TWO 则生成一个管道*/private int mTmpMoveDistance;/*** 处理一些逻辑上的计算*/private void logic(){switch (mStatus){case RUNNING:// 管道mTmpMoveDistance += mSpeed;// 生成一个管道if (mTmpMoveDistance >= PIPE_DIS_BETWEEN_TWO){Pipe pipe = new Pipe(getContext(), getWidth(), getHeight(),mPipeTop, mPipeBottom);mPipes.add(pipe);mTmpMoveDistance = 0;}break;case STOP: // 鸟落下break;default:break;}}

很简单,添加两个变量,然后在logic中,如果是RUNNING状态,记录移动的距离,达到我们预设的值300dp就增加一个。

现在我们的效果(记得删除之前我们在onSizeChanged中添加的那个管道,没必要了,我们已经动态生成了)

可以看到,一开始状态WAITTING,当我们点击后,地板开始移动,管道开始动态添加并移动~~~

那么,现在有一个问题,我们的管道现在动态添加了,随着游戏的运行,我们的管道肯定无限多呀,当然了,我这种超不过10分的渣渣,这个问题是不会出现的。

无限多,即使不崩,估计也卡,那么多管道看不到了,干嘛绘制呢?

那么我们该如何移除这些不在屏幕上的管道呢?

管道的移除:

很简单,看代码:

/*** 记录需要移除的管道*/private List<Pipe> mNeedRemovePipe = new ArrayList<Pipe>();/*** 处理一些逻辑上的计算*/private void logic(){switch (mStatus){case RUNNING:// 更新我们地板绘制的x坐标,地板移动mFloor.setX(mFloor.getX() - mSpeed);// 管道移动for (Pipe pipe : mPipes){if (pipe.getX() < -mPipeWidth){mNeedRemovePipe.add(pipe);continue;}pipe.setX(pipe.getX() - mSpeed);}//移除管道mPipes.removeAll(mNeedRemovePipe);Log.e("TAG", "现存管道数量:" + mPipes.size());// 管道mTmpMoveDistance += mSpeed;// 生成一个管道if (mTmpMoveDistance >= PIPE_DIS_BETWEEN_TWO){Pipe pipe = new Pipe(getContext(), getWidth(), getHeight(),mPipeTop, mPipeBottom);mPipes.add(pipe);mTmpMoveDistance = 0;}break;case STOP: // 鸟落下break;default:break;}}

其实就增加了几行代码,为了好理解,贴出代码较多;我们增加了一个变量mNeedRemovePipe,在遍历Pipes的时候,如果x左边已经小于-mPipeWidth时候,说明看不到了,那么就防到mNeedRemovePipe中;

最后统一移除mNeedRemovePipe。

有人会说,为啥要多创建个mNeedRemovePipe呢?你for循环移除不就行了~嗯,这样是不行的,会报错;

又有人说,我知道那样会报错,但是你可以用CopyOnWriteArrayList这类安全的List,就能for循环时,移除了~~嗯,这样是可以,但是这类List的方法中为了安全,各种clone,势必造成运行速度慢~~我们这里是游戏,千万要避免不必要的速度丢失~~~

好了~~现在管道,彻底没什么问题了~~~

接下来,我们的鸟该动了~~

3、让鸟飞起来

其实特别简单,就两行代码,短的我都不好意思独立章节了~~

private void logic(){switch (mStatus){case RUNNING:// 更新我们地板绘制的x坐标,地板移动mFloor.setX(mFloor.getX() - mSpeed);logicPipe();//默认下落,点击时瞬间上升mTmpBirdDis += mAutoDownSpeed;mBird.setY(mBird.getY() + mTmpBirdDis);break;case STOP: // 鸟落下break;default:break;}}

logic中添加两行就行了~~

mTmpBirdDis += mAutoDownSpeed;
mBird.setY(mBird.getY() + mTmpBirdDis);

默认情况越降越快,当用户点击的时候瞬间上升一段距离,继续往下掉~

我把管道相关的抽取出去了~~

现在的效果:

好了,可以看到我们的鸟终于可以交互了~~上面的动画比较快~~见谅,据说这样图能小一点~

现在,我们已经实现了绝大部分功能了,只剩下一个判断GameOver和计分了~~~let's go !

4、GameOver or Grades++

对于失败的判断,我觉得很简单呀,直接在遍历管道的时候,去判断管道的和鸟是否触碰~~或者鸟的y坐标是否触地~~

在logic中调用checkGameOver()

private void checkGameOver(){// 如果触碰地板,ggif (mBird.getY() > mFloor.getY() - mBird.getHeight()){mStatus = GameStatus.STOP;}// 如果撞到管道for (Pipe wall : mPipes){//已经穿过的if (wall.getX() + mPipeWidth < mBird.getX()){continue;}if (wall.touchBird(mBird)){mStatus = GameStatus.STOP;break;}}}

如果碰到地面gg,如果和管道碰到gg;

public class Pipe
{//.../*** 判断和鸟是否触碰* @param mBird* @return*/public boolean touchBird(Bird mBird){/*** 如果bird已经触碰到管道*/if (mBird.getX() + mBird.getWidth() > x&& (mBird.getY() < height || mBird.getY() + mBird.getHeight() > height+ margin)){return true;}return false;}}

我们在管道中添加了touchBird用于进行判断~~很简单,鸟如果在管道的范围内,如果不在管道的空隙中则为true~~

好了,现在运行代码,发现我们的鸟如果碰到管道或者落地就OVER了~~但是OVER以后,再也不会动了~~

维萨呢?因为OVER后,我们的状态是STOP,而STOP我们没有做任何处理~~

我们应该在STOP中去判断,如果没有落地让鸟落地,然后切换状态为WAITTING~

private void logic(){switch (mStatus){case RUNNING:case STOP: // 鸟落下// 如果鸟还在空中,先让它掉下来if (mBird.getY() < mFloor.getY() - mBird.getWidth()){mTmpBirdDis += mAutoDownSpeed;mBird.setY(mBird.getY() + mTmpBirdDis);} else{mStatus = GameStatus.WAITTING;initPos();}break;default:break;}}/*** 重置鸟的位置等数据*/private void initPos(){mPipes.clear();mNeedRemovePipe.clear();//重置鸟的位置mBird.setY(mHeight * 2 / 3);//重置下落速度mTmpBirdDis = 0;                                                                                                                                          mTmpMoveDistance = 0 ;}

如果STOP,让鸟继续落,到地面了则直接切换为WAITING,同时记得重置一些必要的数据;

最后就剩下分数的计算了~~~

偶也~~

分数的计算:

还好分数的计算也比较简单~~~

/*** 处理一些逻辑上的计算*/private void logic(){switch (mStatus){case RUNNING:mGrade = 0;// 更新我们地板绘制的x坐标,地板移动mFloor.setX(mFloor.getX() - mSpeed);logicPipe();// 默认下落,点击时瞬间上升mTmpBirdDis += mAutoDownSpeed;mBird.setY(mBird.getY() + mTmpBirdDis);// 计算分数mGrade += mRemovedPipe;for (Pipe pipe : mPipes){if (pipe.getX() + mPipeWidth < mBird.getX()){mGrade++;}}checkGameOver();break;case STOP: // 鸟落下}}

可以看到,增加了几行计算分数的代码~我们的分数,每次都是置0,然后加上已经看不到的管道数量(都看不到了,肯定是通过的),然后再加上屏幕上在鸟左边的管道数量~

这就是你获得的分数了~~

所以记得移除管道的时候,通过下mRemovedPipe

private int mRemovedPipe = 0;private void logicPipe(){// 管道移动for (Pipe pipe : mPipes){if (pipe.getX() < -mPipeWidth){mNeedRemovePipe.add(pipe);mRemovedPipe++;continue;}pipe.setX(pipe.getX() - mSpeed);}

且,在OVER以后,在initPos中将mRemovedPipe 置为 0 ;这样重新开始以后,又从0分开始了~~~

好了,到此结束~~至于,管道间的距离,管道宽度,鸟下降速度各种常量,大家如果觉得不适,自行修改~~~

请允许我再贴一次最终效果~:

看着结果的同时,我们总结下:

其实游戏总体来说不难,别看写了两篇这么长,你站远点看,其实还是我们最初的SurfaceViewTemplate,无非多了个重写onTouchEvent和logic()方法,onTouchEvent是交互必须要写的,其实没什么代码~logic方法进行一些重绘时需要的计算~~而draw就安心的draw进行了~~也就是说,这个游戏,其实和绘制个小鸟,点击上升,没什么区别~~~

值得祝贺的是,我们的SurfaceView经过这三篇博客(还有个转盘),基本涵盖的知识点都覆盖了,说不定哪天,大家想出个虐心的简单的游戏就富了呢~~

源码点击下载

我建了一个,方便大家交流。群号:423372824

----------------------------------------------------------------------------------------------------------

博主部分视频已经上线,如果你不喜欢枯燥的文本,请猛戳(初录,期待您的支持):

视频目录地址:本人录制的视频教程

Android SurfaceView实战 带你玩转flabby bird (下)相关推荐

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

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 转载请标 ...

  2. Android SurfaceView实战 带你玩转flabby bird

    分析 仔细观察游戏,需要绘制的有:背景.地板.鸟.管道.分数: 游戏开始时: 地板给人一种想左移动的感觉: 管道与地板同样的速度向左移动: 鸟默认下落: 当用户touch屏幕时,鸟上升一段距离后,下落 ...

  3. Android SurfaceView实战 带你玩切水果1.0

    1.概述 最近开发一个智能项目,项目对象是某智能早教机器人,这个产品主要定位是儿童益智玩的游戏,并且可以从中学习到相关历史知识等相关知识,其中有个游戏关卡就是去到不同国家,可以玩不同的游戏.领导说要用 ...

  4. 详解与重构hyman《Android SurfaceView实战 打造抽奖转盘》

    详解与重构hyman<Android SurfaceView实战 打造抽奖转盘> 作者:邵励治 一.概述--关于SurfaceView您不得不知道的二三事 1.SurfaceView是干什 ...

  5. Android SurfaceView实战 打造抽奖转盘

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/41722441 ,本文出自:[张鸿洋的博客] 1.概述 今天给大家带来Surfac ...

  6. 12个真实项目实战带你玩转Java并发编程

    这篇博客,我会总结如下内容,满满的干货,篇幅可能会很长,做好心理准备 Immutable Object:不可变对象模式:在不引入锁的条件下,能保证访问共享变量时是线程安全的,缺点是会频繁的创建变量. ...

  7. 超详细SVG实战——徒手画pipeline,带你玩转SVG

    记录个 svg 实战应用-最近断断续续在搞公司的前端发布平台,本想搞 pipeline ,结果先给 svg 给拦下了.基于发布平台没有同时多项目发布的能力,so-笔者决定搞个 pipeline 把 j ...

  8. Android SurfaceView使用详解(很好的实战例子)

    一.surfaceview  在显示时才会调用callback中的surfaceCreated.注意,是在显示时,在初始化时不会调用 在隐藏时会调用callback中的surfaceDestroyed ...

  9. 独家直播双十一全网动态?前黑客“劳改”带你玩转大数据

    独家直播双十一全网动态?前黑客"劳改"带你玩转大数据 发表于2015-11-24 10:26| 4044次阅读| 来源CSDN| 7 条评论| 作者蒲婧 CTO俱乐部CTOCTO讲 ...

最新文章

  1. 使用java的html解析器jsoup和jQuery实现一个自动重复抓取任意网站页面指定元素的web应用...
  2. 镗孔指令g76格式_钻孔、镗孔、攻丝,11个固定循环详解!
  3. idea springmvc_IDEA搭建SSM(spring+springmvc+mybatis)Maven项目的入门案例
  4. 介绍一个快速找出 Visual Studio Code 代码多余空格的扩展 - trailing space
  5. LeetCode 1304. 和为零的N个唯一整数
  6. 从人与世界的关系上来看,人其实分为两部分
  7. 必须用Python给程序员不懂浪漫平反一波....不管班花还是校花全都跑不掉~
  8. 博客营销之博客平台的选择和优化
  9. Oracle DBA之监听的静态注册与动态注册
  10. 6迁移-企业级 Hyper-v 群集部署实验方案
  11. JavaScript监听浏览器刷新或是关闭事件
  12. Python 定时关机、重启命令
  13. CAN FD协议描述
  14. 计算机编程c语言汇总,计算机软件编程中的C语言分析
  15. 常用的几种红外接收器
  16. The vertically scrolling ScrollView should not contain another vertically scrolling widget (ListView
  17. EN 15650: 通风口CE认证
  18. 安装使用完虚拟机UltraISO后,删除电脑中多出的“CD驱动器”盘符
  19. SAP SRS 门店WEB系统激活
  20. 软考高级 真题 2012年上半年 信息系统项目管理师 案例分析

热门文章

  1. HTML和CSS做渐变背景和3D卡片
  2. opecv入门:3.6图片特效-浮雕效果
  3. Hyperledger Fabric Model——超级账本组成模型
  4. python for循环range倒序_[Python] for in range()使用以及列表字符串反转方法
  5. Tushare-完美的大数据开放社区
  6. opencv学习笔记六十三:基于CNN的性别、年龄预测
  7. 平面设计师需要掌握哪些印刷知识?
  8. JS控制video播放暂停或者开始
  9. [翻译] 在 Overleaf 中离线工作
  10. 商品比价API使用说明