《flappy bird》是由来自越南的独立游戏开发者DongNguyen所开发的作品,游戏中玩家必须控制一只小鸟,跨越由各种不同长度水管所组成的障碍,而这只鸟其实是根本不会飞的……所以玩家每点击一下小鸟就会飞高一点,不点击就会下降,玩家必须控制节奏,拿捏点击屏幕的时间点,让小鸟能在落下的瞬间跳起来,恰好能够通过狭窄的水管缝隙,只要稍一分神,马上就会失败阵亡。
本文将介绍使用OGEngine游戏引擎来做一个类似FlayppyBird 的游戏,游戏创意及资源属原作者所有,这里只做技术学习使用。
一、没图没真相,先来看看截图
游戏基本上模仿了原版的游戏过程。
二、游戏场景的设计
游戏场景(GameScene)是本游戏的重点,它包括三种元素:
(1)主角小鸟
(1)主角小鸟
(2)障碍物水管
(3)滚动的地板
GameScene所控制的操作:
(1)游戏状态的表示
(2)添加小鸟和水管
(3)分数的实时统计
(4)碰撞的检测
GameScene所控制的操作:
(1)游戏状态的表示
(2)添加小鸟和水管
(3)分数的实时统计
(4)碰撞的检测
1、创建小鸟类(BirdSprite)
在整个游戏的过程当中,小鸟有三种不同的状态:
(1)准备状态(挥动翅膀,不受重力约束)
(2)飞行状态(飞行过程中,受重力的影响)
(3)死亡状态(倒地的状态)
这里用几个常量来标示小鸟的状态
public static final int STATE_READY = 0;
public static final int STATE_FLY = 1;
public static final int STATE_DIE = 2;
小鸟三种状态下的设置:
/**
* 刷新状态
* @param state
*/
public void refreshState(int state) {
switch (state) {
case STATE_READY:
this.setY(initY);
this.setRotation(0);
this.animate(180);
break;
case STATE_FLY:
this.animate(100);
break;
case STATE_DIE:
this.stopAnimation(0);
this.setRotation(90);
break;
}
注:①小鸟在准备状态下是恢复初始坐标位置,恢复旋转角度为0,翅膀振动比较慢一些。
②小鸟在飞行过程中,翅膀振动快一些。
③小脑死亡时停止振动翅膀,旋转角度为90°。
2、创建水管类(BarSprite)
我们知道游戏中,小鸟是不停地由左往右飞,通过层层水管障碍物,事实上,我们只需要让水管和地板由右往左移动就可以实现小鸟由左往右飞的感觉了,呵呵。
我们可以使用 PhysicsHandler 来处理水管的移动问题,使得水管一生成的时候就以设定的速度由右往左匀速移动。
请看下面的代码:
public BarSprite(float pX, float pY, String pTextureRegionName,boolean isUpBar,
VertexBufferObjectManager pVertexBufferObjectManager) {
super(pX, pY, pTextureRegionName, pVertexBufferObjectManager);
this.isUpBar = isUpBar;
mPhysicsHandler = new PhysicsHandler(this);
this.registerUpdateHandler(mPhysicsHandler);
// 设置移动方向和速度
mPhysicsHandler.setVelocityX(-150);
}
/**
* 停止移动
*/
public void stopMove(){
this.unregisterUpdateHandler(mPhysicsHandler);
}
3、创建地板类(FloorSprite)
地板类主要是要实现控制地板的循环滚动状态。
public class FloorSprite extends AnimatedSprite {
/** 视差值 **/
private float mParallaxValue;
/** 视差移动的因数 **/
private float mParallaxFactor = 5;
/** 每秒改变的视差值 **/
private float mParallaxChangePerSecond;
public void setParallaxChangePerSecond(float mParallaxChangePerSecond) {
this.mParallaxChangePerSecond = mParallaxChangePerSecond;
}
public FloorSprite(float pX, float pY, String pTextureRegionName,
float mParallaxChangePerSecond,
VertexBufferObjectManager pVertexBufferObjectManager) {
super(pX, pY, pTextureRegionName, pVertexBufferObjectManager);
this.mParallaxChangePerSecond = mParallaxChangePerSecond;
}
@Override
protected void onManagedUpdate(float pSecondsElapsed) {
super.onManagedUpdate(pSecondsElapsed);
// LogUtil.d("onManagedUpdate pSecondsElapsed-->"+pSecondsElapsed);
this.mParallaxValue += this.mParallaxChangePerSecond * pSecondsElapsed;
}
@Override
protected void onManagedDraw(GLState pGLState, Camera pCamera) {
pGLState.pushModelViewGLMatrix();
{
final float cameraWidth = pCamera.getWidth();
final float shapeWidthScaled = this.getWidthScaled();
float baseOffset = (mParallaxValue * this.mParallaxFactor)
% shapeWidthScaled;
while (baseOffset > 0) {
baseOffset -= shapeWidthScaled;
}
pGLState.translateModelViewGLMatrixf(baseOffset, 0, 0);
float currentMaxX = baseOffset;
do {
super.onManagedDraw(pGLState, pCamera);
pGLState.translateModelViewGLMatrixf(shapeWidthScaled, 0, 0);
currentMaxX += shapeWidthScaled;
} while (currentMaxX < cameraWidth);
}
pGLState.popModelViewGLMatrix();
}
}
4、添加小鸟
前面简单地封装了一下小鸟精灵类,在游戏场景类GameScene里添加小鸟及改变小鸟状态就比较简单了。
birdSprite = new BirdSprite(120, 370, pVertexBufferObjectManager);
birdSprite.setZIndex(pZIndex_middle);
birdSprite.refreshState(game_state);
birdSprite.setScale(1.2f);
this.attachChild(birdSprite);
5、添加水管
我们知道,水管在游戏中是源源不断地,水管的添加,主要是通过createBars(float pX) 来生成,每隔一段时间生产上下一对水管,中间留有小鸟通过的缝道。
/**
* 创建一对bars
* @param pX
*/
private void createBars(float pX) {
float pY = -new Random().nextInt(300);
BarSprite upSprite = new BarSprite(pX, pY, Res.BAR_UP, true,
pVertexBufferObjectManager);
BarSprite downSprite = new BarSprite(pX, upSprite.getBottomY() + 165,
Res.BAR_DOWN, false, pVertexBufferObjectManager);
this.attachChild(upSprite);
this.attachChild(downSprite);
barSprites.add(upSprite);
barSprites.add(downSprite);
this.sortChildren();
}
我们可以通过TimerHandler每隔一段时间创建一对水管。
mTimerHandler = new TimerHandler(pTimerSeconds, true,
new ITimerCallback() {
@Override
public void onTimePassed(TimerHandler pTimerHandler) {
createBars(480);
}
});
三、小鸟物理效果以及碰撞检测的设计
我们知道,游戏的一大特点就是玩家每点击一下小鸟就会飞高一点,不点击小鸟就会受到重力效果以一定的加速度往下掉。
我们使用OGEngine 引擎集合的Box2D功能,很容易地就可以实现这一的效果。
1、为小鸟添加物理效果
/**
* 初始化物理效果
*/
private void initPhysics() {
this.mPhysicsWorld = new PhysicsWorld(new Vector2(0, 50), false);
// 参数 :密度、弹力、摩擦力
FixtureDef FIXTURE_DEF = PhysicsFactory.createFixtureDef(0, 0.0f, 0.0f);
Body birdBody = PhysicsFactory.createBoxBody(mPhysicsWorld, birdSprite,
BodyType.DynamicBody, FIXTURE_DEF);
birdSprite.setBody(birdBody);
this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(birdSprite,
birdBody, true, true));
}
2、在小鸟飞行的状态中,每点击一次屏幕小鸟会有一个向上的速度。
birdSprite.getBody().setLinearVelocity(0, -15); 这样就可以为小鸟设置一个向上的速度。
@Override
public boolean onSceneTouchEvent(TouchEvent pSceneTouchEvent) {
if (pSceneTouchEvent.isActionDown()) {
if (game_state == STATE_READY) {
gameStart();
}
if (!birdSprite.isCollisionFloor() && !birdSprite.isCollisionBar()) {
if(birdSprite.getY()>0){
// 不能飞过顶部
birdSprite.getBody().setLinearVelocity(0, -15);
// 播放音效
SoundRes.playSound(SOUND_WING);
}
}
}
return super.onSceneTouchEvent(pSceneTouchEvent);
}
3、检测碰撞
我们通过注册一个 IUpdateHandler 不停地监听小鸟跟水管或者地板有没发生碰撞,并根据实际情况做对应的逻辑处理。
/**
* 不停检测用的 handler
*/
private IUpdateHandler checkHandler = new IUpdateHandler() {
@Override
public void reset() {
}
@Override
public void onUpdate(float pSecondsElapsed) {
// LogUtil.d("onUpdate--->"+pSecondsElapsed);
if (game_state == STATE_FLY) {
// 小鸟旋转
rotateBird();
if (!birdSprite.isCollisionBar() && isCollidesWithBar()) {
birdSprite.setCollisionBar(true);
// 停止水管的生成和移动
barOver();
// 播放音效
SoundRes.playSound(SOUND_HIT);
}
// 小鸟是否碰撞到地板或者水管
// if (birdSprite.collidesWith(mFloorSprite) && !birdSprite.isCollisionFloor()) {
if ( !birdSprite.isCollisionFloor()&&isContact(birdSprite, mFloorSprite)) {
birdSprite.setCollisionFloor(true);
if(!birdSprite.isCollisionBar()){
// 停止水管的生成和移动
barOver();
// 播放音效
SoundRes.playSound(SOUND_HIT);
}
// 游戏结束
gameOver();
}
// 迭代器遍历bar集合
Iterator<BarSprite> iterator = barSprites.iterator();
while (iterator.hasNext()) {
BarSprite barSprite = iterator.next();
// 检测小鸟是否通过水管
if(barSprite.isUpBar() && !barSprite.isPass()){
if(barSprite.getCentreX()<birdSprite.getX()){
barSprite.setPass(true);
score++;
updateScore(score);
}
4、小鸟角度旋转逻辑
小鸟在上飞或者下飞的情况下会有不同的角度,这个我们可以根据小鸟的Y速度调试出来。
/**
* 小鸟角度旋转逻辑
*/
private void rotateBird(){
float speed_y = birdSprite.getBody().getLinearVelocity().y;
float pRotation = Math.min(Math.max(-30, (speed_y*4f)-45), 90);
birdSprite.getBody().setRotation(pRotation);
}
四、分数的处理
分数的处理比较简单,主要用到OGEngine 的 Text 类,以及安卓子身的SharedPreferences来保存每次的最高纪录即可。
<font color="#000000">public class SharedUtil {
private static final String Shared_System = "Shared_og";
private static final String BEST_SCORE = "best_score";
public static void setBestScore(Context pContext, int pBestScore) {
Editor edit = pContext.getSharedPreferences(Shared_System,
Context.MODE_PRIVATE).edit();
edit.putInt(BEST_SCORE, pBestScore);
edit.commit();
}
public static int getBestScore(Context context) {
return context
.getSharedPreferences(Shared_System, Context.MODE_PRIVATE)
.getInt(BEST_SCORE, 0);
}</font>
好了,分析就到这里,更多细节请各位自下载游戏源码研究: http://www.ogengine.com/download/doc.jsp