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