package org.andengine.entity.particle; import java.util.ArrayList; import org.andengine.engine.camera.Camera; import org.andengine.entity.Entity; import org.andengine.entity.IEntity; import org.andengine.entity.IEntityFactory; import org.andengine.entity.particle.emitter.IParticleEmitter; import org.andengine.entity.particle.initializer.IParticleInitializer; import org.andengine.entity.particle.modifier.IParticleModifier; import org.andengine.opengl.util.GLState; import org.andengine.util.Constants; import org.andengine.util.math.MathUtils; import android.util.FloatMath; /** * (c) 2010 Nicolas Gramlich * (c) 2011 Zynga Inc. * * @author Nicolas Gramlich * @since 19:42:27 - 14.03.2010 */ public class ParticleSystem<T extends IEntity> extends Entity { // =========================================================== // Constants // =========================================================== private static final float[] POSITION_OFFSET_CONTAINER = new float[2]; // =========================================================== // Fields // =========================================================== protected final IEntityFactory<T> mEntityFactory; protected final IParticleEmitter mParticleEmitter; protected final Particle<T>[] mParticles; protected final ArrayList<IParticleInitializer<T>> mParticleInitializers = new ArrayList<IParticleInitializer<T>>(); protected final ArrayList<IParticleModifier<T>> mParticleModifiers = new ArrayList<IParticleModifier<T>>(); private final float mRateMinimum; private final float mRateMaximum; private boolean mParticlesSpawnEnabled = true; protected final int mParticlesMaximum; protected int mParticlesAlive; private float mParticlesDueToSpawn; // =========================================================== // Constructors // =========================================================== public ParticleSystem(final IEntityFactory<T> pEntityFactory, final IParticleEmitter pParticleEmitter, final float pRateMinimum, final float pRateMaximum, final int pParticlesMaximum) { this(0, 0, pEntityFactory, pParticleEmitter, pRateMinimum, pRateMaximum, pParticlesMaximum); } @SuppressWarnings("unchecked") public ParticleSystem(final float pX, final float pY, final IEntityFactory<T> pEntityFactory, final IParticleEmitter pParticleEmitter, final float pRateMinimum, final float pRateMaximum, final int pParticlesMaximum) { super(pX, pY); this.mEntityFactory = pEntityFactory; this.mParticleEmitter = pParticleEmitter; this.mParticles = new Particle[pParticlesMaximum]; this.mRateMinimum = pRateMinimum; this.mRateMaximum = pRateMaximum; this.mParticlesMaximum = pParticlesMaximum; this.registerUpdateHandler(this.mParticleEmitter); } // =========================================================== // Getter & Setter // =========================================================== public boolean isParticlesSpawnEnabled() { return this.mParticlesSpawnEnabled; } public void setParticlesSpawnEnabled(final boolean pParticlesSpawnEnabled) { this.mParticlesSpawnEnabled = pParticlesSpawnEnabled; } public IEntityFactory<T> getParticleFactory() { return this.mEntityFactory; } public IParticleEmitter getParticleEmitter() { return this.mParticleEmitter; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public void reset() { super.reset(); this.mParticlesDueToSpawn = 0; this.mParticlesAlive = 0; } @Override protected void onManagedDraw(final GLState pGLState, final Camera pCamera) { for(int i = this.mParticlesAlive - 1; i >= 0; i--) { this.mParticles[i].onDraw(pGLState, pCamera); } } @Override protected void onManagedUpdate(final float pSecondsElapsed) { super.onManagedUpdate(pSecondsElapsed); if(this.isParticlesSpawnEnabled()) { this.spawnParticles(pSecondsElapsed); } final int particleModifierCountMinusOne = this.mParticleModifiers.size() - 1; for(int i = this.mParticlesAlive - 1; i >= 0; i--) { final Particle<T> particle = this.mParticles[i]; /* Apply all particleModifiers */ for(int j = particleModifierCountMinusOne; j >= 0; j--) { this.mParticleModifiers.get(j).onUpdateParticle(particle); } particle.onUpdate(pSecondsElapsed); if(particle.mExpired){ this.mParticlesAlive--; this.moveParticleToEnd(i); } } } protected void moveParticleToEnd(final int pIndex) { final Particle<T> particle = this.mParticles[pIndex]; final int particlesToCopy = this.mParticlesAlive - pIndex; if(particlesToCopy > 0) { System.arraycopy(this.mParticles, pIndex + 1, this.mParticles, pIndex, particlesToCopy); } this.mParticles[this.mParticlesAlive] = particle; /* This mode of swapping particles is faster than copying tons of array elements, * but it doesn't respect the 'lifetime' of the particles. */ // particles[i] = particles[this.mParticlesAlive]; // particles[this.mParticlesAlive] = particle; } // =========================================================== // Methods // =========================================================== public void addParticleModifier(final IParticleModifier<T> pParticleModifier) { this.mParticleModifiers.add(pParticleModifier); } public void removeParticleModifier(final IParticleModifier<T> pParticleModifier) { this.mParticleModifiers.remove(pParticleModifier); } public void addParticleInitializer(final IParticleInitializer<T> pParticleInitializer) { this.mParticleInitializers.add(pParticleInitializer); } public void removeParticleInitializer(final IParticleInitializer<T> pParticleInitializer) { this.mParticleInitializers.remove(pParticleInitializer); } private void spawnParticles(final float pSecondsElapsed) { final float currentRate = this.determineCurrentRate(); final float newParticlesThisFrame = currentRate * pSecondsElapsed; this.mParticlesDueToSpawn += newParticlesThisFrame; final int particlesToSpawnThisFrame = Math.min(this.mParticlesMaximum - this.mParticlesAlive, (int)FloatMath.floor(this.mParticlesDueToSpawn)); this.mParticlesDueToSpawn -= particlesToSpawnThisFrame; for(int i = 0; i < particlesToSpawnThisFrame; i++){ this.spawnParticle(); } } private void spawnParticle() { if(this.mParticlesAlive < this.mParticlesMaximum){ Particle<T> particle = this.mParticles[this.mParticlesAlive]; /* New particle needs to be created. */ this.mParticleEmitter.getPositionOffset(ParticleSystem.POSITION_OFFSET_CONTAINER); final float x = ParticleSystem.POSITION_OFFSET_CONTAINER[Constants.VERTEX_INDEX_X]; final float y = ParticleSystem.POSITION_OFFSET_CONTAINER[Constants.VERTEX_INDEX_Y]; if(particle == null) { particle = new Particle<T>(); this.mParticles[this.mParticlesAlive] = particle; particle.setEntity(this.mEntityFactory.create(x, y)); } else { particle.reset(); particle.getEntity().setPosition(x, y); } /* Apply particle initializers. */ { for(int i = this.mParticleInitializers.size() - 1; i >= 0; i--) { this.mParticleInitializers.get(i).onInitializeParticle(particle); } for(int i = this.mParticleModifiers.size() - 1; i >= 0; i--) { this.mParticleModifiers.get(i).onInitializeParticle(particle); } } this.mParticlesAlive++; } } protected float determineCurrentRate() { if(this.mRateMinimum == this.mRateMaximum){ return this.mRateMinimum; } else { return MathUtils.random(this.mRateMinimum, this.mRateMaximum); } } // =========================================================== // Inner and Anonymous Classes // =========================================================== }