package org.andengine.entity.sprite;
import java.util.Arrays;
import org.andengine.opengl.shader.PositionColorTextureCoordinatesShaderProgram;
import org.andengine.opengl.shader.ShaderProgram;
import org.andengine.opengl.texture.region.ITiledTextureRegion;
import org.andengine.opengl.vbo.VertexBufferObjectManager;
import org.andengine.opengl.vbo.VertexBufferObject.DrawType;
import org.andengine.util.math.MathUtils;
import org.andengine.util.time.TimeConstants;
/**
* (c) 2010 Nicolas Gramlich
* (c) 2011 Zynga Inc.
*
* @author Nicolas Gramlich
* @since 15:25:46 - 10.03.2010
*/
public class AnimatedSprite extends TiledSprite implements TimeConstants {
// ===========================================================
// Constants
// ===========================================================
private static final int LOOP_CONTINUOUS = -1;
private static final int FRAMEINDEX_INVALID = -1;
// ===========================================================
// Fields
// ===========================================================
private boolean mAnimationRunning;
private long mAnimationProgress;
private long mAnimationDuration;
private long[] mFrameEndsInNanoseconds;
private int mFirstTileIndex;
private int mInitialLoopCount;
private int mRemainingLoopCount;
private IAnimationListener mAnimationListener;
private int mCurrentFrameIndex;
private int mFrameCount;
private int[] mFrames;
private boolean mAnimationStartedFired;
// ===========================================================
// Constructors
// ===========================================================
public AnimatedSprite(final float pX, final float pY, final ITiledTextureRegion pTiledTextureRegion, final VertexBufferObjectManager pVertexBufferObjectManager) {
super(pX, pY, pTiledTextureRegion, pVertexBufferObjectManager, DrawType.DYNAMIC);
}
public AnimatedSprite(final float pX, final float pY, final ITiledTextureRegion pTiledTextureRegion, final VertexBufferObjectManager pVertexBufferObjectManager, final ShaderProgram pShaderProgram) {
super(pX, pY, pTiledTextureRegion, pVertexBufferObjectManager, DrawType.DYNAMIC, pShaderProgram);
}
public AnimatedSprite(final float pX, final float pY, final ITiledTextureRegion pTiledTextureRegion, final VertexBufferObjectManager pVertexBufferObjectManager, final DrawType pDrawType) {
super(pX, pY, pTiledTextureRegion, pVertexBufferObjectManager, pDrawType);
}
public AnimatedSprite(final float pX, final float pY, final ITiledTextureRegion pTiledTextureRegion, final VertexBufferObjectManager pVertexBufferObjectManager, final DrawType pDrawType, final ShaderProgram pShaderProgram) {
super(pX, pY, pTiledTextureRegion, pVertexBufferObjectManager, pDrawType, pShaderProgram);
}
public AnimatedSprite(final float pX, final float pY, final ITiledTextureRegion pTiledTextureRegion, final ITiledSpriteVertexBufferObject pTiledSpriteVertexBufferObject) {
super(pX, pY, pTiledTextureRegion, pTiledSpriteVertexBufferObject);
}
public AnimatedSprite(final float pX, final float pY, final ITiledTextureRegion pTiledTextureRegion, final ITiledSpriteVertexBufferObject pTiledSpriteVertexBufferObject, final ShaderProgram pShaderProgram) {
super(pX, pY, pTiledTextureRegion, pTiledSpriteVertexBufferObject, pShaderProgram);
}
public AnimatedSprite(final float pX, final float pY, final float pWidth, final float pHeight, final ITiledTextureRegion pTiledTextureRegion, final VertexBufferObjectManager pVertexBufferObjectManager) {
super(pX, pY, pWidth, pHeight, pTiledTextureRegion, pVertexBufferObjectManager, DrawType.DYNAMIC);
}
public AnimatedSprite(final float pX, final float pY, final float pWidth, final float pHeight, final ITiledTextureRegion pTiledTextureRegion, final VertexBufferObjectManager pVertexBufferObjectManager, final ShaderProgram pShaderProgram) {
super(pX, pY, pWidth, pHeight, pTiledTextureRegion, pVertexBufferObjectManager, DrawType.DYNAMIC, pShaderProgram);
}
public AnimatedSprite(final float pX, final float pY, final float pWidth, final float pHeight, final ITiledTextureRegion pTiledTextureRegion, final VertexBufferObjectManager pVertexBufferObjectManager, final DrawType pDrawType) {
super(pX, pY, pWidth, pHeight, pTiledTextureRegion, pVertexBufferObjectManager, pDrawType);
}
public AnimatedSprite(final float pX, final float pY, final float pWidth, final float pHeight, final ITiledTextureRegion pTiledTextureRegion, final VertexBufferObjectManager pVertexBufferObjectManager, final DrawType pDrawType, final ShaderProgram pShaderProgram) {
super(pX, pY, pWidth, pHeight, pTiledTextureRegion, pVertexBufferObjectManager, pDrawType, pShaderProgram);
}
public AnimatedSprite(final float pX, final float pY, final float pWidth, final float pHeight, final ITiledTextureRegion pTiledTextureRegion, final ITiledSpriteVertexBufferObject pTiledSpriteVertexBufferObject) {
super(pX, pY, pWidth, pHeight, pTiledTextureRegion, pTiledSpriteVertexBufferObject, PositionColorTextureCoordinatesShaderProgram.getInstance());
}
public AnimatedSprite(final float pX, final float pY, final float pWidth, final float pHeight, final ITiledTextureRegion pTiledTextureRegion, final ITiledSpriteVertexBufferObject pTiledSpriteVertexBufferObject, final ShaderProgram pShaderProgram) {
super(pX, pY, pWidth, pHeight, pTiledTextureRegion, pTiledSpriteVertexBufferObject, pShaderProgram);
}
// ===========================================================
// Getter & Setter
// ===========================================================
public boolean isAnimationRunning() {
return this.mAnimationRunning;
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
protected void onManagedUpdate(final float pSecondsElapsed) {
super.onManagedUpdate(pSecondsElapsed);
if(this.mAnimationRunning) {
if(!this.mAnimationStartedFired && this.mAnimationProgress == 0 ) {
this.mAnimationStartedFired = true;
if(this.mFrames == null) {
this.setCurrentTileIndex(this.mFirstTileIndex);
} else {
this.setCurrentTileIndex(this.mFrames[0]);
}
this.mCurrentFrameIndex = 0;
if(this.mAnimationListener != null) {
this.mAnimationListener.onAnimationStarted(this, this.mInitialLoopCount);
this.mAnimationListener.onAnimationFrameChanged(this, AnimatedSprite.FRAMEINDEX_INVALID, 0);
}
}
final long nanoSecondsElapsed = (long) (pSecondsElapsed * TimeConstants.NANOSECONDS_PER_SECOND);
this.mAnimationProgress += nanoSecondsElapsed;
if(this.mInitialLoopCount == AnimatedSprite.LOOP_CONTINUOUS) {
while(this.mAnimationProgress > this.mAnimationDuration) {
this.mAnimationProgress -= this.mAnimationDuration;
if(this.mAnimationListener != null) {
this.mAnimationListener.onAnimationLoopFinished(this, this.mRemainingLoopCount, this.mInitialLoopCount);
}
}
} else {
while(this.mAnimationProgress > this.mAnimationDuration) {
this.mAnimationProgress -= this.mAnimationDuration;
this.mRemainingLoopCount--;
if(this.mRemainingLoopCount < 0) {
break;
} else if(this.mAnimationListener != null) {
this.mAnimationListener.onAnimationLoopFinished(this, this.mRemainingLoopCount, this.mInitialLoopCount);
}
}
}
if(this.mInitialLoopCount == AnimatedSprite.LOOP_CONTINUOUS || this.mRemainingLoopCount >= 0) {
final int newFrameIndex = this.calculateCurrentFrameIndex();
if(this.mCurrentFrameIndex != newFrameIndex) {
if(this.mFrames == null) {
this.setCurrentTileIndex(this.mFirstTileIndex + newFrameIndex);
} else {
this.setCurrentTileIndex(this.mFrames[newFrameIndex]);
}
if(this.mAnimationListener != null) {
this.mAnimationListener.onAnimationFrameChanged(this, this.mCurrentFrameIndex, newFrameIndex);
}
}
this.mCurrentFrameIndex = newFrameIndex;
} else {
this.mAnimationRunning = false;
if(this.mAnimationListener != null) {
this.mAnimationListener.onAnimationFinished(this);
}
}
}
}
// ===========================================================
// Methods
// ===========================================================
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 AnimatedSprite animate(final long pFrameDurationEach) {
return this.animate(pFrameDurationEach, true);
}
public AnimatedSprite animate(final long pFrameDurationEach, final IAnimationListener pAnimationListener) {
return this.animate(pFrameDurationEach, true, pAnimationListener);
}
public AnimatedSprite animate(final long pFrameDurationEach, final boolean pLoop) {
return this.animate(pFrameDurationEach, (pLoop) ? AnimatedSprite.LOOP_CONTINUOUS : 0, null);
}
public AnimatedSprite animate(final long pFrameDurationEach, final boolean pLoop, final IAnimationListener pAnimationListener) {
return this.animate(pFrameDurationEach, (pLoop) ? AnimatedSprite.LOOP_CONTINUOUS : 0, pAnimationListener);
}
public AnimatedSprite animate(final long pFrameDurationEach, final int pLoopCount) {
return this.animate(pFrameDurationEach, pLoopCount, null);
}
public AnimatedSprite animate(final long pFrameDurationEach, final int pLoopCount, final IAnimationListener pAnimationListener) {
final long[] frameDurations = new long[this.getTiledTextureRegion().getTileCount()];
Arrays.fill(frameDurations, pFrameDurationEach);
return this.animate(frameDurations, pLoopCount, pAnimationListener);
}
public AnimatedSprite animate(final long[] pFrameDurations) {
return this.animate(pFrameDurations, true);
}
public AnimatedSprite animate(final long[] pFrameDurations, final IAnimationListener pAnimationListener) {
return this.animate(pFrameDurations, true, pAnimationListener);
}
public AnimatedSprite animate(final long[] pFrameDurations, final boolean pLoop) {
return this.animate(pFrameDurations, (pLoop) ? AnimatedSprite.LOOP_CONTINUOUS : 0, null);
}
public AnimatedSprite animate(final long[] pFrameDurations, final boolean pLoop, final IAnimationListener pAnimationListener) {
return this.animate(pFrameDurations, (pLoop) ? AnimatedSprite.LOOP_CONTINUOUS : 0, pAnimationListener);
}
public AnimatedSprite animate(final long[] pFrameDurations, final int pLoopCount) {
return this.animate(pFrameDurations, pLoopCount, null);
}
public AnimatedSprite animate(final long[] pFrameDurations, final int pLoopCount, final IAnimationListener pAnimationListener) {
return this.animate(pFrameDurations, 0, this.getTiledTextureRegion().getTileCount() - 1, pLoopCount, pAnimationListener);
}
public AnimatedSprite animate(final long[] pFrameDurations, final int pFirstTileIndex, final int pLastTileIndex, final boolean pLoop) {
return this.animate(pFrameDurations, pFirstTileIndex, pLastTileIndex, (pLoop) ? AnimatedSprite.LOOP_CONTINUOUS : 0, null);
}
public AnimatedSprite animate(final long[] pFrameDurations, final int pFirstTileIndex, final int pLastTileIndex, final boolean pLoop, final IAnimationListener pAnimationListener) {
return this.animate(pFrameDurations, pFirstTileIndex, pLastTileIndex, (pLoop) ? AnimatedSprite.LOOP_CONTINUOUS : 0, pAnimationListener);
}
public AnimatedSprite animate(final long[] pFrameDurations, final int pFirstTileIndex, final int pLastTileIndex, final int pLoopCount) {
return this.animate(pFrameDurations, pFirstTileIndex, pLastTileIndex, pLoopCount, null);
}
public AnimatedSprite 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 AnimatedSprite 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 AnimatedSprite 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 AnimatedSprite init(final long[] pFrameDurations, final int pFrameCount, final int[] pFrames, final int pFirstTileIndex, final int pLoopCount, final IAnimationListener pAnimationListener) {
this.mAnimationStartedFired = false;
this.mFrameCount = pFrameCount;
this.mAnimationListener = pAnimationListener;
this.mInitialLoopCount = pLoopCount;
this.mRemainingLoopCount = 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.NANOSECONDS_PER_MILLISECOND);
final long lastFrameEnd = frameEndsInNanoseconds[this.mFrameCount - 1];
this.mAnimationDuration = lastFrameEnd;
this.mAnimationProgress = 0;
this.mAnimationRunning = true;
return this;
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
public static interface IAnimationListener {
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
/**
* @param pAnimatedSprite
* @param pInitialLoopCount is {@link AnimatedSprite#LOOP_CONTINUOUS} when {@link AnimatedSprite} loops infinitely.
*/
public void onAnimationStarted(final AnimatedSprite pAnimatedSprite, final int pInitialLoopCount);
/**
* @param pAnimatedSprite
* @param pOldFrameIndex equals {@link AnimatedSprite#FRAMEINDEX_INVALID}, the first time {@link IAnimationListener#onAnimationFrameChanged(AnimatedSprite, int, int)} is called.
* @param pNewFrameIndex the new frame index of the currently active animation.
*/
public void onAnimationFrameChanged(final AnimatedSprite pAnimatedSprite, final int pOldFrameIndex, final int pNewFrameIndex);
/**
* @param pAnimatedSprite
* @param pRemainingLoopCount is {@link AnimatedSprite#LOOP_CONTINUOUS} when {@link AnimatedSprite} loops infinitely.
* @param pInitialLoopCount is {@link AnimatedSprite#LOOP_CONTINUOUS} when {@link AnimatedSprite} loops infinitely.
*/
public void onAnimationLoopFinished(final AnimatedSprite pAnimatedSprite, final int pRemainingLoopCount, final int pInitialLoopCount);
public void onAnimationFinished(final AnimatedSprite pAnimatedSprite);
}
}