AndEngine进阶之自定义Tiled精灵

18 篇文章 0 订阅
7 篇文章 0 订阅

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]);
		}
	}
}


评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值