AndEngine内置了一个TiledSprite类,可以传入TiledTextureRegion的纹理以构造一个可以连续播放的精灵,但必须要先制作好一张动画序列图片,俗称Tiled图。但有时候在游戏项目开发中,美术人员本来的工作量已经很大,而且这种Tiled在需要修改时也带来了工作量。在IPhone的cocos2d里的精灵类有一个runAction的方法可以播放一组序列图片以达到动画功能,但这种方式在使用的时候稍嫌麻烦,于是我封装了一个自定义Tiled精灵类。
package com.weedong.sprite; import static org.anddev.andengine.util.constants.Constants.VERTEX_INDEX_X; import static org.anddev.andengine.util.constants.Constants.VERTEX_INDEX_Y; import java.util.Arrays; import javax.microedition.khronos.opengles.GL10; import org.anddev.andengine.collision.RectangularShapeCollisionChecker; import org.anddev.andengine.collision.ShapeCollisionChecker; import org.anddev.andengine.entity.primitive.BaseRectangle; import org.anddev.andengine.entity.scene.Scene; import org.anddev.andengine.entity.scene.Scene.IOnAreaTouchListener; import org.anddev.andengine.entity.shape.IShape; import org.anddev.andengine.entity.shape.RectangularShape; import org.anddev.andengine.input.touch.TouchEvent; import org.anddev.andengine.opengl.texture.Texture; import org.anddev.andengine.opengl.texture.region.TextureRegion; import org.anddev.andengine.opengl.texture.region.TextureRegionFactory; import org.anddev.andengine.opengl.texture.region.buffer.TextureRegionBuffer; import org.anddev.andengine.opengl.texture.source.ITextureSource; import org.anddev.andengine.opengl.util.GLHelper; import org.anddev.andengine.util.MathUtils; import org.anddev.andengine.util.constants.TimeConstants; import com.weedong.scene.ITextureLoadManager; import com.weedong.utils.TextureUtils; /** * 自定义精灵,几乎可以实现像cocos2d一般的使用非tiled图片进行动画播放的功能<br /> * 用法:在构造函数里传入相关的图片即可 * @author * */ public class CustomTiledSprite extends BaseRectangle { private static final int LOOP_CONTINUOUS = -1; private boolean mAnimationRunning; private long mAnimationProgress; private long mAnimationDuration; private long[] mFrameEndsInNanoseconds; private int mFirstTileIndex; private int mInitialLoopCount; private int mLoopCount; private IAnimationListener mAnimationListener; private int mFrameCount; private int[] mFrames; private TextureRegion[] aryTextureRegion = null; private TextureRegion mCurrentTextureRegion = null; private IOnAreaTouchListener onAreaTouchListener; private Scene mScene; /** * 构造方法 * @param pX * @param pY * @param scene * @param aryTextureSource 传入TextureSource以构造 */ public CustomTiledSprite(float pX, float pY, Scene scene, ITextureSource[] aryTextureSource) { this(pX, pY, scene, aryTextureSource, false); } /** * 构造方法 * @param pX * @param pY * @param scene * @param aryTextureSource 传入TextureSource以构造 * @param bFlippedHorizontal 图片是否水平翻转 */ public CustomTiledSprite(float pX, float pY, Scene scene, ITextureSource[] aryTextureSource, boolean bFlippedHorizontal) { super(pX, pY, aryTextureSource[0].getWidth(),aryTextureSource[0].getHeight()); this.mScene = scene; loadAnimationResource(scene, aryTextureSource, bFlippedHorizontal); } /** * 构造方法 * @param pX * @param pY * @param scene * @param aryTexture 传入TextureRegion以构造 */ public CustomTiledSprite(float pX, float pY, Scene scene, TextureRegion[] aryTexture) { super(pX, pY, aryTexture[0].getWidth(),aryTexture[0].getHeight()); this.mScene = scene; this.aryTextureRegion = aryTexture; mCurrentTextureRegion = aryTextureRegion[0]; this.initBlendFunction(); } /** * 注意,若使用此构造函数,请实例化精灵后,一定要使用 * loadAnimationResource方法加载资源 * @param pX * @param pY */ public CustomTiledSprite(float pX, float pY) { super(pX, pY, 0, 0); } /** * 加载动画资源 * @author * @param scene 当前的场景 * @param aryTextureSource 资源 * @param bFlippedHorizontal 是否水平翻转图片 */ public void loadAnimationResource(Scene scene, ITextureSource[] aryTextureSource, boolean bFlippedHorizontal) { int textureWidth = aryTextureSource[0].getWidth(); int textureHeight = aryTextureSource[0].getHeight(); this.setWidth(textureWidth); this.setHeight(textureHeight); textureWidth = TextureUtils.getTextureCloseWidth(textureWidth); textureHeight = TextureUtils.getTextureCloseHeight(textureHeight); aryTextureRegion = new TextureRegion[aryTextureSource.length]; for(int i = 0; i < aryTextureSource.length; ++i) { Texture texture = new Texture(textureWidth, textureHeight, TextureUtils.autoRecogniseTextureOptions()); TextureRegion textureRegion = TextureRegionFactory.createFromSource(texture, aryTextureSource[i], 0, 0); textureRegion.setFlippedHorizontal(bFlippedHorizontal); aryTextureRegion[i] = textureRegion; ITextureLoadManager textureLoadManager = (ITextureLoadManager)scene; textureLoadManager.loadTextureAndAppendToContainer(texture); } mCurrentTextureRegion = aryTextureRegion[0]; this.initBlendFunction(); } public boolean isAnimationRunning() { return this.mAnimationRunning; } @Override protected void onManagedUpdate(final float pSecondsElapsed) { super.onManagedUpdate(pSecondsElapsed); if(this.mAnimationRunning) { final long nanoSecondsElapsed = (long) (pSecondsElapsed * TimeConstants.NANOSECONDSPERSECOND); this.mAnimationProgress += nanoSecondsElapsed; if(this.mAnimationProgress > this.mAnimationDuration) { this.mAnimationProgress %= this.mAnimationDuration; if(this.mInitialLoopCount != LOOP_CONTINUOUS) { this.mLoopCount--; } } if(this.mInitialLoopCount == LOOP_CONTINUOUS || this.mLoopCount >= 0) { final int currentFrameIndex = this.calculateCurrentFrameIndex(); if(this.mFrames == null) { this.setCurrentTileIndex(this.mFirstTileIndex + currentFrameIndex); } else { this.setCurrentTileIndex(this.mFrames[currentFrameIndex]); } } else { this.mAnimationRunning = false; if(this.mAnimationListener != null) { this.mAnimationListener.onAnimationEnd(this); } } } } public void setCurrentTileIndex(int index) { this.mCurrentTextureRegion = aryTextureRegion[index]; this.updateVertexBuffer(); mCurrentTextureRegion.getTextureBuffer().update(); } public void stopAnimation() { this.mAnimationRunning = false; } public void stopAnimation(final int pTileIndex) { this.mAnimationRunning = false; this.setCurrentTileIndex(pTileIndex); } private int calculateCurrentFrameIndex() { final long animationProgress = this.mAnimationProgress; final long[] frameEnds = this.mFrameEndsInNanoseconds; final int frameCount = this.mFrameCount; for(int i = 0; i < frameCount; i++) { if(frameEnds[i] > animationProgress) { return i; } } return frameCount - 1; } public CustomTiledSprite animate(final long pFrameDurationEach) { return this.animate(pFrameDurationEach, true); } public CustomTiledSprite animate(final long pFrameDurationEach, final boolean pLoop) { return this.animate(pFrameDurationEach, (pLoop) ? LOOP_CONTINUOUS : 0, null); } public CustomTiledSprite animate(final long pFrameDurationEach, final int pLoopCount) { return this.animate(pFrameDurationEach, pLoopCount, null); } public CustomTiledSprite animate(final long pFrameDurationEach, final boolean pLoop, final IAnimationListener pAnimationListener) { return this.animate(pFrameDurationEach, (pLoop) ? LOOP_CONTINUOUS : 0, pAnimationListener); } public CustomTiledSprite animate(final long pFrameDurationEach, final int pLoopCount, final IAnimationListener pAnimationListener) { final long[] frameDurations = new long[aryTextureRegion.length]; Arrays.fill(frameDurations, pFrameDurationEach); return this.animate(frameDurations, pLoopCount, pAnimationListener); } public CustomTiledSprite animate(final long[] pFrameDurations) { return this.animate(pFrameDurations, true); } public CustomTiledSprite animate(final long[] pFrameDurations, final boolean pLoop) { return this.animate(pFrameDurations, (pLoop) ? LOOP_CONTINUOUS : 0, null); } public CustomTiledSprite animate(final long[] pFrameDurations, final int pLoopCount) { return this.animate(pFrameDurations, pLoopCount, null); } public CustomTiledSprite animate(final long[] pFrameDurations, final boolean pLoop, final IAnimationListener pAnimationListener) { return this.animate(pFrameDurations, (pLoop) ? LOOP_CONTINUOUS : 0, pAnimationListener); } public CustomTiledSprite animate(final long[] pFrameDurations, final int pLoopCount, final IAnimationListener pAnimationListener) { return this.animate(pFrameDurations, 0, aryTextureRegion.length - 1, pLoopCount, pAnimationListener); } public CustomTiledSprite animate(final long[] pFrameDurations, final int pFirstTileIndex, final int pLastTileIndex, final boolean pLoop) { return this.animate(pFrameDurations, pFirstTileIndex, pLastTileIndex, (pLoop) ?LOOP_CONTINUOUS : 0, null); } public CustomTiledSprite animate(final long[] pFrameDurations, final int pFirstTileIndex, final int pLastTileIndex, final int pLoopCount) { return this.animate(pFrameDurations, pFirstTileIndex, pLastTileIndex, pLoopCount, null); } public CustomTiledSprite animate(final long[] pFrameDurations, final int[] pFrames, final int pLoopCount) { return this.animate(pFrameDurations, pFrames, pLoopCount, null); } /** * Animate specifics frames * * @param pFrameDurations must have the same length as pFrames. * @param pFrames indices of the frames to animate. * @param pLoopCount * @param pAnimationListener */ public CustomTiledSprite animate(final long[] pFrameDurations, final int[] pFrames, final int pLoopCount, final IAnimationListener pAnimationListener) { final int frameCount = pFrames.length; if(pFrameDurations.length != frameCount) { throw new IllegalArgumentException("pFrameDurations must have the same length as pFrames."); } return this.init(pFrameDurations, frameCount, pFrames, 0, pLoopCount, pAnimationListener); } /** * @param pFrameDurations * must have the same length as pFirstTileIndex to * pLastTileIndex. * @param pFirstTileIndex * @param pLastTileIndex * @param pLoopCount * @param pAnimationListener */ public CustomTiledSprite animate(final long[] pFrameDurations, final int pFirstTileIndex, final int pLastTileIndex, final int pLoopCount, final IAnimationListener pAnimationListener) { if(pLastTileIndex - pFirstTileIndex < 1) { throw new IllegalArgumentException("An animation needs at least two tiles to animate between."); } final int frameCount = (pLastTileIndex - pFirstTileIndex) + 1; if(pFrameDurations.length != frameCount) { throw new IllegalArgumentException("pFrameDurations must have the same length as pFirstTileIndex to pLastTileIndex."); } return this.init(pFrameDurations, frameCount, null, pFirstTileIndex, pLoopCount, pAnimationListener); } private CustomTiledSprite init(final long[] pFrameDurations, final int frameCount, final int[] pFrames, final int pFirstTileIndex, final int pLoopCount, final IAnimationListener pAnimationListener) { this.mFrameCount = frameCount; this.mAnimationListener = pAnimationListener; this.mInitialLoopCount = pLoopCount; this.mLoopCount = pLoopCount; this.mFrames = pFrames; this.mFirstTileIndex = pFirstTileIndex; if(this.mFrameEndsInNanoseconds == null || this.mFrameCount > this.mFrameEndsInNanoseconds.length) { this.mFrameEndsInNanoseconds = new long[this.mFrameCount]; } final long[] frameEndsInNanoseconds = this.mFrameEndsInNanoseconds; MathUtils.arraySumInto(pFrameDurations, frameEndsInNanoseconds, TimeConstants.NANOSECONDSPERMILLISECOND); final long lastFrameEnd = frameEndsInNanoseconds[this.mFrameCount - 1]; this.mAnimationDuration = lastFrameEnd; this.mAnimationProgress = 0; this.mAnimationRunning = true; return this; } public static interface IAnimationListener { public void onAnimationEnd(final CustomTiledSprite pAnimatedSprite); } @Override public void reset() { super.reset(); this.initBlendFunction(); } @Override protected void onInitDraw(final GL10 pGL) { super.onInitDraw(pGL); GLHelper.enableTextures(pGL); GLHelper.enableTexCoordArray(pGL); } @Override protected void onApplyTransformations(final GL10 pGL) { super.onApplyTransformations(pGL); this.mCurrentTextureRegion.onApply(pGL); } private void initBlendFunction() { if(this.mCurrentTextureRegion.getTexture().getTextureOptions().mPreMultipyAlpha) { this.setBlendFunction(BLENDFUNCTION_SOURCE_PREMULTIPLYALPHA_DEFAULT, BLENDFUNCTION_DESTINATION_PREMULTIPLYALPHA_DEFAULT); } } @Override public boolean onAreaTouched(TouchEvent pSceneTouchEvent, float pTouchAreaLocalX, float pTouchAreaLocalY) { if(this.onAreaTouchListener != null) return this.onAreaTouchListener.onAreaTouched(pSceneTouchEvent, this, pTouchAreaLocalX, pTouchAreaLocalY); return super.onAreaTouched(pSceneTouchEvent, pTouchAreaLocalX, pTouchAreaLocalY); } /** * 设置点击监听器 * @author * @param listener * @param bRegisterTouchArea 是否注册点击区域 */ public void addAreaTouchedListener(IOnAreaTouchListener listener, boolean bRegisterTouchArea) { if(bRegisterTouchArea) mScene.registerTouchArea(this); this.onAreaTouchListener = listener; } /** * 设置点击监听器 * @author * @param listener */ public void addAreaTouchedListener(IOnAreaTouchListener listener) { this.onAreaTouchListener = listener; } /** * 取消注册点击区域 */ public void unRegisterTouchArea() { this.mScene.unregisterTouchArea(this); } /** * 水平翻转 * @author * @param nFirstTiled * @param nLastTiled */ public void setFlippedHorizontal(int nFirstTiled, int nLastTiled) { for(int i = nFirstTiled; i < nLastTiled; i++) { aryTextureRegion[i].setFlippedHorizontal(true); } } /** * 取消水平翻转 * @author */ public void resetFlippedHorizontal() { for(TextureRegion textureRegion : aryTextureRegion) { textureRegion.setFlippedHorizontal(false); } } /** * 垂直翻转 * @author * @param nFirstTiled * @param nLastTiled */ public void setFlippedVertical(int nFirstTiled, int nLastTiled) { for(int i = nFirstTiled; i < nLastTiled; i++) { aryTextureRegion[i].setFlippedVertical(true); } } /** * 取消垂直翻转 * @author */ public void resetFlippedVertical() { for(TextureRegion textureRegion : aryTextureRegion) { textureRegion.setFlippedVertical(false); } } /** * 克隆纹理 * @author * @return */ public TextureRegion[] cloneTextureRegion() { TextureRegion[] ret = new TextureRegion[aryTextureRegion.length]; for(int i = 0; i < ret.length; ++i) { ret[i] = aryTextureRegion[i].clone(); } return ret; } @Override protected void finalize() throws Throwable { super.finalize(); for(TextureRegion region : aryTextureRegion) { final TextureRegionBuffer textureRegionBuffer = region.getTextureBuffer(); if(textureRegionBuffer.isManaged()) { textureRegionBuffer.unloadFromActiveBufferObjectManager(); } } } @Override public boolean collidesWith(final IShape pOtherShape) { if(pOtherShape instanceof RectangularShape) { final RectangularShape pOtherRectangularShape = (RectangularShape) pOtherShape; return CustomRectangularShapeCollisionChecker.checkCollision(this, pOtherRectangularShape); } else { return false; } } public static class CustomRectangularShapeCollisionChecker extends ShapeCollisionChecker { // =========================================================== // Constants // =========================================================== private static final int RECTANGULARSHAPE_VERTEX_COUNT = 4; private static final float[] VERTICES_CONTAINS_TMP = new float[2 * RECTANGULARSHAPE_VERTEX_COUNT]; private static final float[] VERTICES_COLLISION_TMP_A = new float[2 * RECTANGULARSHAPE_VERTEX_COUNT]; private static final float[] VERTICES_COLLISION_TMP_B = new float[2 * RECTANGULARSHAPE_VERTEX_COUNT]; public static boolean checkContains(final RectangularShape pRectangularShape, final float pX, final float pY) { RectangularShapeCollisionChecker.fillVertices(pRectangularShape, VERTICES_CONTAINS_TMP); return ShapeCollisionChecker.checkContains(VERTICES_CONTAINS_TMP, 2 * RECTANGULARSHAPE_VERTEX_COUNT, pX, pY); } public static boolean checkCollision(final RectangularShape pRectangularShapeA, final RectangularShape pRectangularShapeB) { fillVertices(pRectangularShapeA, VERTICES_COLLISION_TMP_A); fillVertices(pRectangularShapeB, VERTICES_COLLISION_TMP_B); return ShapeCollisionChecker.checkCollision(2 * RECTANGULARSHAPE_VERTEX_COUNT, VERTICES_COLLISION_TMP_A, 2 * RECTANGULARSHAPE_VERTEX_COUNT, VERTICES_COLLISION_TMP_B); } public static void fillVertices(final RectangularShape pRectangularShape, final float[] pVertices) { final float left = 0; final float top = 0; final float right = pRectangularShape.getWidth() * pRectangularShape.getScaleX(); final float bottom = pRectangularShape.getHeight() * pRectangularShape.getScaleY(); pVertices[0 + VERTEX_INDEX_X] = left; pVertices[0 + VERTEX_INDEX_Y] = top; pVertices[2 + VERTEX_INDEX_X] = right; pVertices[2 + VERTEX_INDEX_Y] = top; pVertices[4 + VERTEX_INDEX_X] = right; pVertices[4 + VERTEX_INDEX_Y] = bottom; pVertices[6 + VERTEX_INDEX_X] = left; pVertices[6 + VERTEX_INDEX_Y] = bottom; pRectangularShape.getLocalToSceneTransformation().transform(pVertices); // Log.i("CustomTiledSprite", "collision width:" + right + ",height:" + bottom + ",final collision width:" + pVertices[2 + VERTEX_INDEX_X] + ",height:" + pVertices[6 + VERTEX_INDEX_Y]); } } }