Hey, look at all these acorns!

Now we have a handle on what an object pool is, why we need one, and how we can use LibGDX to look after one for us. Next, we need to look at how we can use one in Nutty Birds.

I am going to take a liberty with the gameplay and make a fundamental change to the way the game is played. We will design the game in such a way that we can only have three acorns on the screen at on time. Whenever you fire another while three are already in play, the oldest will be removed from the game. This may sound rather crude, but our aim here is to try out an object pool. Once we are done, you can try and shape the game the way you wish to.

Pooling the acorns

Our first task is to move our acorn from a Sprite instance to a class in its own right. This class will implement the Pool.Poolable interface.

The following is the code listing for our new Acorn class:

public class Acorn implements Pool.Poolable {
  private final Sprite sprite;
  public Acorn(AssetManager assetManager) {
    sprite = new Sprite(assetManager.get("acorn.png", Texture.class));
    sprite.setOrigin(sprite.getWidth() / 2, sprite.getHeight() / 2);
  }

  public void setPosition(float x, float y) {
    sprite.setPosition(x, y);
  }

  public void setRotation(float degrees) {
    sprite.setRotation(degrees);
  }

  public float getWidth() {
    return sprite.getWidth();
  }

  public float getHeight() {
    return sprite.getHeight();
  }

  public void draw(Batch batch) {
    sprite.draw(batch);
  }

  @Override
  public void reset() {
    sprite.setPosition(0,0);
    sprite.setRotation(0F);
  }
}

As you can see, our new class contains the Sprite class that we originally created in the GameScreen class; it implements the Pool.Poolable interface and we delegate four methods from the Sprite class. We implement the reset() method as required, and in return we reset all the rotation and the position back to zero. In our constructor, we pass in our AssetManager instance so we can get access to our texture.

This class is now ready to go and be part of an object pool!

Our next step is to create our own acorn pool. To do this, we need to create a class that extends the Pool class. Let's create a class called AcornPool and extend that Pool class.

Here is the code you we will end up with:

public class AcornPool extends Pool<Acorn> {
  public static final int ACORN_COUNT = 3;
  private final AssetManager assetManager;
  public AcornPool(AssetManager assetManager) {
    super(ACORN_COUNT,ACORN_COUNT);
    this.assetManager = assetManager;
  }

  @Override
  protected Acorn newObject() {
    return new Acorn(assetManager);
  }
}

As you can see, we have typed our Pool class to be that of the Acorn class. We pass in our AssetManager instance, as we will need this later on. We have also defined our parameters for the pool with the ACORN_COUNT constant. Finally, we implement our newObject() method. Here, we just return a freshly created Acorn instance.

The next task is a rather big one: we need to update the GameScreen class to use this new pool.

First, we should define our new pool class as a variable but also have a separate map for managing our bodies to the Acorn instances. Add the following code to the GameScreen class:

private AcornPool acornPool;
private OrderedMap<Body, Acorn> acorns = new OrderedMap<>();

Now, in the show() method, we will instantiate the pool as follows:

acornPool = new AcornPool(nuttyGame.getAssetManager());

Perfect! Next, we need to update the createAcorn() method, which was previously called createBullet(), but I have renamed it since we now know what the projectile turned out to be. We need to change the way we create the acorn, from instantiation to using the pool. Let's take a look at the following code snippet:

private void createAcorn() {
  CircleShape circleShape = new CircleShape();
  circleShape.setRadius(0.5f);
  BodyDef bd = new BodyDef();
  bd.type = BodyDef.BodyType.DynamicBody;
  Body acorn = world.createBody(bd);
  acorn.setUserData("acorn");
  acorn.createFixture(circleShape, 1);
  acorn.setTransform(new Vector2(convertUnitsToMetres(firingPosition.x), convertUnitsToMetres(firingPosition.y)), 0);
  acorns.put(acorn, acornPool.obtain());
  circleShape.dispose();
  float velX = Math.abs((MAX_STRENGTH * -MathUtils.cos(angle) * (distance / 100f)));
  float velY = Math.abs((MAX_STRENGTH * -MathUtils.sin(angle) * (distance / 100f)));
  acorn.setLinearVelocity(velX, velY);
}

This is how the method should look. As you can see, we have replaced the creation code with a simple call to the pool to obtain our instance. At this point, we don't know if it is fresh or reused. However, if we do this correctly, we won't care or notice!

Finally, we need to update our code so we are referencing the new acorn map, instead of the previous sprite map. Let's take a look at the following code snippet:

private void updateAcornPositions() {
  for (Body body : acorns.keySet()) {
    Acorn acorn = acorns.get(body);
    acorn.setPosition(convertMetresToUnits(body.getPosition().x) - acorn.getWidth() / 2f,
    convertMetresToUnits(body.getPosition().y) - acorn.getHeight() / 2f);
    acorn.setRotation(MathUtils.radiansToDegrees * body.getAngle());
  }
}

Here I created a new method to take care of the updates! We now need to add a call to this method in our update() method.

Next, we need to update our draw() method to draw from our new acorn map, as follows:

private void draw() {
  // Code omitted for brevity
  for (Acorn acorn : acorns.values()) {
    acorn.draw(batch);
  }
  // Code omitted for brevity
}

Brilliant! If you run the project now, it'll look and behave like it did earlier. However, we are not limiting our acorns yet and, if you look at our pool in a debug view, you will see that we are doing nothing but just creating new instances.

Freeing the acorns!

We need to apply our limit to the acorns and ensure that they are returned to the pool. This is actually a straightforward step.

We need to check the map size; if it is at our maximum, which in this case is 3, we take the first entry offered by the map. Next, we can queue up to have the Box2D body removed from the world, remove the acorn from the map, and then return the acorn to the pool. We should create a method called checkLimitAndRemoveAcornIfNecessary() and place it in our GameScreen class. The following is the code required to do this:

private void checkLimitAndRemoveAcornIfNecessary() {
  if (acorns.size == AcornPool.ACORN_COUNT) {
    Body body = acorns.keys().iterator().next();
    toRemove.add(body);
    Acorn acorn = acorns.remove(body);
    acornPool.free(acorn);
  }
}

As you can see, we check the map. We grab the first offered instance, and queue it up to be removed. Next, we remove the map entry and return it to the pool. To make it perform a bit more than expected, we can change our map from an ObjectMap instance to an OrderedMap instance as this will keep track of the insertions made in the map. Let's take a look at the following code:

private OrderedMap<Body, Acorn> acorns = new OrderedMap<>();

Our final step is to add the call to the preceding method in our createAcorn() method just before we obtain an acorn from the pool:

checkLimitAndRemoveAcornIfNecessary();
acorns.put(acorn, acornPool.obtain());

If we run up the project now, you will find that we are now limited to three acorns. Believe it or not, the sprites being used are all the same instance. If you don't believe me as a trace out on the newObject() method of the AcornPool class and see how many get created.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.176.166