Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

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?

share|improve this question
1  
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. –  Jonathan Connell Jun 27 '11 at 12:32
    
-1 if this is related to game programming, then eating pizza is too. Grab yourself some good software design books –  Maik Semder Jun 27 '11 at 21:58
9  
@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. –  BlueRaja - Danny Pflughoeft Jun 28 '11 at 21:27
    
@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 –  Maik Semder Jun 28 '11 at 21:51
add comment

3 Answers 3

up vote 40 down vote accepted

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.

share|improve this answer
5  
Wow, thank you for putting so much effort into the answer. I keep reading how globals are ugly and singletons should perhaps be avoided, so I really appreciate such a complete answer. –  Juicy Jun 28 '11 at 0:30
2  
+1 for the Service Locator! I didn't even know that there was something built into XNA for facilitating it. I always had built a custom one. Is there any advantage for using the XNA version? –  AttackingHobo Jun 28 '11 at 6:15
    
@AttackingHobo I'd only guess it would be more performant... or not depending on your useage of it. Probably one for a useage dependant test. –  Jonathan Connell Jun 28 '11 at 7:38
    
@AttackingHobo: The advantage is that you don't have to reinvent the wheel, and it's already automatically available to all your GameComponents, via Game.Services. The syntax is a bit ugly, but at the bottom of that post I linked to I show an easy way to fix that. –  BlueRaja - Danny Pflughoeft Jun 28 '11 at 16:26
1  
+1 for Dependency Injection. –  Kyle C Jun 28 '11 at 18:56
show 3 more comments

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.

share|improve this answer
1  
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. –  Juicy Jun 27 '11 at 12:48
    
For my purposes the map does own the entities, since map here is analogous with 'level'. I'll consider your idea about messages, thanks. –  Juicy Jun 27 '11 at 12:53
    
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. –  Zan Lynx Jul 29 '11 at 17:29
add comment

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.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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