39
\$\begingroup\$

I have a basic 2D tower defense game in C++.

Each map is a separate class which inherits from GameState. The map delegates the logic and drawing code to each object in the game and sets data such as the map path. In pseudo-code the logic section might look something like this:

update():
  for each creep in creeps:
    creep.update()
  for each tower in towers:
    tower.update()
  for each missile in missiles:
    missile.update()

The objects (creeps, towers and missiles) are stored in vector-of-pointers. The towers must have access to the vector-of-creeps and the vector-of-missiles to create new missiles and identify targets.

The question is: where do I declare the vectors? Should they be members of the Map class, and passed as arguments to the tower.update() function? Or declared globally? Or are there other solutions I'm missing entirely?

When several classes need to access the same data, where should the data be declared?

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Global members are considered 'ugly' but are quick and make development easier, if it's a small game, that's no problem (IMHO). You could also create an exteral class which handles the logic (why the towers need these vectors) and has access to all of the vectors. \$\endgroup\$ Jun 27, 2011 at 12:32
  • \$\begingroup\$ -1 if this is related to game programming, then eating pizza is too. Grab yourself some good software design books \$\endgroup\$ Jun 27, 2011 at 21:58
  • 10
    \$\begingroup\$ @Maik: How is software design unrelated to game programming? Just because it also applies to other fields of programming does not make it off-topic. \$\endgroup\$ Jun 28, 2011 at 21:27
  • \$\begingroup\$ @BlueRaja lists of software design patterns are better suited on SO, that's what it is there for after all. GD.SE is for game programming, not software design \$\endgroup\$ Jun 28, 2011 at 21:51

3 Answers 3

53
\$\begingroup\$

