Soaring birds are nice but it's easy to feel that the result of the previous recipe could have been much better if the birds were better animated. If you've worked with the ParticleEmitter
class before or have been observant of the birds, you will know that particles can actually be animated although they only cycle through every frame once per lifetime. This is much too slow for the birds.
In this recipe, we're going to look at what's needed to make the birds flap their wings. It's not as simple as it sounds and requires modifying the ParticleEmitter
code and writing our own ParticleInfluencer
class.
If we have a look at the ParticleEmitter
class to see what we need to do, we can see that there is an updateParticle
method that seems like a good place to start. This is called for every particle in each update cycle. One thing that is less obvious at first is that since we have the same ParticleInfluencer
instance affecting all particles, it also needs to be updated separately for each frame. To achieve the latter, we can use a control.
To be able to modify the ParticleEmitter
class, we need the source. This means we have to check it out from the repository. If you're not comfortable with this, you can still do the first part and learn more about the ParticleInfluencer
instance.
After having checked out the source code for jMonkeyEngine from the repository, it should be opened as a project in the SDK.
Build it and then change the reference in the properties for this project to use the .jar
files from the source code project instead of the supplied jMonkeyEngine.jar
files.
In the first section, we'll create a new ParticleInfluencer
instance. This consists of the following steps:
BirdParticleInfluencer
and have it extend the DefaultParticleInfluencer
class. Since the flat particles point in the direction they're flying, it sometimes looks weird when they have a Y-velocity. We're going to fix that by not allowing the particles to have any velocity in the y axis. We override the influenceParticle
method and set the Y-velocity to 0
. After this we need to normalize the velocity, as shown in the following code:public void influenceParticle(Particle particle, EmitterShape emitterShape) { super.influenceParticle(particle, emitterShape); particle.velocity.setY(0); particle.velocity.normalizeLocal(); }
ParticleInfluencer
interface in the ParticleEmitter
element's Property window with our own.ParticleEmitter
instance to animate particles continuously. This will consist of the following steps:ParticleInfluencer
interface ready to update the particles in every frame. Let's start by making our ParticleInfluencer
interface ready to update the particles in every frame. We're going to add two methods to it. The first one is for updating the particle, and the second one is for updating the influencer itself, as shown in the following code:public void influenceRealtime(Particle particle, float tpf); public void update(float tpf);
BirdParticleInfluencer
class, we're going to need some new fields. The maxImages
property keeps track of how many images there are in a cycle. The animationFps
property defines how fast the animation should run. These two properties should be added to the class's read/write/clone methods as well to ensure that they're saved properly. The time
and increaseFrames
are runtime properties only:private int maxImages = 1; private float animationFps = 10f; private float time = 0f; private int increaseFrames;
update
method. This is the method that runs once every frame. We add functionality to check whether it's time to change the frame in the particle or not. The logic goes like this: when the current passed time is larger than the time between frames, increase the frame index by one. Using a while
loop rather than an if
statement allows us to compensate for low frame rate, by skipping several frames, if necessary, to keep up with the frames per second:public void update(float tpf){ super.update(tpf); float timeBetweenFrames = 1f / animationFps; time += tpf; increaseFrames = 0; while (time > timeBetweenFrames){ increaseFrames++; time -= interval; } }
influenceRealtime
, which is the method that is run once per particle and frame, all we do is tell it to increase the imageIndex
value if needed, making sure not to exceed the maximum images in the cycle:public void influenceRealtime(Particle particle, float tpf) { super.influenceRealtime(particle, tpf); if(increaseFrames > 0){ particle.imageIndex = (particle.imageIndex + increaseFrames) % maxImages; } }
influenceRealtime
is called from the ParticleEmitter
class. At the end of the updateParticle
method, add the following code:particleInfluencer.influenceRealtime(p, tpf);
Unfortunately, we also need to comment out the following line:
//p.imageIndex = (int) (b * imagesX * imagesY);
In the last section of the recipe, we will create a control that will update the ParticleInfluencer
class. This consists of the following steps:
BirdParticleEmitterControl
and make it extend AbstractControl
. The important bit here is the controlUpdate
method where we in turn call the update
method of the ParticleEmitter
instance:public void controlUpdate(float tpf){ super.update(tpf); if(spatial != null && spatial instanceof ParticleEmitter){ ((ParticleEmitter)spatial).getParticleInfluencer().update(tpf); } }
public Control cloneForSpatial(Spatial spatial) { return new BirdParticleEmitterControl(); }
public void simpleInitApp() { Node scene = (Node) assetManager.loadModel("Scenes/ParticleTest.j3o"); scene.setLocalTranslation(0, 60, 0); rootNode.attachChild(scene); }
Particle emitters are normally limited in what control you have over the particles. The ParticleInfluencer
class gives us some basic control during particle creation.
Since the birds are flat planes, they look best when viewed straight on. This creates a problem when we have said that they should always point in the direction they're flying if they're moving along the y axis.
The influenceParticle
method is a method implemented from the ParticleInfluencer
interface and it is called upon the creation of every new particle. Since the DefaultParticleInfluencer
instance is already applying a velocity with variation, we just needed to remove any Y-velocity.
In the ParticleEmitter
class, we commented out a line in the update
method. That's the current animation logic that will override our changes every time. A workaround would be to let the ParticleInfluencer
class keep track of the current frame, but that would make all the birds have the same frame. Another alternative would be to move it to one of the other ParticleInfluencer
classes.
By using the control pattern to update the ParticleInfluencer
class, we can offset some code and keep minimum changes in the ParticleEmitter
class.
Unfortunately, the changes we made to the ParticleEmitter
class won't be picked up by Scene Composer, as it uses its own compiled classes. So to see it, we had to start an application and load the scene there.
The birds now continuously flap their wings like many small birds do when flying. Larger birds tend to glide more, with only an occasional flap. They also fly in straight lines.
The influenceRealtime
method we created opens up new possibilities to create better looking particles.
An additional touch would be to implement logic to have the birds both soar and flap interchangeably, and circle around a point or change their direction. Are you up for it?
3.139.83.151