Create a game like Flappy Bird in Android using AndEngine
![Download](/images/download.png)
Keywords: AndEngine AndEnginePhysicsBox2DExtension SimpleBaseGameActivity ResourceManager SceneManager MenuScene CameraScene AutoParallaxBackground AnimatedSprite DynamicSpriteBatch TiledSprite Sound Music HUD Font PhysicsHandler GenericPool PhysicsWorld Fixture Body ContactListener
Contents- Overview
- Create a new Eclipse Android project
- Add AndEngine library
- Add AndEngine Physics Box2D extension
- Android manifest
- AndEngine concepts
- Game Activity
- Resource Manager
- TextureAtlas & TextureRegion
- Font
- Sound & Music
- Scene Manager
- Base Scene
- Splash Scene
- Main Menu Scene
- Sub Menu Scene
- Game Scene
- Auto Parallax Background
- HUD
- Bird
- Pipes
- Generic Pool
- Physics World
- Body & Fixture
- Contact Listener
- Camera Scene
- What's next?
21. Pipes
![Flappy Pipes](/screenshots/flappychick/Flappy_Pipe.png)
The advantage of combining sprites into a batch is that they can be positioned or transformed as a single entity.
Let's create a new class for pipe in package com.appsrox.flappychick.entity.
public class Pipe extends DynamicSpriteBatch { private static final float DEMO_VELOCITY = 150.0f; private static final float DEMO_GAP = 75.0f; private static final float DEMO_HEIGHT = 150.0f; private static final float DEMO_POSITION = 1.1f*GameActivity.CAMERA_WIDTH; private TiledSprite mPipe1a; private TiledSprite mPipe1b; private float mGroundY; private float mPipeWidth; private float mPipeHeight; private final PhysicsHandler mPhysicsHandler; public Pipe(TiledTextureRegion pTiledTextureRegion, VertexBufferObjectManager pVertexBufferObjectManager, float pGroundY, float pDeltaHeight) { super(pTiledTextureRegion.getTexture(), 2, pVertexBufferObjectManager); mGroundY = pGroundY; mPipeWidth = pTiledTextureRegion.getWidth(); mPipeHeight = DEMO_HEIGHT + pDeltaHeight; mPhysicsHandler = new PhysicsHandler(this); registerUpdateHandler(mPhysicsHandler); mPhysicsHandler.setVelocity(-DEMO_VELOCITY, 0); //top final float pipe1aX = 0; final float pipe1aY = 0; mPipe1a = new TiledSprite(pipe1aX, pipe1aY, pTiledTextureRegion, pVertexBufferObjectManager); mPipe1a.setCurrentTileIndex(0); mPipe1a.setHeight(mPipeHeight); //bottom final float pipe1bX = 0; final float pipe1bY = mPipeHeight + Pipe.DEMO_GAP; mPipe1b = new TiledSprite(pipe1bX, pipe1bY, pTiledTextureRegion, pVertexBufferObjectManager); mPipe1b.setCurrentTileIndex(1); mPipe1b.setHeight(mGroundY - pipe1bY); setPosition(DEMO_POSITION, 0); } @Override protected boolean onUpdateSpriteBatch() { this.draw(mPipe1a); this.draw(mPipe1b); return true; } @Override public boolean collidesWith(IShape pOtherShape) { Sprite sprite = (Sprite) pOtherShape; if (sprite.getX()+sprite.getWidth() > this.mX && (sprite.getY() < mPipe1a.getHeight() || sprite.getY()+sprite.getHeight() > mPipe1b.getY()) && sprite.getX() < this.mX+mPipeWidth) { return true; } return super.collidesWith(pOtherShape); } public void die() { unregisterUpdateHandler(mPhysicsHandler); } public void alive() { registerUpdateHandler(mPhysicsHandler); } @Override public void reset() { super.reset(); setX(DEMO_POSITION); } }Basically, in the constructor we create the top and bottom pipe from tiled texture region and set different tile index to each. We then set initial position and velocity to the sprite batch.
Notice the use of PhysicsHandler as an update handler to the batch for imparting velocity. We additionally override collidesWith() method to implement our own logic to check collision of an entity with the pipes.
22. Generic Pool
As you might have guessed, we need a pool of pipes (of different heights) so that we can recycle them as the game progresses. AndEngine provides GenericPool for easily creating pool of objects.Let's create a new class in package com.appsrox.flappychick.entity.
public class PipePool extends GenericPool<Pipe> { private TiledTextureRegion mPipeTextureRegion; private VertexBufferObjectManager mVertexBufferObjectManager; private float mGroundY; private int mPipeIndex; public PipePool(TiledTextureRegion pPipeTextureRegion, VertexBufferObjectManager pVertexBufferObjectManager, float pGroundY) { super(); this.mPipeTextureRegion = pPipeTextureRegion; this.mVertexBufferObjectManager = pVertexBufferObjectManager; this.mGroundY = pGroundY; } @Override protected Pipe onAllocatePoolItem() { return new Pipe(mPipeTextureRegion, mVertexBufferObjectManager, mGroundY, 50*MathUtils.random(-2, +2)); } @Override protected void onHandleRecycleItem(Pipe pItem) { // pItem.setIgnoreUpdate(true); // pItem.setVisible(false); } @Override protected void onHandleObtainItem(Pipe pItem) { pItem.reset(); } @Override public synchronized Pipe obtainPoolItem() { mPipeIndex++; return super.obtainPoolItem(); } public int getPipeIndex() { return mPipeIndex; } }GenericPool is an abstract class so we've to implement few methods. In onAllocatePoolItem() we create a new instance of Pipe with random height.
The other thing to note is in obtainPoolItem() method we increment mPipeIndex which is used as the score.
Now that we have Pipe and PipePool ready, let's revisit GameScene class and implement the game play.
private Pipe mPipe; private PipePool mPipePool; private boolean mGameOver; private float mPipeWidth; @Override public void createScene() { //... mPipeWidth = mResourceManager.mPipeTextureRegion.getWidth(); //create entities final Rectangle ground = new Rectangle(0, SCREEN_HEIGHT - mResourceManager.mParallaxLayerFront.getHeight(), SCREEN_WIDTH, mResourceManager.mParallaxLayerFront.getHeight(), mVertexBufferObjectManager); ground.setColor(Color.TRANSPARENT); final Rectangle roof = new Rectangle(0, 0, SCREEN_WIDTH, 1, mVertexBufferObjectManager); roof.setColor(Color.TRANSPARENT); mPipePool = new PipePool(mResourceManager.mPipeTextureRegion, mVertexBufferObjectManager, ground.getY()); mPipePool.batchAllocatePoolItems(10); mPipe = mPipePool.obtainPoolItem(); attachChild(ground); attachChild(roof); attachChild(mPipe); //... /* The actual collision-checking. */ registerUpdateHandler(new IUpdateHandler() { @Override public void reset() {} @Override public void onUpdate(float pSecondsElapsed) { if (!mGameOver && mPipe.collidesWith(mBird)) { mGameOver = true; mResourceManager.mSound.play(); mBird.stopAnimation(0); mPipe.die(); mAutoParallaxBackground.setParallaxChangePerSecond(0); return; } if (mPipe.getX() < -mPipeWidth) { detachChild(mPipe); mPipePool.recyclePoolItem(mPipe); mPipePool.shufflePoolItems(); mPipe = mPipePool.obtainPoolItem(); attachChild(mPipe); sortChildren(); } if (score != mPipePool.getPipeIndex() && mBird.getX() > (mPipe.getX()+mPipeWidth)) { score = mPipePool.getPipeIndex(); mHudText.setText(String.valueOf(score)); } } }); }In brief, we create new entities for ground and roof, and obtain pipe from the pool. The other modification is in update handler. When pipe collides with the bird we end the game and play a sound. Otherwise, we update the score if the bird is able to pass through the pipes.
Also, if the pipe goes out of screen then we recycle it and obtain a new one from the pool after shuffling it.