When you need a single instance of a class throughout your program, we call that class a service. There are several standard methods of implementing services in programs:

  • Global variables. These are the easiest to implement, but the worst design. If you use too many global variables, you will quickly find yourself writing modules that rely on each other too much (strong-coupling), making the flow of logic very difficult to follow. Global variables are not multithreading-friendly. Global variables make tracking the lifetime of objects more difficult, and clutter the namespace. They are, however, the most performant option, so there are times when they can and should be used, but use them spareingly.
  • Singletons. About 10-15 years ago, singletons were the big design-pattern to know about. However, nowadays they are looked down upon. They are much easier to multi-thread, but you must limit their use to one thread at a time, which is not always what you want. Tracking lifetimes is just as difficult as with global variables.
    A typical singleton class will look something like this:

    class MyClass
    {
    private:
        static MyClass* _instance;
        MyClass() {} //private constructor
    
    public:
        static MyClass* getInstance();
        void method();
    };
    
    ...
    
    MyClass* MyClass::_instance = NULL;
    MyClass* MyClass::getInstance()
    {
        if(_instance == NULL)
            _instance = new MyClass(); //Not thread-safe version
        return _instance;
    
        //Note that _instance is *never* deleted - 
        //it exists for the entire lifetime of the program!
    }
    
  • Dependency Injection (DI). This just means passing the service in as a constructor parameter. A service must already exist in order to pass it into a class, so there's no way for two services to rely on each other; in 98% of the cases, this is what you want (and for the other 2%, you can always create a setWhatever() method and pass in the service later). Because of this, DI doesn't have the same coupling problems as the other options. It can be used with multithreading, because each thread can simply have its own instance of every service (and share only those it absolutely needs to). It also makes code unit-testable, if you care about that.

    The problem with dependency injection is that it takes up more memory; now every instance of a class needs references to every service it will use. Also, it gets annoying to use when you have too many services; there are frameworks that mitigate this problem in other languages, but because of C++'s lack of reflection, DI frameworks in C++ tend to be even more work than just doing it manually.

    //Example of dependency injection
    class Tower
    {
    private:
        MissileCreationService* _missileCreator;
        CreepLocatorService* _creepLocator;
    public:
        Tower(MissileCreationService*, CreepLocatorService*);
    }
    
    //In order to create a tower, the creating-class must also have instances of
    // MissileCreationService and CreepLocatorService; thus, if we want to 
    // add a new service to the Tower constructor, we must add it to the
    // constructor of every class which creates a Tower as well!
    //This is not a problem in languages like C# and Java, where you can use
    // a framework to create an instance and inject automatically.
    

    See this page (from the documentation for Ninject, a C# DI framework) for another example.

    Dependency injection is the usual solution for this problem, and is the answer you will see most highly upvoted to questions like this on StackOverflow.com. DI is a type of Inversion of Control (IoC).

  • Service Locator. Basically, just a class that holds an instance of every service. You can do it using reflection, or you can just add a new instance to it every time you want to create a new service. You still have the same problem as before - How do classes access this locator? - which can be solved in any of the above ways, but now you only need to do it for your ServiceLocator class, rather than for dozens of services. This method is also unit-testable, if you care about that sort of thing.

    Service Locators are another form of Inversion of Control (IoC). Usually, frameworks that do automatic dependency injection will also have a service locator.

    XNA (Microsoft's C# game programming framework) includes a service locator; to learn more about it, see this answer.


By the way, IMHO the towers should not know about the creeps. Unless you are planning on simply looping over the list of creeps for every tower, you'll probably want to implement some nontrivial space partitioning; and that sort of logic doesn't belong in the towers class.

\$\endgroup\$
1
  • \$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$
    – user1430
    Sep 19, 2017 at 20:00
5
\$\begingroup\$

I personally would use polymorphism here. Why have a missile vector, a tower vector, and a creep vector..when they all call the same function; update? Why not have a vector of pointers to some base class Entity or GameObject?

I find a good way to design is to think 'does this make sense in terms of ownership'? Obviously a tower owns a way to update itself, but does a map own all the objects on it? If you go for global, are you saying that nothing owns the towers and creeps? Global is usually a bad solution - it promotes bad design patterns, yet it is much easier to work with. Consider weighing up 'do I want to finish this?' and 'do I want something that I can reuse'?

One way around this is some form of messaging system. The tower may send a message to the map (which it has access to, perhaps a reference to its owner?) that it hit a creep, and the map then tells the creep it's been hit. This is very clean and segregates data.

Another way is to just search the map itself for what it wants. However, there may be issues with update order here.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Your suggestion about polymorphism isn't really relevant. I have them stored in separate vectors so I can iterate over each type individually, such as in the drawing code (where I will want certain objects drawn first) or in collision code. \$\endgroup\$
    – Juicy
    Jun 27, 2011 at 12:48
  • \$\begingroup\$ For my purposes the map does own the entities, since map here is analogous with 'level'. I'll consider your idea about messages, thanks. \$\endgroup\$
    – Juicy
    Jun 27, 2011 at 12:53
  • 1
    \$\begingroup\$ In a game performance matters. So vectors of the same object times have better locality of reference. Also, polymorphic objects with virtual pointers have awful performance because they cannot be inlined into the update loop. \$\endgroup\$
    – Zan Lynx
    Jul 29, 2011 at 17:29
0
\$\begingroup\$

This is a case in which strict object oriented programming (OOP) breaks down.

According to the principles of OOP, you should group data with related behavior using classes. But you have a behavior (targeting) that needs data that are not related to each other (towers and creeps). In this situation, many programmers will try to associate the behavior with part of the data it needs (e.g. towers handle targeting, but don't know about creeps), but there's another option: don't group the behavior with the data.

Instead of making the targeting behavior a method of the tower class, make it a free function which accepts towers and creeps as arguments. This might require making more of the members that are left in the tower and creep classes public, and that's OK. Data hiding is useful, but it's a means, not an end in itself, and you shouldn't be a slave to it. Besides, private members aren't the only way to control access to data — if data isn't passed into a function and it isn't global, it's effectively hidden from that function. If using this technique lets you avoid global data, you might actually be improving encapsulation.

An extreme example of this approach is the entity system architecture.

\$\endgroup\$

You must log in to answer this question.

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