Book Image

JMonkeyEngine 3.0 Cookbook

By : Rickard Eden
Book Image

JMonkeyEngine 3.0 Cookbook

By: Rickard Eden

Overview of this book

Table of Contents (17 chapters)
jMonkeyEngine 3.0 Cookbook
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

An advanced ParticleEmitter class


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.

Getting ready

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.

How to do it…

In the first section, we'll create a new ParticleInfluencer instance. This consists of the following steps:

  1. The first thing we'll do is create a new class called 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();
    }
  2. We can now replace the ParticleInfluencer interface in the ParticleEmitter element's Property window with our own.

  3. That was the easy part, and that's how far we get without modifying the engine. In the next section, we will extend the current ParticleEmitter instance to animate particles continuously. This will consist of the following steps:

    1. Let's start by making our 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);
    2. In our 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;
    3. Now, let's go to our 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;
        }
      }
    4. In 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;
        }
      }
    5. That's the influencer part. Let's make sure 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:

  1. We create a new class called 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);
      }
    }
  2. Apart from that, we also need to add the following code for it to work properly:

    public Control cloneForSpatial(Spatial spatial) {
      return new BirdParticleEmitterControl();
    }
  3. To affect the birds by our changes, we need to do a few more things. First, we need to open the birds scene in the SceneComposer window.

  4. Selecting the Emitter element, we need to choose Add Control.. and then select Custom Control. Our newly created control should be available in the list.

  5. Now, we need to load the scene inside an application. We just load the scene and move it up into the sky by using the following code:

    public void simpleInitApp() {
      Node scene = (Node) assetManager.loadModel("Scenes/ParticleTest.j3o");
      scene.setLocalTranslation(0, 60, 0);
      rootNode.attachChild(scene);
    }

How it works...

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.

There's more…

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?