Object Pooling

I began today with the intention of implementing the EnemyObject and getting that to work with in the GameScreen alongside the PlayerObject. However after a rather short time I decided that those two classes needed to interact so much that it was going to be a pain to make that work and then refactor once I’d moved all the Components into GameScreen when I was ready to implement object pooling.

Clearly that means I was ready for a bit of a refactor to make object pooling work – so off I went. Firstly I made arrays for each of my Component types in my GameScreen:

PhysicsComponent *m_pPhysicsComponents;
SpriteComponent *m_pSpriteComponents;
HealthComponent *m_pHealthComponents;

Then initialised these in the constructor. At this point I debated whether or not to set up each of the components with the correct data using their parametrised constructors (I actually had no idea that’s what non-default constructors were called) or if I should just use default constructors and then afterwards call some init function. I decided that having an init function meant that all the variables were getting set twice, so I decided to go for the parametrised constructor option. Mind you I’m now regretting that decision:

m_pPhysicsComponents = new PhysicsComponent[14]{ 
 { glm::vec3(400, 300, 0), 1.0f, 0.0f, 0.97f }, 
 {},
 {},
 {},
 //yeeeeep, this is shit
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false }
 };
 m_pSpriteComponents = new SpriteComponent[14]{ 
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(51.0f, 86.0f), "ship.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(51.0f, 86.0f), "cylon.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(51.0f, 86.0f), "cylon.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(51.0f, 86.0f), "cylon.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false }
 };
 m_pHealthComponents = new HealthComponent[4]{
 {100},
 {30},
 {30},
 {30}
 };

So now that the components are stored in GameScreen I need to pass pointers along to the GameObjects so they can still access the components they need. This was pretty easy for the players and enemies since I also had access to them in GameScreen:

m_player = new PlayerObject(m_pBulletManager, m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders);
m_player->AddComponent(m_pPhysicsComponents);
m_player->AddComponent(m_pSpriteComponents);
m_player->AddComponent(m_pHealthComponents);
m_gameObjects.push_back(m_player);

for (int i = 0; i < 3; i++)
{
 m_gameObjects.push_back(new EnemyObject(m_pBulletManager, m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders));
 m_gameObjects[i + 1]->AddComponent(m_pPhysicsComponents + i + 1);
 m_gameObjects[i + 1]->AddComponent(m_pSpriteComponents + i + 1);
 m_gameObjects[i + 1]->AddComponent(m_pHealthComponents + i + 1);
}

Getting the components into the BulletObjects was a bit more complex. I decided I would need to pass a pointer to the position of the first bullet related component in each component array to the BulletManager. Good idea, however I’d need to limit the number of BulletsObjects the BulletManager could create since I didn’t want to run out of Components. This meant a small refactor to pool the BulletObjects in the BulletManager too.

I also needed to change how I was shooting the bullets. Previously I’d been creating the bullet (and thus the components) in the Shoot function which meant I could just grab the position and rotation of the player and initialise the bullet with that info. However now I’d need to update the PhysicsComponent with this data *after* it had already been created. At this point I decided to make the position and rotation variables in PhysicsComponent public since I was clearly going to need to set them outside of the constructor. I added a shoot function to my bullet to accomplish all this:

void BulletObject::Shoot(int speed, float a_fRotationAngle, glm::vec3 a_startPos)
{
 if (m_physicsComponent != nullptr) {
  m_physicsComponent->m_fRotationAngle = a_fRotationAngle;
  m_physicsComponent->m_position = a_startPos;
  m_physicsComponent->AddForce(glm::vec2(speed, speed));
  GameObject::GetComponent(SPRITE)->m_bActive = true;
 }
}

As you can see in the last line – I’ve added a boolean to my Component class to determine if the component is currently active (since components hang around all the time now it makes sense to be able to turn them off when we don’t want them). So I start all the components in my bullets off as inactive – then turn them on when they are shot. There’s also a check in the BulletManager Update function to set them back to inactive when a bullet goes off screen.

I’ve implemented a rather basic system in BulletManager to handle looking for an inactive bullet that can be shot:

void BulletManager::Shoot(int speed, float a_fRotationAngle, glm::vec3 a_position) 
{
 //clearly need to make this 10 a const (or a variable if I pass it in, I guess)
 if (m_uiNumActiveBullets < 10) {
  while (true) { //is this bad? 
   //My teachers (and I) always taught it was bad coding practice
   // but otherwise I'm wasting a boolean and an extra check
 
   ++m_iLastAssignedBullet;
   if (m_iLastAssignedBullet >= 10) {
     m_iLastAssignedBullet = 0;
   }

   if (!m_bullets[m_iLastAssignedBullet].IsActive()) {
    m_bullets[m_iLastAssignedBullet].Shoot(speed, a_fRotationAngle, a_position);
    break;
   }
  }
 }
}

So basically I keep track of where I last assigned a bullet since it seems likely that the following position in the array will also be an inactive bullet. There’s probably a much better way of doing this – but for now it will do.

For completeness sake – the BulletManager constructor now looks like this:

m_bullets = new BulletObject[10]{
 //ok, this is shit - gotta find a better way of doing this.
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 {m_uiVBO, m_uiIBO, m_pShaderProgram}
 };
 for (unsigned int i = 0; i < 10; ++i) {
 m_bullets[i].AddComponent(m_pPhysicsComponents + i);
 m_bullets[i].AddComponent(m_pSpriteComponents + i);
 }

Which is called like so, in the GameScreen class:

m_pBulletManager = new BulletManager(m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, 
 m_pPhysicsComponents + 4, m_pSpriteComponents + 4);

This code clearly still needs some work (so many hard-coded values…) but it only took me about 2 hours to implement all this and get it up and running successfully. I’m pretty happy about this since I have often become lost and confused during refactors like this over the past year – getting tangled up half way through and needing a colleague to help me get untangled. I also seem to be getting faster since I know it’s taken me at least double the time to do something like this in the past.

If you have any comments on what I’ve done then please let me know. As always, my code is on github.