0
\$\begingroup\$

I have a base class called Entity and several sub classes like Player and Goblin. They all have the method update(delta), but that's not enough to update Goblin for example, which also requires setTargetPosition(vec2) to get updated properly.

All sub classes are stored together in a single vector vector<Entity*> entities.

Solution 1 is to use some C++ version of JavaScript's instanceof and depending on class call methods like update() and setTargetPosition().

Solution 2 is to have a source file with global variables like Goblin's target position and thus only update() is necessary because all the data that's needed is in the file with global variables.

What solution is better than those above?

\$\endgroup\$
  • 2
    \$\begingroup\$ Who calls setTargetPosition? If they know it's a goblin, why would they need to dynamic_cast them? \$\endgroup\$ – Vaillancourt Sep 9 '19 at 18:29
  • \$\begingroup\$ Is there a reason why setTargetPosition can't be called from update? \$\endgroup\$ – Pharap Sep 10 '19 at 7:04
  • \$\begingroup\$ Because Goblin doesn't have the necessary data, in this case the players position which is the target position. \$\endgroup\$ – Comlud Sep 10 '19 at 14:50
8
\$\begingroup\$

Your design is at odds with reality, and you need to bring the two into alignment.

The thesis of your design seems to be, essentially, that "everything is an entity" and "everything is in one big list" because you can just update() that whole list. That's fine, up until the point where it isn't, which is the point you've reached now.

Your interface for Entity is not rich enough to actually express reality: some Entity implementations require extra information to fully update themselves, information that is not present in Entity. You can address this in a few ways.

One way is to give Entity that information; this means storing the target position or target entity in Entity itself instead of Goblin, which now let's Entity have the requisite interface to manage and update the target position. If most subclasses won't use this information, however, this is somewhat wasteful.

What I'd argue is the better way is to simply treat the entities that need special handling (goblins) differently. Store them in a separate std::vector<Goblin*> so you know everything in that vector is a goblin and can use the goblin-specific APIs. Then you can both update() them and set their target positions all in a loop.

This avoids complicating the base class with data and methods that aren't universal to all child types. It also gives you a much stronger locality of reference with respect to the update of the goblin instances: you can know when all the goblins start and stop the update work because there's an explicit loop for it, rather than a loop over the big entity "master list" that is potentially processing entity types in an effectively-random order.

This can be a huge win when when you end up having other logic that really wants to depend on the goblin update being "done," as well as for finding places where your work can be data-parallel and benefit from concurrency.

And of course it doesn't involve dynamic_cast or the stashing of information in well-known globals, as requested.

\$\endgroup\$
  • 1
    \$\begingroup\$ Did you write an answer about a similar topic not long ago? I tried to find it but I couldn't. \$\endgroup\$ – Vaillancourt Sep 9 '19 at 19:13
  • 1
    \$\begingroup\$ Probably, this kind of problem is a favorite of mine. \$\endgroup\$ – user1430 Sep 9 '19 at 19:14
  • \$\begingroup\$ Thank you Josh for the great answer! \$\endgroup\$ – Comlud Sep 10 '19 at 6:03
  • 1
    \$\begingroup\$ Good answer. One addition: Even if Goblin is currently the only enemy requiring a target update, it's probably worthwhile to create an interface shared by all (future) entities requiring target updates and use that in the new vector std::vector<INeedsTargetUpdate*>. \$\endgroup\$ – Llewellyn Sep 10 '19 at 7:07
  • \$\begingroup\$ @Josh, if the game has hundreds of entities that all require different method calls before update(), is it still good to keep 'em in different vectors? The amount of vectors will become pretty big. \$\endgroup\$ – Comlud Sep 10 '19 at 15:10
5
\$\begingroup\$

Preface: I've designed several game engines in C and C++ by hand and have plenty of experience with game engine architectural patterns and theory

It appears you're designing a game from scratch. Let me first say, no, you should not use global variables for this purpose. They are stored in what's called the BSS or Data segment and while you may not notice the performance hit from cache misses right away, I can guarantee that once you have your game in a more developed state, you will be kicking yourself in the butt for not outlining the design of your custom engine before embarking on the design journey.

In simple terms, a cache miss happens when a program tries to accesses memory which is "far away" from the memory being frequently or currently accessed, which in a game is primarily going to be stack memory. A professor once told me "Cache is king" when it comes to performance.

Aside from that, you may want to read through a common architectural pattern called Entity-Component-System architecture which is easy to understand and simple to implement. Since, in a custom engine, you will have a graphics implementation, a serializing implementation for saving loading, potentially a reflection system, audio or the like, your program will be moving through different segments of memory quite often and it is important to begin compartmentalizing your data now as it may get quite cluttered quite fast.

Like all tools in the game-dev toolbox, global variables have their time and place. They can be quite useful in reflection with regards to entity-factories when using static initializers, or for singleton classes. However, for storing the objects which make up the "meat-and-potatoes" of your game, you should certainly plan on a different strategy other than global variables.

For more complete information regarding memory management, this is an excellent overview of pitfalls and strategies and may provide a more complete picture to help you plan.

\$\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.