The biggest change since the last video: enemies that you can shoot at
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.
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:
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.
I’m finally at the point where I had to tackle this problem, which can be briefly summarized as follows. Any game with a shooting mechanic needs to handle bullets, which are a large number of short-lived objects. The naive solution is to create a new bullet every time the player fires a weapon, and then forget about it when the bullet’s no longer ‘alive’. However, the Android garbage collector will punish you if you do that, and your game will experience large stalls every few seconds as it reclaims all that memory. You need to set up a pool of objects, and reuse them. Any Android developer who’s done a game with some kind of ‘bullet’ in it knows this. I dealt with this in Bus Jumper, where the falling objects are essentially bullets. But Neil Rajah presented some new challenges, which required a new solution.
