AndEngine是Android上一个很出色的基于OpenGL的游戏引擎,其特点是所有代码都是用Java编写,代码之间层次非常分别,组件颗粒度非常小,直接带来的优点就是非常容易用,扩展也非常轻松,但由于Android的VM虽然是优化过的,但性能也是一般般。
AndEngine内置了对TMX地图的支持,我用一张1024*1024的jpg图片测试过,在我的Dell Venue上只能达到45~50帧/秒的速度,而在模拟器上更是惨不忍睹。这种速度显然在真正的游戏开发中是难以接受的,于是我就着手改进。
由于TMX地图能提供很强大的功能,首先它几乎可以分割无限大的地图,而且还可以添加层和对象,因此要想使用它则必须要容忍它的慢,我的思路是在某些需要大地图,但却不太需要在地图中添加大量的层和对象的地方,抛弃使用TMX地图,转而自己切割地图。
原理很简单,就是参考AndEngine对大图片的切割,直接上代码,有OpenGL基础的很容易就能看明白。
MultiSpriteLayer
package com.weedong.background; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL11; import org.anddev.andengine.collision.RectangularShapeCollisionChecker; import org.anddev.andengine.engine.camera.Camera; import org.anddev.andengine.entity.shape.RectangularShape; import org.anddev.andengine.entity.sprite.Sprite; import org.anddev.andengine.opengl.buffer.BufferObjectManager; import org.anddev.andengine.opengl.texture.region.TextureRegion; import org.anddev.andengine.opengl.util.GLHelper; import org.anddev.andengine.opengl.vertex.RectangleVertexBuffer; import org.anddev.andengine.util.MathUtils; import org.anddev.andengine.util.constants.Constants; import android.util.Log; public class MultiSpriteLayer extends RectangularShape { private Sprite[][] arySprite = null; public MultiSpriteLayer(Sprite[][] sprites) { super(0, 0, 0, 0, null); arySprite = sprites; int tiledWidth = (int)arySprite[0][0].getWidth(); int tiledHeight = (int)arySprite[0][0].getHeight(); this.mSharedVertexBuffer = new RectangleVertexBuffer(GL11.GL_STATIC_DRAW, true); BufferObjectManager.getActiveInstance().loadBufferObject(this.mSharedVertexBuffer); this.mSharedVertexBuffer.update(tiledWidth, tiledHeight); super.mWidth = tiledWidth * arySprite.length; final float width = super.mWidth; super.mBaseWidth = width; super.mHeight = tiledHeight * arySprite[0].length; final float height = super.mHeight; super.mBaseHeight = height; this.mRotationCenterX = width * 0.5f; this.mRotationCenterY = height * 0.5f; this.mScaleCenterX = this.mRotationCenterX; this.mScaleCenterY = this.mRotationCenterY; } private final float[] mCullingVertices = new float[2 * RectangleVertexBuffer.VERTICES_PER_RECTANGLE]; private final RectangleVertexBuffer mSharedVertexBuffer; @Override protected void onInitDraw(final GL10 pGL) { super.onInitDraw(pGL); GLHelper.enableTextures(pGL); GLHelper.enableTexCoordArray(pGL); } @Override protected void onApplyVertices(final GL10 pGL) { if(GLHelper.EXTENSIONS_VERTEXBUFFEROBJECTS) { final GL11 gl11 = (GL11)pGL; this.mSharedVertexBuffer.selectOnHardware(gl11); GLHelper.vertexZeroPointer(gl11); } else { GLHelper.vertexPointer(pGL, this.mSharedVertexBuffer.getFloatBuffer()); } } @Override protected void drawVertices(GL10 pGL, Camera pCamera) { final float cameraMinX = pCamera.getMinX(); final float cameraMinY = pCamera.getMinY(); final float cameraWidth = pCamera.getWidth(); final float cameraHeight = pCamera.getHeight(); final Sprite[][] tmxTiles = arySprite; final int tileColumns = tmxTiles[0].length; final int tileRows = tmxTiles.length; final int tileWidth = (int)tmxTiles[0][0].getWidth(); final int tileHeight = (int)tmxTiles[0][0].getHeight(); final float scaledTileWidth = tileWidth * this.mScaleX; final float scaledTileHeight = tileHeight * this.mScaleY; final float[] cullingVertices = this.mCullingVertices; RectangularShapeCollisionChecker.fillVertices(this, cullingVertices); final float layerMinX = cullingVertices[Constants.VERTEX_INDEX_X]; final float layerMinY = cullingVertices[Constants.VERTEX_INDEX_Y]; /* Determine the area that is visible in the camera. */ final float firstColumnRaw = (cameraMinX - layerMinX) / scaledTileWidth; final int firstColumn = MathUtils.bringToBounds(0, tileColumns - 1, (int)Math.floor(firstColumnRaw)); final int lastColumn = MathUtils.bringToBounds(0, tileColumns - 1, (int)Math.ceil(firstColumnRaw + cameraWidth / scaledTileWidth)); final float firstRowRaw = (cameraMinY - layerMinY) / scaledTileHeight; final int firstRow = MathUtils.bringToBounds(0, tileRows - 1, (int)Math.floor(firstRowRaw)); final int lastRow = MathUtils.bringToBounds(0, tileRows - 1, (int)Math.floor(firstRowRaw + cameraHeight / scaledTileHeight)); final int visibleTilesTotalWidth = (lastColumn - firstColumn + 1) * tileWidth; pGL.glTranslatef(firstColumn * tileWidth, firstRow * tileHeight, 0); for(int row = firstRow; row <= lastRow; row++) { final Sprite[] tmxTileRow = tmxTiles[row]; for(int column = firstColumn; column <= lastColumn; column++) { final TextureRegion textureRegion = tmxTileRow[column].getTextureRegion(); if(textureRegion != null) { textureRegion.onApply(pGL); pGL.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4); } pGL.glTranslatef(tileWidth, 0, 0); } pGL.glTranslatef(-visibleTilesTotalWidth, tileHeight, 0); } pGL.glLoadIdentity(); } @Override protected void onUpdateVertexBuffer() { } }
AbstractMultiSpriteBackgroundScene
package com.weedong.scene; import org.anddev.andengine.entity.sprite.Sprite; import org.anddev.andengine.opengl.texture.Texture; import org.anddev.andengine.opengl.texture.TextureOptions; import org.anddev.andengine.opengl.texture.region.TextureRegion; import org.anddev.andengine.opengl.texture.region.TextureRegionFactory; import com.weedong.activity.BaseWeedongLayoutGameActivity; import com.weedong.background.MultiSpriteLayer; /** * 抽象类<br/> * 由多个精灵组成的背景的Scene<br/> * 其特点是是可以像TMX一样支持超过1024*1024的背景图,并且速度很快<br/> * 若要使背景地图可以拖动,可以参考AndEngine的例子重写onSceneTouchEvent方法并使用SurfaceScrollDetector * @author * */ public abstract class AbstractMultiSpriteBackgroundScene extends AbstractGameScene { public AbstractMultiSpriteBackgroundScene(int nLayerCount, BaseWeedongLayoutGameActivity gameActivity) { super(nLayerCount, gameActivity); } public AbstractMultiSpriteBackgroundScene(int nLayerCount, BaseWeedongLayoutGameActivity gameActivity, ILoadingScene loadingScene) { super(nLayerCount, gameActivity, loadingScene); } @Override protected void onLoadScene() { super.onLoadScene(); initializeBackground(); } private void initializeBackground() { String[][] aryBackgroundFilePath = getBackgroundFilePath(); Sprite[][] arySprite = new Sprite[aryBackgroundFilePath.length][aryBackgroundFilePath[0].length]; for(int i = 0; i < arySprite.length; i++) { for(int j = 0; j < arySprite[0].length; j++) { //将所有精灵的TextureOptions设为TextureOptions.NEAREST可以达到最快速度 //如果设成BILINEAR_PREMULTIPLYALPHA会导致精灵的边界出现一条黑线 Texture backgroundTexture = new Texture(512, 512, TextureOptions.NEAREST); loadTextureAndAppendToContainer(backgroundTexture); TextureRegion backgroundRegion = TextureRegionFactory.createFromAsset(backgroundTexture, mGameActivity, aryBackgroundFilePath[i][j], 0, 0); Sprite eachSprite = new Sprite(0, 0, backgroundRegion); arySprite[i][j] = eachSprite; } } MultiSpriteLayer layer = new MultiSpriteLayer(arySprite); layer.setCullingEnabled(true); this.attachChild(layer); this.mGameActivity.mCamera.setBounds(0, layer.getWidth(), 0, layer.getHeight()); this.mGameActivity.mCamera.setBoundsEnabled(true); } /** * 子类必须实现此方法,以按顺序返回组成背景的所有精灵的图片路径 * @author * @return */ protected abstract String[][] getBackgroundFilePath(); }
使用很简单,继承AbstractMultiSpriteBackgroundScene实现其中的getBackgroundFilePath方法即可。若要使背景地图可以拖动,可以参考AndEngine的例子重写onSceneTouchEvent方法并使用SurfaceScrollDetector