2
\$\begingroup\$

Example I have an "Enemy" class, and 5 types of enemy subclasses.

These enemies will be spawn often, so I want to use an object pool.

However, this means I can no longer use an object pool for Enemies, now I have to use an object pool for each enemy subclass, so now, I have to make 5 object pools, and that's an arbitrary number that could just as easily be 25.

eg.

// Each with their own implementations of Enemy's "attack" or similar method
class Dragon extends Enemy { ... }
class Ogre extends Enemy { ... }

ObjectPool<Ogre> ogrePool;
ObjectPool<Dragon> dragonPool;
// etc ...

It seems a bit weird.

So what can I do? If I take out the subclasses, I can either put all the functionallity of every subclass into the base class, and keep an id of the type of class this base class represents, calling the appropriate functions accordingly... But this leaves a bloated hard to maintain class that requires a lot of modification everytime I add or remove a subclass behaviour.

eg:

class Enemy {
    enum EnemyType {OGRE, DRAGON, ETC;}
    EnemyType enemyType;

    public Enemy(EnemyType type) { this.enemyType = type; }

    public void attack() {
        switch(enemyType) {
            case Ogre:
                //Do ogre attack
                break;
            // etc...
    }

    // ...    

}

// Now I can instantiate things from 1 object pool

Alternately I could try and use composition. I could have enemy contain components of other classes that govern behaviour. For example, I could have something like an "WeaponBehaviour" and "ArmourBehaviour". Then, the type of weapon behaviour, armour behavour, etc, would differentiate the different subclasses. However, every time i obtain an inatnace of my enemy class, I would have to instantiate each behaviour, and would end up likely needing an object pool for each behaviour.

eg:

class Enemy {
    WeaponComponent weaponComponent;
    ArmourComponent armourComponent;

    public Enemy() {}

    public void init(WeaponComponent weapon, ArmourComponent armour) {
        weaponComponent = weapon;
        armourComponent = armour;
    }

   public void attack() {
       weaponComponent.attack();
   }

   // ...


}

// And then instantiate things like so:

ObjectPool<Enemy> enemyObjectPool;
Enemy enemy = enemyObjectPool.obtain();
enemy.initialize(new OgreWeapon(), new OgreArmour());
// or alternately have an object pool for OgreWeapons and OgreArmour

None of the solutions I can think of seem like they are ideal, and I was wondering what a good way to organize this was.

\$\endgroup\$
1

3 Answers 3

1
\$\begingroup\$

I personally prefer your second idea of using composition, but if you wanted to stay with the style of using generics as you do currently, but not having to initialize multiple pools maybe you could allow one object pool to spawn different enemies/objects,a small snippet just to give you an idea of what I mean:

 ObjectPool myObjectPool = new ObjectPool();
 myObjectPool.Init<Ogre>(10); //Cache 10 Ogre Objects
 myObjectPool.Obtain<Ogre>(); //Get an ogre
 myObjectPool.Release<Ogre>(); //Return it to the pool

This type of ObjectPool could instead be an "ObjectPoolManager", that internally maps types to instances your current ObjectPool system

\$\endgroup\$
3
  • \$\begingroup\$ Thanks. Ya I was considering just putting the pool in my factory class. I am using LibGdx's provided [object pool] (github.com/libgdx/libgdx/wiki/Memory-management) so that's why it uses templates. If I use composition, I still need separate object pools for each component, and then also the Enemy class, which contains the components - so I'm not really sure :/ \$\endgroup\$
    – not tellin
    Aug 13, 2014 at 0:52
  • \$\begingroup\$ You could have the "expensive" objects each enemy needs be part of the enemy class, and give the specific enemy components access to those (similar to the Strategy pattern). For example, you could have an EnemyMovementSystem class with a Move(Enemy enemy) method. It'd be a cheap object (<-this is the key) that Ogre/Dragon/etc. extend (like OgreMovementSystem). All they'd do is expose a Move method that Enemy can call. So you'd pool Enemy, but since the EnemyMovementSystem will be a very cheap object you don't have to pool it. \$\endgroup\$ Aug 13, 2014 at 1:07
  • \$\begingroup\$ It also makes customizing enemies very easy. OgreMovementSystem could have a movementSpeed parameter, DragonMovementSystem could have a maxHeight parameter (just watch out, as adding more instance variables makes the object more expensive). It'd also let you do interesting things like defining common AI patterns that all your enemies share with ease. \$\endgroup\$ Aug 13, 2014 at 1:12
1
\$\begingroup\$

You mentioned an object pool for each enemy subclass...

"It seems a bit weird."

But it would work, right? Seems fine to me. Subclassing helps you organize your code, while object pools are inherently a space-for-time tradeoff.

Another approach (which you allude to) would be to reduce your number of classes with parameterization. One extreme of this is to replace subclasses with a giant switch(enemyType){} case statement. There may be ways to structure the actual source to make the "bloat" more palatable.

Another approach somewhere in between would be a stateless behavior object, one which operates on an Enemy instance but can be shared across all enemies of that type. Something like

Enemy e;
e.setBehavior(enemyBehaviorXxx);  // only one instance of each enemy behavior

and in one of Enemy's methods,

enemyBehavior.updateMe(this);

This requires that Enemy expose enough methods/fields for any EnemyBehavior to do its work. (Sort of semi-bloat, I guess.) You may end up with a few

public int generalVar1;
public int generalVar2;

Which you may find "weird". I'd even agree, but probably do it anyway. It's all tradeoffs.

\$\endgroup\$
0
\$\begingroup\$

You could use polymorphism.

let all your diferent enemies inherit from the same class or implement the same interface, I'll call it Enemy

Then all of your enemies would belong to the class Enemy, and that allows you to do the following

Enemy enemyA = new EnemyA();

Enemy enemyB = new EnemyB();

and finally you could do something like

Queue<Enemy> Pool; :)

Something to have in count is that you can call methods and use properties in common if they are defined in the father or "base" class, but properties that are not inside the Enemy class can't be accesed

\$\endgroup\$
1
  • \$\begingroup\$ This answer would be better if it explained the operation the user needs to perform when they want to initialize a new EnemyB instance from the pool, when the next items in the queue might be of type EnemyA. \$\endgroup\$
    – DMGregory
    Sep 22, 2018 at 14:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .