0
\$\begingroup\$

I have a base class Object, and depending on how the object must behave on the scene, I have multiple abstract "role" classes, that inherits virtually from Object:

  • Updatable if the object is dynamic, and must be updated every frame;
  • Collidable if the object can collide with others, and thus must take part on collision detection;
  • Drawable if the object is visible and must be drawn.

So, for instance, a particle special effect animation is both a Updatable, because it moves, and Drawable, because it is visible, but is not Collidable. A trigger may be Updatable, but is definitely Collidable, and is not Drawable because it is invisible.

If objects are never removed or created, this design is easy to manage: I have three lists on my world, one for each object type, that hold pointers for all objects of that type: i.e. the updatable_list holds a pointer to all my Updatables, etc. This is what I have so far.

Now I have to add and remove objects, and this design is biting me.

When a new object is added, I would have to use dynamic_cast to figure out its roles and add its pointer to the proper lists. But dynamic_cast is usually referred as bad design and code smell, not to mention being slow.

When an object is removed, it can potentially be part of multiple lists, so I would have to track its existence across lists, maybe by storing std::weak_ptr instead of raw pointers, and would have to check and potentially remove every time I use an object from such list. Other options would be linear search and remove from all lists upon removal, or use std::unordered_set instead of std::list to make it fast to search and remove. But I find these other options a bit fragile, because I risk having dangling pointers if I "forget" to remove the object from some list.

So, my question, is this a good design? How can it be improved? How is this problem of different object types usually handled in game engines?

\$\endgroup\$
5
  • \$\begingroup\$ Who owns the Objects? How are they created? \$\endgroup\$ – Vaillancourt May 10 '20 at 21:15
  • \$\begingroup\$ The world owns the objects, I have a "main" list of std::shared_ptr<Object> who owns all objects. They are either created on initialization or during the update of some updatable object (i.e. some objects can spawn others). \$\endgroup\$ – lvella May 11 '20 at 3:55
  • \$\begingroup\$ Your architecture sound very much like an Entity - Component - System architecture. \$\endgroup\$ – Philipp May 11 '20 at 7:53
  • \$\begingroup\$ @lvella What code/functions does your object have? What does it look like, if an object is for example not collidable? does it still have the "onCollide" function, just empty? \$\endgroup\$ – PSquall May 11 '20 at 13:09
  • \$\begingroup\$ The basic Object only have get_position(), get/set_transformation(), Drawable has draw(), Collidable has bounding volume related attributes and funcions, Updatable has update(). They don't have functions they don't need. \$\endgroup\$ – lvella May 11 '20 at 13:18
1
\$\begingroup\$

I would suggest two different ways of solving your problem. One close to your current implementation and another more abstract concept, but that is easier to use in the long run.

First the simple changes. If i understand it correctly, your roles are all subclasses from objectclass. Since objectdoesnt have the onDraw-method, you cant just add it to the drawable-list.
You could just implement a default onDraw-method in objectclass and every subclass can overwrite this function. This way your drawable-list would just be an objectlist with subclasses of object.

But how do you know, if your subclass is a drawable and needs to be added to the drawable-list? The easiest way to create a bitflag for every list-type you have. If you set the bit of drawable and collidable on 1, then on creating this subclass it will be added to the drawable-list and the collidable-list.
To add new instances of the classes, add an integer as ID and change the lists to maps. That way, you have a map where you can add and delete instances. This way you can even choose to remove an object from a certain list (e.g. something goes invisible, remove it from the drawable-list)

Now the more complex one. Your system is on half of the way to an Entitiy-Component-System. Instead of objects, that draw themselves and can change their state, you can give those objects container with those information.
Your object (lets call it entity) has slots for those components. Your drawable-list would now point to the drawable-component (containing the grapic information) of that entity instead of the entity itself. some goes for the collidable.
Your systems like the renderer or physics-engine would simply iterate over those lists and do their job. Some systems would need multiple of those components. like rendering the position and the graphics and the physics engine the position and the bounding volume.

Next step would be, that your entity is nothing more than an ID. It holds no data other that an integer, so why even bother. Your role-list can just be lists of those components. If Entity 431 gets removed, remove the components with the same id, or yet the entry 431 in your role lists.

If you are interested in this concept, look for Entity-Component-System or ECS. You will find plenty of resources for this.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.