可以看到,我们在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 mNeedRemovePipe = new ArrayList();
/**
- 处理一些逻辑上的计算
*/
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经过这三篇博客(还有个转盘),基本涵盖的知识点都覆盖了,说不定哪天,大家想出个虐心的简单的游戏就富了呢~~
(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经过这三篇博客(还有个转盘),基本涵盖的知识点都覆盖了,说不定哪天,大家想出个虐心的简单的游戏就富了呢~~
[外链图片转存中…(img-ppzQITlV-1718507586933)]