Android SurfaceView实战 带你玩转flabby bird (下)
转载请标明出处: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;
- }
- }
- @Override
- public 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;
- }
- @Override
- public 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()
- {
- // 如果触碰地板,gg
- if (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经过这三篇博客(还有个转盘),基本涵盖的知识点都覆盖了,说不定哪天,大家想出个虐心的简单的游戏就富了呢~~
Android SurfaceView实战 带你玩转flabby bird (下)的更多相关文章
- Android SurfaceView实战 带你玩转flabby bird (上)
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/42965779 ,本文出自:[张鸿洋的博客] 1.概述 哈,记得以前写过Andro ...
- Android SurfaceView实战 打造抽奖转盘
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/41722441 ,本文出自:[张鸿洋的博客] 1.概述 今天给大家带来Surfac ...
- Android项目实战(三十三):AS下获取获取依赖三方的jar文件、aar 转 jar
使用 Android studio 开发项目中,有几种引用三方代码的方式:jar 包 ,类库 ,gradle.build 的compile依赖. 大家会发现github上不少的项目只提供compile ...
- Keymob带你玩转新广告法下的移动营销
2015年9月1日新广告法正式实施,对广告代言人.广告类别.广告语等都做了一系列新规定,堪称有史以来最严广告法.随着新广告法的实施,以往一些庸俗.夸张的广告也逐渐和大众说再见了. 2015年 “互联网 ...
- Android 高仿微信6.0主界面 带你玩转切换图标变色
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/41087219,本文出自:[张鸿洋的博客] 1.概述 学习Android少不了模仿 ...
- Java开发不懂Docker,学尽Java也枉然,阿里P8架构师手把手带你玩转Docker实战
转: Java开发不懂Docker,学尽Java也枉然,阿里P8架构师手把手带你玩转Docker实战 Docker简介 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一 ...
- (转载)Android项目实战(二十八):使用Zxing实现二维码及优化实例
Android项目实战(二十八):使用Zxing实现二维码及优化实例 作者:听着music睡 字体:[增加 减小] 类型:转载 时间:2016-11-21我要评论 这篇文章主要介绍了Android项目 ...
- Android SurfaceView使用详解
1. SurfaceView的定义前面已经介绍过View了,下面来简单介绍一下SurfaceView,参考SDK文档和网络资料:SurfaceView是View的子类,它内嵌了一个专门用于绘制的Sur ...
- 手把手让你爱上Android sdk自带“9妹”(9patch 工具)
前几天群成员讨论过关于9patch的工具[我比较喜欢喊它9妹子,西西(*^_^*)].然后研究了一下,比较简单但是很实用的一个Android sdk 自带工具.这里给大家做一个分享下经验! 1.什么是 ...
随机推荐
- 【科研论文】基于文件解析的飞行器模拟系统软件设计(应用W5300)
摘要: 飞行器模拟系统是复杂飞行器研制和使用过程中的重要设备,它可以用来模拟真实飞行器的输入输出接口,产生与真实系统一致的模拟数据,从而有效避免因使用真实飞行器带来的高风险,极大提高地面测发控系统的研 ...
- IntelliJ IDEA 14 注册码生成器
IntelliJ IDEA 14 注册码生成器 文件为Java代码 自己编译运行里面的程序输入名称然后就生成注册码了工具:http://yun.baidu.com/s/1cZKsA部分工具生成的注册码 ...
- linux驱动: 如何向模块传递参数, module_param和module_param_array
如何向模块传递参数,Linux kernel 提供了一个简单的框架. 1. module_param(name, type, perm); name 既是用户看到的参数名,又是模块内接受参数的 ...
- [Swust OJ 801]--Ordered Fractions
题目链接:http://acm.swust.edu.cn/problem/801/ Time limit(ms): 1000 Memory limit(kb): 10000 Description ...
- Java安全机制之泛型(JDK1.5)
泛型,类型安全机制. 好处: 1.将运行时期出现问题ClassCastException转移到了编译时期,方便解决问题,减少运行时期的问题,有利于程序的健壮性. 2.避免了强制转换的麻烦 泛型格式: ...
- django 开发简易博客(二)
这一节我们来了解模板和视图.URL的使用. 一.使用模板 在blog目录中新建templates文件夹,在templates文件夹下新建base.html文件.目录结构如下 templates/ ba ...
- jbpmAPI-1
1.1. What is jBPM? jBPM是一个灵活的业务流程管理(BPM)套件.它是轻量级的,完全开源Apache许可下(分布式),用Java编写的.它允许您模型.执行和监控业务流程的整个生命周 ...
- 一个分组查询 每组前 10 的sql 语句
USE tmpgo CREATE TABLE Employee( ID int identity(1,1), EmpName varchar(20), EmpSalary varchar(10), E ...
- C语言之三大查找算法
查找算法 1.二分查找 二分查找就是折半查找,其基本思想是:首先选取表中间位置的记录,将其关键字与给定关键字key进行比较,若相等,则查找成功.若key值比该关键字值大,则要找的元素一定在右子表中,则 ...
- 多种EDA软件的鼠标增强工具EDAHelper
最新版本(unicode版本,各种语言环境显示中文)下载地址:http://www.jyxtec.com/edahelper/EDAHelper-2.1.13.7z 繁体版:http://www.jy ...