在游戏开发中,最耗费性能的是显示文本,因为必须临时在内存中将文本生成bitmap,然后将bitmap绑定到OpenGL的纹理并渲染出来,AndEngine的作者估计是深入Java开发中缓存这个概念的重要性的影响,他对文本精灵作了一个精心的规划,分为不可变的文本精灵(Text)和可变的文本精灵(ChangeableText),并将生成文本bitmap并转换到纹理的职责委托给一个叫Font的类,Font类的实现很特别,它将要显示的字符串切分为单个的字符,通过canvas生成bitmap后缓存起来,然后在绑定纹理的时候合并,这样做的好处是当显示重复的字符时性能非常高,缺点是实现复杂导致若缓存池没有此字符时则性能稍低下。在实际使用中,当一个场景里已经存在很多精灵时,Text或ChangeableText的内容经常无法显示或者显示为一片空白区域。
在IPhone,文本精灵分别是CCLabelTTF和CCLabelAtlas,作用相当于AndEngine的Text和ChangeableText,但实现机制完全不一样,CCLabelTTF是将要显示的字符串先生成一张整体的bitmap然后使用Opengl渲染,而CCLabelAtlas则要求预先传入制作好的文字序列图片以便缓存起来。因此我参考Cocos2d的做法增加了一个Label类,此类结合LoadingScene使用起来还靠谱,缺点是在运行时同时改变两个以上的Label的文字时会有时其中一个Label的文字无法显示。以下是代码:
UltraTextSource:
package com.weedong.opengl; import org.anddev.andengine.opengl.texture.source.ITextureSource; import org.anddev.andengine.util.HorizontalAlign; import org.anddev.andengine.util.MathUtils; import org.anddev.andengine.util.StringUtils; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.Bitmap.Config; import android.graphics.Paint.FontMetrics; import android.util.FloatMath; import android.util.Log; /** * 性能较强的Font TextureSource * @author * */ public class UltraFontTextureSource implements ITextureSource { private int mWidth; private int mHeight; private Bitmap mBitmap; public UltraFontTextureSource(String text, Typeface typeface, float fontSize, final int pColor, final boolean pAntiAlias, HorizontalAlign alignment) { initialize(text, alignment, fontSize, typeface, pAntiAlias, pColor); } public UltraFontTextureSource(String text, Typeface typeface, float fontSize, final int pColor, final boolean pAntiAlias) { this(text, typeface, fontSize, pColor, pAntiAlias, HorizontalAlign.LEFT); } public UltraFontTextureSource(String text, Typeface typeface, float fontSize, final int pColor) { this(text, typeface, fontSize, pColor, true); } private void initialize(String text, HorizontalAlign alignment, float fontSize, Typeface typeface, final boolean pAntiAlias, final int pColor) { Paint textPaint = new Paint(); textPaint.setTypeface(typeface); textPaint.setTextSize(fontSize); textPaint.setColor(pColor); textPaint.setAntiAlias(pAntiAlias); FontMetrics fontMetrics = textPaint.getFontMetrics(); int lineHeight = (int) FloatMath.ceil(Math.abs(fontMetrics.ascent) + Math.abs(fontMetrics.descent)); int lineGap = (int)(FloatMath.ceil(fontMetrics.leading)); String[] aryLines = StringUtils.split(text, '\n', null); int lineCount = aryLines.length; int nMaxLineWidth = getMaxLineWidth(textPaint, aryLines); int nMaxLineHeight = lineCount * lineHeight + (lineCount - 1) * lineGap; int width = MathUtils.nextPowerOfTwo(nMaxLineWidth); int height = MathUtils.nextPowerOfTwo(nMaxLineHeight); mWidth = width; mHeight = height; Bitmap.Config config = Bitmap.Config.ARGB_8888; Bitmap bitmap = Bitmap.createBitmap(width, height, config); Canvas canvas = new Canvas(bitmap); bitmap.eraseColor(0); Log.i("UltraFont", "font width:" + bitmap.getWidth() + ",height:" + bitmap.getHeight()); mBitmap = bitmap; int centerOffsetHeight = (height - nMaxLineHeight) / 2; int centerOffsetWidth = (width - nMaxLineWidth) / 2; switch (alignment) { case LEFT: centerOffsetWidth = 0; break; case CENTER: //centerOffsetWidth = (effectiveTextWidth - textWidth) / 2; break; case RIGHT: centerOffsetWidth = width - nMaxLineWidth; break; } float originalY = -fontMetrics.ascent + centerOffsetHeight; for(int i = 0; i < aryLines.length; ++i) { canvas.drawText(aryLines[i], centerOffsetWidth, originalY + i * (lineHeight + lineGap), textPaint); } } private int getMaxLineWidth(Paint paint, String[] aryLines) { int maximumLineWidth = 0; Rect getStringWidthTemporaryRect = new Rect(); for (int i = aryLines.length - 1; i >= 0; i--) { paint.getTextBounds(aryLines[i], 0, aryLines[i].length(), getStringWidthTemporaryRect); maximumLineWidth = Math.max(maximumLineWidth, getStringWidthTemporaryRect.width()); } return maximumLineWidth; } @Override public int getHeight() { return mHeight; } @Override public int getWidth() { return mWidth; } @Override public Bitmap onLoadBitmap(Config pBitmapConfig) { return mBitmap; } public ITextureSource clone() { return null; } }
Label:
package com.weedong.sprite; import org.anddev.andengine.entity.Entity; import org.anddev.andengine.entity.sprite.Sprite; import org.anddev.andengine.opengl.texture.Texture; import org.anddev.andengine.opengl.texture.region.TextureRegion; import android.graphics.Typeface; import com.weedong.opengl.UltraFontTextureSource; import com.weedong.scene.AbstractBaseScene; import com.weedong.utils.TextureUtils; /** * 文本精灵 * @author * */ public class Label extends Entity { private Sprite mSprite; private Typeface mFace; private float mFontSize; private int mColor; private AbstractBaseScene mScene; public Label(final float pX, final float pY, AbstractBaseScene scene, String text, Typeface face, float fontSize, int color) { this.mFace = face; this.mFontSize = fontSize; this.mColor = color; this.mScene = scene; TextureRegion region = TextureUtils.createFontTextureRegion(scene, text, face, fontSize, color); mSprite = new Sprite(pX, pY, region); this.attachChild(mSprite); } public Sprite getSprite() { return mSprite; } public float getX() { return mSprite.getX(); } public float getY() { return mSprite.getY(); } public float getWidth() { return mSprite.getWidth(); } public float getHeight() { return mSprite.getHeight(); } public void setText(final String text, final Typeface typeface, final float fontSize, final int pColor) { UltraFontTextureSource font = new UltraFontTextureSource(text, typeface, fontSize, pColor); Texture texture = mSprite.getTextureRegion().getTexture(); texture.clearTextureSources(); texture.addTextureSource(font, 0, 0); } public void setText(String text) { setText(text, mFace, mFontSize, mColor); } public void setText(String text, int pColor) { setText(text, mFace, mFontSize, pColor); } }