Here’s the second half of the story – how to recycle dead bullets. I added a bullet cleanup system that checks for collisions with platforms, and for bullets that have gone past the visible edge of the world.
@Override
protected void process
(Entity e
) {
BulletComponent bulletComponent
= Components.
getBulletComponent(e
);
if (!bulletComponent.
active()) {
return;
}
// If the bullet is touching a platform, deactivate
PhysicsComponent physicsComponent
= Components.
getPhysicsComponent(e
);
if (PhysicsContactChecker.
isTouching(physicsComponent,
Label.
Platform) != null) {
Bullet bullet
= bulletComponent.
getBullet();
bullet.
deactivate();
return;
}
// If the bullet is past the right edge of the screen, deactivate
SpatialComponent spatialComponent
= Components.
getSpatialComponent(e
);
Vector2 position
= spatialComponent.
getPosition();
float width
= spatialComponent.
getSpatial().
getWidth();
if (position.
x - width
/ 2 > camera.
position.
x + camera.
viewportWidth * camera.
zoom / 2) {
Bullet bullet
= bulletComponent.
getBullet();
bullet.
deactivate();
return;
}
}
To make this work, I had to add some more data to the Bullet class. I spent quite some time this morning working on this and wondering why it wasn’t working. Once a bullet was deactivated, it appeared that it would never get activated again. I finally realized that it was activating, but then immediately deactivating. And the reason is the contact / collision data. I’m maintaining contact data in the physics component, outside of box2d. When I deactivate the box2d body, the contacts are deleted in the box2d world, but I wasn’t clearing out my own contact data. As a result, when I put the bullet back into the game, even though I’d moved it to a new position, it still had the old contact data from when it collided with the platform. So the collision code fired again, and the bullet got recycled immediately.
Once I figured that out, the fix was simple – when deactivating a bullet, clear the contact data in the associated physics component. This requires saving the physics component in the bullet. I might come back and re-think this at some point, it feels like there might be too much data shared between various classes. Anyway, here’s the new Bullet class:
public abstract class Bullet {
protected BulletPool pool;
protected boolean active;
protected PhysicsComponent physicsComponent;
public void activate(Vector2 position) {
active = true;
Body body = physicsComponent.getBody();
body.setTransform(position, 0);
body.setActive(true);
}
public void deactivate() {
physicsComponent.getContact().clearAllContacts();
Body body = physicsComponent.getBody();
body.setTransform(-10, -10, 0);
body.setActive(false);
pool.free(this);
active = false;
}
public boolean active() {
return active;
}
}
When deactivating, clear contacts, mark the body inactive, and move it out of the way. Activating the body does the inverse – mark it active, and place it where it needs to be. With this approach, I don’t really need to mark the renderable invisible – an inactive bullet will never be picked up by the camera.
This seems to work fairly well. I added a log to check the high water mark of my bullet pool. Even when I’m being obnoxious with the ‘fire’ key, it’s hard to cross 20-25 bullets. That should be a manageable number, both for Artemis and for box2d.