To welcome the new Year i wanted to create a small Firework.
This tutorial is meant for people new to jME.
We will make use of ParticleEffects for the explosions, a Controller to shoot the Firework into the Sky and some 3DTest.
In the end it should look something like this:
A Sparkling texture for the explosion particles.
This texture has a black background, which will be transparent later.
The Code is split up into 3 files:
What needs to be done:
In the main update loop we create a new Firework periodically.
A Controller (FireworkController.java) moves the firework into the sky.
The Controller also keeps track of the lifetime of the Firework. Once the lifetime of a firework has expired,
the Controller spawns a new Explosion at the current location of the Firework and removes it from the Scene.
A firework will be represented by a simple sphere.
A trail of particles is attached to the sphere to represent the .. you guessed it, firework trail :)
Step by Step:
The next few lines will explain the most important code parts.
These code parts alone will make no sense, they are just for explanation. The whole source files are attached below.
To be able to move the sphere and particle trail together, we create a parent node for both elements.
Then we attach the sphere and particle effect to this parent node. Since the sphere and particle effect
are now childs of this node, they will automatically follow the node everywhere.
The FireworkController will later only move the Node.
public void spawnFirework() { Sphere sphere = new Sphere("s", 5, 5, 0.06f); Node spNode = new Node("spNode"); spNode.attachChild(sphere); spNode.setModelBound(new BoundingBox()); spNode.updateModelBound(); spNode.getLocalTranslation().set(start); // color the Sphere yellow sphere.setRenderState(yellowMaterialState); // add a controller to move the Sphere upwards spNode.addController(new FireworkController(spNode, lifetime, speed)); spNode.attachChild(createTrail()); // attach the Sphere to the scene. rootNode.attachChild(spNode); rootNode.updateGeometricState(0, true); rootNode.updateRenderState(); }
We create a ParticleMesh with the ParticleFactory and set the different attributes to make it look like a small trail.
Important is to set the RepeatType to RT_CYCLE, so that the trail never stops until we remove it from the scene.
The explosion effect for example will be set to RT_CLAMP, cause we only want to play an explosion once.
The returned ParticleMesh is then attached as a child to the spNode (see above).
public ParticleMesh createTrail() { ParticleMesh particleGeom = ParticleFactory.buildParticles("trail", 10); // add a gravity effect to the particle effect particleGeom.addInfluence(SimpleParticleInfluenceFactory .createBasicGravity(new Vector3f(0, -0.15f, 0), true)); particleGeom.setEmissionDirection(new Vector3f(0.0f, 1.0f, 0.0f)); // allow to shoot only downwards in a 20° angle particleGeom.setMaximumAngle(FastMath.DEG_TO_RAD * 170); particleGeom.setMinimumAngle(FastMath.DEG_TO_RAD * 190); particleGeom.getParticleController().setSpeed(0.2f); particleGeom.setMinimumLifeTime(10.0f); particleGeom.setMaximumLifeTime(50.0f); particleGeom.setStartSize(0.15f); particleGeom.setEndSize(0.01f); particleGeom.getParticleController().setControlFlow(false); // set the repeat type to looping particleGeom.getParticleController().setRepeatType(Controller.RT_CYCLE); particleGeom.warmUp(10); particleGeom.setInitialVelocity(0.005f); // the trail should be a simple white particleGeom.setStartColor(ColorRGBA.white.clone()); particleGeom.setEndColor(ColorRGBA.white.clone()); // apply alpha, texture and ZBuffer renderstates ... return particleGeom; }
Controllers can be attached to scene elements.
The main part of an controller is the update() method, there we can move the object it is attached to around over time.
Our controller needs to know the lifetime and speed of the firework.
Every update cycle, we reduce the lifetime, once the lifetime is 0, we spawn an explosion effect at the current location of the sphere and remove it from the scene.
public void update(float time) { lifetime -= time; // add hSpeed to the X-Axis and speed to the Y-Axis object.getLocalTranslation().addLocal((leftright==true?1:-1)* hSpeed*time, vSpeed*time, 0); if (lifetime <= 0) { // the life has come to an end // create a nice explosion at the current location and // remove this controller from the object and the object from the scene ExplosionFactory.get().spawnExplosion(object.getLocalTranslation().clone()); object.removeController(this); object.removeFromParent(); } }
In step 3 we spawn an explosion with ExplosionFactory.get().spawnExplosion().
The explosion effect could have been made with a simple method like the trail effect.
But then we would have to always create new explosions and let the GarbageCollector delete the old ones, which is not very smart performance wise.
With the ExplosionFactory class we create a pool of about 20 predefined particle effect which can be reused.
If at one time, more than 20 effect need to be active, the pool gets extended automaticly.
see ExplosionFactory.java
The main Application.
import java.awt.Font; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import com.jme.app.SimpleGame; import com.jme.bounding.BoundingBox; import com.jme.bounding.OrientedBoundingBox; import com.jme.image.Texture; import com.jme.math.FastMath; import com.jme.math.Vector3f; import com.jme.renderer.ColorRGBA; import com.jme.scene.Controller; import com.jme.scene.Node; import com.jme.scene.shape.Sphere; import com.jme.scene.state.AlphaState; import com.jme.scene.state.MaterialState; import com.jme.scene.state.TextureState; import com.jme.scene.state.ZBufferState; import com.jme.system.DisplaySystem; import com.jme.util.TextureManager; import com.jme.util.Timer; import com.jmex.effects.particles.ParticleFactory; import com.jmex.effects.particles.ParticleMesh; import com.jmex.effects.particles.SimpleParticleInfluenceFactory; import com.jmex.font3d.Font3D; import com.jmex.font3d.Text3D; import com.jmex.font3d.effects.Font3DTexture; public class HappyNewYear extends SimpleGame { /** 3DText font and text */ private Font3D myfont; private Text3D text; private float textwidth; private Random rand; /** spawnrate of new fireworks */ private float spawnRate = 0.5f; private float lastSpawn = 0; /** material state for the sphere */ private MaterialState yellowMaterialState; /** * Set up all the necessary stuff. * Init the Explosionfactory, create the 3DText. */ @Override protected void simpleInitGame() { display.setTitle("Happy New Year"); // create a 3DFont from a system font (Arial) */ myfont = new Font3D(new Font("Arial", Font.PLAIN, 2), 0.1, true, true,true); // color the Text Font3DGradient gradient = new Font3DGradient(Vector3f.UNIT_Y, ColorRGBA.white, ColorRGBA.red); gradient.applyEffect(myfont); text = myfont.createText("HAPPY NEW YEAR 2008!", 2, 0); // scale Z-Axis of the font down to make it slimmer text.setLocalScale(new Vector3f(1, 1, 0.1f)); text.updateWorldBound(); // get the width of our 3DText, we need it later to spawn the Firework OrientedBoundingBox bb = (OrientedBoundingBox) text.getWorldBound(); textwidth = bb.extent.x; rootNode.attachChild(text); // initialize the ExplosionFactory and some other stuff ExplosionFactory.init(rootNode); rand = new Random(); yellowMaterialState = display.getRenderer().createMaterialState(); yellowMaterialState.setAmbient(ColorRGBA.yellow); // move the cam a bit to the left and backwards cam.setLocation(new Vector3f(5, 0, 15)); } /** * The update Method gets called every frame. * Spawn a new Firework periodical. */ @Override protected void simpleUpdate() { super.simpleUpdate(); float time = Timer.getTimer().getTimeInSeconds(); if (lastSpawn + spawnRate < time) { spawnFirework(); lastSpawn = time; } } /** * Spawn a new firework. * Creates a new Sphere at a random position below the 3DText * and attaches a controller to it. * Location and speed are more or less random. */ public void spawnFirework() { float lifetime = 0.2f + rand.nextFloat() * 1f; float speed = 6 + rand.nextFloat() * 5; // Define a new start loction and create a new Sphere. Vector3f start = text.getLocalTranslation().add( textwidth + (rand.nextFloat() * 5 * (rand.nextBoolean() == true ? 1 : -1)), -5, 1); Sphere sphere = new Sphere("s", 5, 5, 0.06f); Node spNode = new Node("spNode"); spNode.attachChild(sphere); spNode.setModelBound(new BoundingBox()); spNode.updateModelBound(); spNode.getLocalTranslation().set(start); // color the Sphere yellow sphere.setRenderState(yellowMaterialState); // add a controller to move the Sphere upwards spNode.addController(new FireworkController(spNode, lifetime, speed)); spNode.attachChild(createTrail()); // attach the Sphere to the scene. rootNode.attachChild(spNode); rootNode.updateRenderState(); } /** * creates a particle trail. * @return particleMesh representing the trail of a firework */ public ParticleMesh createTrail() { ParticleMesh particleGeom = ParticleFactory.buildParticles("trail", 10); // add a gravity effect to the particle effect particleGeom.addInfluence(SimpleParticleInfluenceFactory .createBasicGravity(new Vector3f(0, -0.15f, 0), true)); particleGeom.setEmissionDirection(new Vector3f(0.0f, 1.0f, 0.0f)); // allow to shoot only downwards in a 20° angle particleGeom.setMaximumAngle(FastMath.DEG_TO_RAD * 170); particleGeom.setMinimumAngle(FastMath.DEG_TO_RAD * 190); particleGeom.getParticleController().setSpeed(0.2f); particleGeom.setMinimumLifeTime(10.0f); particleGeom.setMaximumLifeTime(50.0f); particleGeom.setStartSize(0.15f); particleGeom.setEndSize(0.01f); particleGeom.getParticleController().setControlFlow(false); // set the repeat type to looping particleGeom.getParticleController().setRepeatType(Controller.RT_CYCLE); particleGeom.warmUp(10); particleGeom.setInitialVelocity(0.005f); // the trail should be a simple white particleGeom.setStartColor(ColorRGBA.white.clone()); particleGeom.setEndColor(ColorRGBA.white.clone()); // apply alpha, texture and ZBuffer renderstates DisplaySystem display = DisplaySystem.getDisplaySystem(); AlphaState as = display.getRenderer().createAlphaState(); as = DisplaySystem.getDisplaySystem().getRenderer().createAlphaState(); as.setBlendEnabled(true); as.setSrcFunction(AlphaState.SB_SRC_ALPHA); as.setDstFunction(AlphaState.DB_ONE); as.setTestEnabled(true); as.setTestFunction(AlphaState.TF_GREATER); TextureState ts = display.getRenderer().createTextureState(); ts.setTexture(TextureManager.loadTexture( ExplosionFactory.class.getClassLoader() .getResource("spark.jpg"), Texture.FM_LINEAR, Texture.FM_LINEAR)); ZBufferState zs = display.getRenderer().createZBufferState(); zs.setWritable(false); zs.setEnabled(true); particleGeom.setRenderState(ts); particleGeom.setRenderState(as); particleGeom.setRenderState(zs); return particleGeom; } /** * The Entry point. * Creates and starts the game. */ public static void main(String[] args) { // we only want to see important messages Logger.getLogger("com.jme").setLevel(Level.SEVERE); Logger.getLogger("com.jmex").setLevel(Level.SEVERE); HappyNewYear happy = new HappyNewYear(); happy.setDialogBehaviour(SimpleGame.ALWAYS_SHOW_PROPS_DIALOG); happy.start(); } }
The Controller which moves the fireworks upwards, spawns explosions and removes the firework from the scene.
import java.util.Random; import com.jme.scene.Controller; import com.jme.scene.Spatial; /** * The FireworkController moves the sphere diagonally upwards. * When the lifetime of the object is finished, the controller * removes himself from the Sphere and removes the Sphere from the Scene. */ public class FireworkController extends Controller { /** lifetime in seconds */ private float lifetime; /** vertical speed */ private float vSpeed; /** the object to be moved */ private Spatial object; /** horizontal speed */ private float hSpeed = 2f; /** should the object move digonally to the left or right */ private boolean leftright; private Random rand; /** * The constructor, which takes the object to move, the lifetime and vertical speed. * @param object object to move * @param lifetime lifetime in seconds * @param vSpeed vertical speed */ public FireworkController(Spatial object, float lifetime, float vSpeed) { this.lifetime = lifetime; this.object = object; this.vSpeed = vSpeed; rand = new Random(); // should the firework be moved diagonally to the left or right leftright = rand.nextBoolean(); } /** * The update Method gets called every frame. * Moves the object upwards and a bit to the left or right. * When the lifetime of the object is finished, it gets removed from the scene. * An explosion is spawned when the object dies. */ @Override public void update(float time) { lifetime -= time; // add hSpeed to the X-Axis and speed to the Y-Axis object.getLocalTranslation().addLocal((leftright==true?1:-1)* hSpeed*time, vSpeed*time, 0); if (lifetime <= 0) { // the life has come to an end // create a nice explosion at the current location and // remove this controller from the object and the object from the scene ExplosionFactory.get().spawnExplosion(object.getLocalTranslation().clone()); object.removeController(this); object.removeFromParent(); } } }
The ExplosionFactory defines the look of the Particle effect and keeps a pool of a number of (20) those Effects which will be reused.
import java.util.ArrayList; import java.util.Random; import java.util.logging.Logger; import com.jme.image.Texture; import com.jme.math.Vector3f; import com.jme.renderer.ColorRGBA; import com.jme.scene.Controller; import com.jme.scene.Node; import com.jme.scene.state.AlphaState; import com.jme.scene.state.TextureState; import com.jme.scene.state.ZBufferState; import com.jme.system.DisplaySystem; import com.jme.util.TextureManager; import com.jmex.effects.particles.ParticleFactory; import com.jmex.effects.particles.ParticleMesh; import com.jmex.effects.particles.SimpleParticleInfluenceFactory; /** * The ExplosionFactory keeps a pool of a number of the ParticleEffect to reuse them. * The color and intensity of the explosion effects are more or less random. */ public class ExplosionFactory { private static ExplosionFactory instanze = null; /** pool of explosions */ private ArrayList<ParticleMesh> explosions = null; /** render states for the particle effects */ private AlphaState as = null; private TextureState ts = null; private ZBufferState zs = null; /** reference to the root node, to attach the particleeffect to the scene */ private Node rootNode = null; private Random rand; public static ExplosionFactory get() { if (instanze == null) { Logger.getLogger("effects").severe("ExplosionFactory not yet initialized"); Logger.getLogger("effects").severe("call ExplosionFactory.init(rootNode) first"); } return instanze; } public static void init(final Node root) { if (instanze == null) { instanze = new ExplosionFactory(root); } } /** * Sets up the Explosion effect and creates a pool of particles with random colors. * @param root */ private ExplosionFactory(final Node root) { explosions = new ArrayList<ParticleMesh>(); rootNode = root; rand = new Random(); DisplaySystem display = DisplaySystem.getDisplaySystem(); as = display.getRenderer().createAlphaState(); as = DisplaySystem.getDisplaySystem().getRenderer().createAlphaState(); as.setBlendEnabled(true); as.setSrcFunction(AlphaState.SB_SRC_ALPHA); as.setDstFunction(AlphaState.DB_ONE); as.setTestEnabled(true); as.setTestFunction(AlphaState.TF_GREATER); ts = display.getRenderer().createTextureState(); ts.setTexture(TextureManager.loadTexture( ExplosionFactory.class.getClassLoader() .getResource("spark.jpg"), Texture.FM_LINEAR, Texture.FM_LINEAR)); zs = display.getRenderer().createZBufferState(); zs.setWritable(false); zs.setEnabled(true); // create a pool of 20 different explosion effects for (int i = 0; i < 20; i++) { createExplosion(); } } /** * Try to get a inactive particle effect out of the pool. * If we can't find one (all are busy), we create a new one and expand the pool. */ public ParticleMesh getExplosion() { for (int x = 0, tSize = explosions.size(); x < tSize; x++) { ParticleMesh e = explosions.get(x); if (!e.isActive()) { return e; } } return createExplosion(); } /** * creates a particle effect with random colors * @return */ private ParticleMesh createExplosion() { ParticleMesh particleGeom = ParticleFactory.buildParticles("explosion", 80); // add a gravity effect to the particle effect particleGeom.addInfluence(SimpleParticleInfluenceFactory .createBasicGravity(new Vector3f(0, -0.15f, 0), true)); particleGeom.setEmissionDirection(new Vector3f(0.0f, 1.0f, 0.0f)); // allow to shoot particles in all directions (360°) particleGeom.setMaximumAngle(3.1415927f); particleGeom.setMinimumAngle(0); particleGeom.getParticleController().setSpeed(0.2f); particleGeom.setMinimumLifeTime(100.0f); particleGeom.setMaximumLifeTime(600.0f); particleGeom.setStartSize(0.15f +rand.nextFloat()/10*1.6f); // values from 0.15 to 0.3 particleGeom.setEndSize(0.03f); particleGeom.getParticleController().setControlFlow(false); particleGeom.getParticleController().setRepeatType(Controller.RT_CLAMP); particleGeom.warmUp(200); // make some explosion appear more powerful than others // more velocity means bigger/wider explosions particleGeom.setInitialVelocity(0.01f +(rand.nextFloat() / 50)); // random color particleGeom.setStartColor(new ColorRGBA(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), 1f)); particleGeom.setEndColor(new ColorRGBA(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), 0.0f)); // apply renderstartes particleGeom.setRenderState(ts); particleGeom.setRenderState(as); particleGeom.setRenderState(zs); // attach the particle effect to the root node rootNode.attachChild(particleGeom); rootNode.updateRenderState(); // add the effect to the pool to reuse it later explosions.add(particleGeom); return particleGeom; } /** * Creates a new explosion at a given location. * @param location location where the explosion should appear */ public void spawnExplosion(Vector3f location) { ParticleMesh mesh = getExplosion(); mesh.getLocalTranslation().set(location); mesh.updateGeometricState(0, true); mesh.forceRespawn(); } }
Sources above may throw IndexOutOfBoundsException when particles are detached.
So deferring particle removal until update is ended is added.
Sources below are modified for jME2 and refactored for ease of use.
You just attach / detach class 'Firework' to show / hide firework.
Avoiding IndexOutOfBoundsException
public void update(final float time) {
this.lifetime -= time;
// add hSpeed to the X-Axis and speed to the Y-Axis
this.object.getLocalTranslation().addLocal((this.leftright == true ? 1 : -1) * this.hSpeed * time,
this.vSpeed * time, 0);
if (this.lifetime <= 0) {
setActive(false);
this.explosionFactory.addDone(this.object);
}
}
public void updateWorldData(final float time) {
super.updateWorldData(time);
this.timePassed += time;
if (this.timePassed > this.spawnRate) {
spawnFirework();
this.timePassed = 0;
}
this.factory.respawn();
}
...
void respawn() {
for (Spatial s : this.done) {
s.clearControllers();
s.removeFromParent();
spawnExplosion(s.getLocalTranslation().clone());
}
this.done.clear();
}
import java.awt.Font;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme.app.SimpleGame;
import com.jme.bounding.OrientedBoundingBox;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jmex.font3d.Font3D;
import com.jmex.font3d.Text3D;
import com.jmex.font3d.effects.Font3DGradient;
public class HappyNewYear extends SimpleGame {
/** 3DText font and text */
private Font3D myfont;
private Text3D text;
private float textwidth;
/**
* Set up all the necessary stuff.
* Init the Explosionfactory, create the 3DText.
*/
@Override
protected void simpleInitGame() {
display.setTitle("Happy New Year");
// create a 3DFont from a system font (Arial) */
myfont = new Font3D(new Font("Arial", Font.PLAIN, 2), 0.1, true, true,true);
// color the Text
Font3DGradient gradient = new Font3DGradient(Vector3f.UNIT_Y, ColorRGBA.white, ColorRGBA.red);
gradient.applyEffect(myfont);
text = myfont.createText("HAPPY NEW YEAR 2009!", 2, 0);
// scale Z-Axis of the font down to make it slimmer
text.setLocalScale(new Vector3f(1, 1, 0.1f));
text.updateWorldBound();
// get the width of our 3DText, we need it later to spawn the Firework
OrientedBoundingBox bb = (OrientedBoundingBox) text.getWorldBound();
textwidth = bb.extent.x;
rootNode.attachChild(text);
Firework firework = new Firework();
rootNode.attachChild(firework);
// move the cam a bit to the left and backwards
cam.setLocation(new Vector3f(5, 0, 15));
}
/**
* The Entry point.
* Creates and starts the game.
*/
public static void main(String[] args) {
// we only want to see important messages
Logger.getLogger("com.jme").setLevel(Level.SEVERE);
Logger.getLogger("com.jmex").setLevel(Level.SEVERE);
HappyNewYear happy = new HappyNewYear();
happy.setConfigShowMode(ConfigShowMode.AlwaysShow);
happy.start();
}
}
import java.util.Random;
import com.jme.image.Texture;
import com.jme.image.Texture.MagnificationFilter;
import com.jme.image.Texture.MinificationFilter;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jmex.effects.particles.ParticleFactory;
import com.jmex.effects.particles.ParticleMesh;
import com.jmex.effects.particles.SimpleParticleInfluenceFactory;
@SuppressWarnings("serial")
public class Firework extends Node {
private static final String PATH_SPARK = "spark.jpg";
/** spawnrate of new fireworks */
private float spawnRate = 0.5f;
private float timePassed;
/** material state for the sphere */
private final MaterialState sphereMatState;
private float minLifetime;
private float maxLifetime;
private float minSpeed;
private float maxSpeed;
private final ExplosionFactory factory;
/**
* Set up all the necessary Stuff. Init the Explosionfactory, create the 3DText.
*/
public Firework() {
// initialize the ExplosionFactory and some other stuff
this.factory = new ExplosionFactory(this, PATH_SPARK);
this.sphereMatState = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
this.sphereMatState.setAmbient(ColorRGBA.white);
this.minLifetime = 0.2f;
this.maxLifetime = 1.2f;
this.minSpeed = 6;
this.maxSpeed = 11;
}
public void setExplosionSize(int size) {
this.factory.setExplosionSize(size);
}
public void setSpawnRate(float rate) {
this.spawnRate = rate;
}
public void setLifetime(float min, float max) {
this.minLifetime = min;
this.maxLifetime = max;
}
public void setSpeed(float min, float max) {
this.minSpeed = min;
this.maxSpeed = max;
}
@Override
public void updateWorldData(final float time) {
super.updateWorldData(time);
this.timePassed += time;
if (this.timePassed > this.spawnRate) {
spawnFirework();
this.timePassed = 0;
}
this.factory.respawn();
}
/**
* Spawn a new firework.<br>
* Creates a new Sphere at a random position below the 3DText and attaches a controller to it.
* Location and speed are more or less random.
*/
private void spawnFirework() {
final Random rand = FastMath.rand;
final float lifetime = this.minLifetime + rand.nextFloat() * (this.maxLifetime - this.minLifetime);
final float speed = this.minSpeed + rand.nextFloat() * (this.maxSpeed - this.minSpeed);
// Define a new start loction and create a new Sphere.
final Vector3f start = new Vector3f(rand.nextFloat() * 10f - 5f, 0, 0);
final Sphere sphere = new Sphere("s", 5, 5, 0.06f);
final Node spNode = new Node("firesphere");
spNode.attachChild(sphere);
spNode.getLocalTranslation().set(start);
// color the Sphere yellow
sphere.setRenderState(this.sphereMatState);
// add a controller to move the Sphere upwards
spNode.addController(new FireworkController(spNode, lifetime, speed, this.factory));
spNode.attachChild(createTrail());
// attach the Sphere to the scene.
this.attachChild(spNode);
this.updateRenderState();
}
/**
* creates a particle trail.
*
* @return particleMesh representing the trail of a firework
*/
private ParticleMesh createTrail() {
final Renderer r = DisplaySystem.getDisplaySystem().getRenderer();
final ParticleMesh particleGeom = ParticleFactory.buildParticles("trail", 10);
// add a gravity effect to the particle effect
particleGeom.addInfluence(SimpleParticleInfluenceFactory.createBasicGravity(new Vector3f(0, -0.15f, 0), true));
particleGeom.setEmissionDirection(new Vector3f(0.0f, 1.0f, 0.0f));
// allow to shoot only downwards in a 20° angle
particleGeom.setMaximumAngle(FastMath.DEG_TO_RAD * 170);
particleGeom.setMinimumAngle(FastMath.DEG_TO_RAD * 190);
particleGeom.getParticleController().setSpeed(0.2f);
particleGeom.setMinimumLifeTime(10.0f);
particleGeom.setMaximumLifeTime(50.0f);
particleGeom.setStartSize(0.3f);
particleGeom.setEndSize(0.1f);
particleGeom.getParticleController().setControlFlow(false);
// set the repeat type to looping
particleGeom.getParticleController().setRepeatType(Controller.RT_CYCLE);
particleGeom.warmUp(10);
particleGeom.setInitialVelocity(0.005f);
// the trail should be a simple white
particleGeom.setStartColor(ColorRGBA.white.clone());
particleGeom.setEndColor(ColorRGBA.white.clone());
// apply alpha, texture and ZBuffer renderstates
final BlendState bs = r.createBlendState();
particleGeom.setRenderState(bs);
bs.setBlendEnabled(true);
bs.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
bs.setDestinationFunction(BlendState.DestinationFunction.One);
bs.setTestEnabled(true);
bs.setTestFunction(BlendState.TestFunction.GreaterThan);
TextureState texState = r.createTextureState();
Texture tex = TextureManager.loadTexture(PATH_SPARK, MinificationFilter.NearestNeighborNoMipMaps, MagnificationFilter.NearestNeighbor);
texState.setTexture(tex);
particleGeom.setRenderState(texState);
final ZBufferState zs = r.createZBufferState();
particleGeom.setRenderState(zs);
zs.setWritable(false);
zs.setEnabled(true);
particleGeom.updateRenderState();
return particleGeom;
}
}
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Logger;
import com.jme.image.Texture;
import com.jme.image.Texture.MagnificationFilter;
import com.jme.image.Texture.MinificationFilter;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jmex.effects.particles.ParticleFactory;
import com.jmex.effects.particles.ParticleMesh;
import com.jmex.effects.particles.SimpleParticleInfluenceFactory;
/**
* The ExplosionFactory keeps a pool of a number of the ParticleEffect to reuse them.<br>
* The color and intensity of the explosion effects are more or less random.<br>
*/
class ExplosionFactory {
public static final Logger log = Logger.getLogger(ExplosionFactory.class.getName());
private static final int POOL_SIZE = 20;
/** pool of explosions */
private ArrayList<ParticleMesh> explosions;
/** render states for the particle effects */
private BlendState blendState;
private TextureState texState;
private ZBufferState zbufferState;
/** reference to the root node, to attach the particleeffect to the scene */
private Node rootNode;
private final ArrayList<Spatial> done;
private final Random rand;
private int explosionSize = 100;
/**
* Sets up the Explosion effect and creates a pool of particles with random colors.
*
* @param root
*/
ExplosionFactory(final Node root, String imagePath) {
final Renderer r = DisplaySystem.getDisplaySystem().getRenderer();
this.explosions = new ArrayList<ParticleMesh>();
this.rootNode = root;
this.rand = new Random();
this.done = new ArrayList<Spatial>();
this.blendState = r.createBlendState();
this.blendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
this.blendState.setDestinationFunction(BlendState.DestinationFunction.One);
this.blendState.setBlendEnabled(true);
this.texState = r.createTextureState();
Texture tex = TextureManager.loadTexture(imagePath, MinificationFilter.NearestNeighborNoMipMaps, MagnificationFilter.NearestNeighbor);
this.texState.setTexture(tex);
this.zbufferState = r.createZBufferState();
this.zbufferState.setWritable(false);
// create a pool of 20 different explosion effects
for (int i = 0; i < POOL_SIZE; i++) {
createExplosion();
}
}
void setExplosionSize(int size) {
if (this.explosionSize == size)
return;
for (ParticleMesh mesh : this.explosions) {
mesh.removeFromParent();
}
this.explosions.clear();
this.explosionSize = size;
}
/**
* Try to get a inactive particle effect out of the pool.<br>
* If we can't find one (all are busy), we create a new one and expand the pool.<br>
*/
ParticleMesh getExplosion() {
for (int x = 0, tSize = this.explosions.size(); x < tSize; x++) {
final ParticleMesh e = this.explosions.get(x);
if (!e.isActive()) {
return e;
}
}
return createExplosion();
}
/**
* creates a particle effect with random colors
*
* @return
*/
private ParticleMesh createExplosion() {
final ParticleMesh particleGeom = ParticleFactory.buildParticles("explosion", this.explosionSize);
// add a gravity effect to the particle effect
particleGeom.addInfluence(SimpleParticleInfluenceFactory.createBasicGravity(new Vector3f(0, -0.15f, 0), true));
particleGeom.setEmissionDirection(new Vector3f(0.0f, 1.0f, 0.0f));
// allow to shoot particles in all directions (360°)
particleGeom.setMaximumAngle(3.1415927f);
particleGeom.setMinimumAngle(0);
particleGeom.getParticleController().setSpeed(0.2f);
particleGeom.setMinimumLifeTime(100.0f);
particleGeom.setMaximumLifeTime(600.0f);
particleGeom.setStartSize(1f + this.rand.nextFloat()*0.16f);
particleGeom.setEndSize(0.03f);
particleGeom.getParticleController().setControlFlow(false);
particleGeom.getParticleController().setRepeatType(Controller.RT_CLAMP);
particleGeom.warmUp(200);
// make some explosion appear more powerful than others
// more velocity means bigger/wider explosions
particleGeom.setInitialVelocity(0.01f + (this.rand.nextFloat() / 50));
// random color
particleGeom.setStartColor(new ColorRGBA(this.rand.nextFloat(), this.rand.nextFloat(), this.rand.nextFloat(),
1f));
particleGeom.setEndColor(new ColorRGBA(this.rand.nextFloat(), this.rand.nextFloat(), this.rand.nextFloat(),
0.0f));
// apply renderstartes
particleGeom.setRenderState(this.texState);
particleGeom.setRenderState(this.blendState);
particleGeom.setRenderState(this.zbufferState);
// attach the particle effect to the root node
this.rootNode.attachChild(particleGeom);
this.rootNode.updateRenderState();
// add the effect to the pool to reuse it later
this.explosions.add(particleGeom);
return particleGeom;
}
/**
* Creates a new explosion at a given location.
*
* @param location
* location where the explosion should appear
*/
void spawnExplosion(final Vector3f location) {
final ParticleMesh mesh = getExplosion();
mesh.getLocalTranslation().set(location);
mesh.updateWorldData(0);
mesh.forceRespawn();
}
void respawn() {
for (Spatial s : this.done) {
s.clearControllers();
s.removeFromParent();
spawnExplosion(s.getLocalTranslation().clone());
}
this.done.clear();
}
void addDone(final Spatial work) {
this.done.add(work);
}
}
import java.util.Random;
import com.jme.scene.Controller;
import com.jme.scene.Spatial;
/**
* The FireworkController moves the sphere diagonally upwards.<br>
* When the lifetime of the object is finished,<br>
* the controller removes himself from the Sphere and removes the Sphere from the Scene.<br>
*/
@SuppressWarnings("serial")
class FireworkController extends Controller {
/** lifetime in seconds */
private float lifetime;
/** vertical speed */
private final float vSpeed;
/** the object to be moved */
private final Spatial object;
/** horizontal speed */
private final float hSpeed = 2f;
/** should the object move digonally to the left or right */
private final boolean leftright;
private final Random rand;
private final ExplosionFactory explosionFactory;
/**
* The constructor, which takes the object to move, the lifetime and vertical speed.
*
* @param object_
* object to move
* @param lifetime_
* lifetime in seconds
* @param vSpeed_
* vertical speed
*/
FireworkController(final Spatial object_, final float lifetime_, final float vSpeed_, final ExplosionFactory factory) {
this.explosionFactory = factory;
this.lifetime = lifetime_;
this.object = object_;
this.vSpeed = vSpeed_;
this.rand = new Random();
// should the firework be moved diagonally to the left or right
this.leftright = this.rand.nextBoolean();
}
/**
* The update Method gets called every frame.<br>
* Moves the object upwards and a bit to the left or right.<br>
* When the lifetime of the object is finished, it gets removed from the scene.<br>
* An explosion is spawned when the object dies.
*/
@Override
public void update(final float time) {
this.lifetime -= time;
// add hSpeed to the X-Axis and speed to the Y-Axis
this.object.getLocalTranslation().addLocal((this.leftright == true ? 1 : -1) * this.hSpeed * time,
this.vSpeed * time, 0);
if (this.lifetime <= 0) {
setActive(false);
this.explosionFactory.addDone(this.object);
}
}
}