7

I'm practicing my newbie c++ skills by making a small game using SFML and OpenGL. The programming part has been going fine for the most part, but I've questions regarding actual code/class design.

I've one class, MainLoop, which contains the game loop and owns one instance of each of the following classes: Events, Graphics, Commands, Game and UI. I initially wanted all of those to be a single class( with functions separated in different .cpp files), but was told that was the wrong approach for OOP/C++. However, while I can see the good side with separating them( encapsulation, modularity, debugging), I seem to be encountering a lot of bad stuff too. Let me take an example with the user pressing a UI button.

First, the MainLoop gets the event from SFML's window class. MainLoop sends it over to my own Event class, which interprets the event and sends it over to the UI class to check if it 'hit' any of the buttons. If true, the UI class then sends it over to the Command class which interprets the button command. Then, finally, the command class sends it over to the Game class or wherever else it needs to go.

It all seems very heavy-handed to me, and has also, at least the way I've been doing it at the moment, required a lot of forward declarations( and before I learned about those I ended up with tons of circular dependencies). I doubt it does much good for performance either.

Anyway, is there some trick here that I'm missing? How should these classes be connected, how should they communicate? How should I be forwarding commands from, say, the Event class to the UI class? Should I really have forward declarations, includes and stuff everywhere, and doesn't that ruin modularity? Should I have it all run through the MainLoop class and forward the returns using the integers/floats/chars which don't require declarations instead? I'm kinda at a loss here.

Charles
  • 50,943
  • 13
  • 104
  • 142

2 Answers2

4

I can suggest you some designs I used while developing games, while they are not anything good for sure but I never had many problems with them. I'll keep it short just to give you the idea.

First of all you need a view manager, this manager should manage the current view of your game, this can be implemented as a stack of views or whatever. So you will have a ViewManager class which knows all the views of your game and is able to dispatch things to the current one.

Then you need an abstract class GameView which should provide basic interface from the outside like:

  • drawMe(), that draws the view
  • receivedMouseEvent(Event e), that will receive mouse events
  • activate() and deactivate() to do actions that should be done when the view is pushed or popped in the view manager

Now with this abstract class you should implement any specific view or part of view of your game so that you can push and pop them in the view stack.

A good thing is to have subclasses to manage UI elements, eg class ActiveArea which responds to clicks, Button which inherits from ActiveArea and it is also able to provide a two state graphics. These elements should be contained inside a list of clickable elements which is stored in the abstract view so that every concrete view can add its buttons to a common implementation with no worries. In this way you can have something like (metacode)

void AbstractView::receiveEvent(Event e) {
  for (ActiveArea *area in areas)
    if (area.isInside(e)) {
      area->action();
      return;
    }

  innerReceiveEvent(e); //which should be a pure virtual function that will call a method specified in concrete views
}

In this way you will have each view managing their own state, and the view manager that will take care of drawing and managing events, eg

void ViewManager::draw() {
  for (AbstractView *view in views) // from top to bottom of the stack
    view.draw();
}
Jack
  • 131,802
  • 30
  • 241
  • 343
  • I hadn't considered such an approach, but it does sound useful. So far I've just been using a simple global-acting 'GameState' which I implement in Events/UI/Commands to decide what to show and how to react to input. I.e. if GameState==MainMenu, draw these UI objects, if in the main game view draw these, and so forth. Your approach might be a fair bit cleaner and more useful. But you don't destroy them at all? You just make them active/inactive? – user1870725 Dec 02 '12 at 19:08
  • This mainly depends on your specific problems, if views are heavy then you should destroy them, otherwise you can just deactivate them. Of course the former approach requires you to have a persistent state of the view (that could be an inner object) so that you can recover it when needed. – Jack Dec 02 '12 at 20:46
  • I also used the approach you suggested but I felt the necessity to switch to something more similar to the one I explained once that complexity of the whole thing grew over a certain threshold, code became a mess so much that it was worth refactoring it – Jack Dec 02 '12 at 20:47
  • I think I'll try my hand at implementing something like this, as I can see how my current approach would become messy eventually. Sounds like it'll take some planning to get it all working right though. Thanks for the tip! – user1870725 Dec 02 '12 at 21:50
3

I can imagine that it seems heavy, but it is the proper way to do this. Note that function calls aren't heavy at all, and it does make the whole thing a lot easier to read. Or at least it should. ;-)

Every class should have a header file containing the class definition, but not the implementation of its member functions. Any file should be able to include any class header file. Only if you are using templates (where the implementation must be in the header file) there may be circular dependencies, but from your description I don't think you have them. The headers don't need to include each other. If you need to pass pointers or references to other classes in function arguments, it's fine to forward-declare the other classes at the start of your header. You should be able to have any include at the top of a source file. If not, please give more information on why you think this is needed in your case.

Bas Wijnen
  • 1,288
  • 1
  • 8
  • 17
  • I agree.. and I default to forward declaration where possible.. Keep in mind when thinking about classes and their public functions that the purpose of OO is to manage complexity (for yourself and any other human working on the code) by creating layers of abstraction. And for performance, usually the algorithms/datastructures you choose have way more impact than calling functions. Though with games and other realtime software, you sometimes have to compromise for performance. – Emile Vrijdags Dec 02 '12 at 18:36
  • 1
    It seems kinda strange to me to use forward declarations everywhere. Shouldn't each class, optimally, be able to 'stand on its own'? For the sake of modularity, I mean. I.e. if my Graphics class needs my UI class(to get the coordinates of UI elements for drawing, say), then I won't be able to use my Graphics class in another application without also including the UI class...Stuff like that. Also, in that same regard, should the commands go directly from one sub-class(e.g. Events) to another(e.g. UI), or should they be relayed through my MainLoop class thus at least preserving some modularity? – user1870725 Dec 02 '12 at 18:55
  • Your Graphics class will need "a" UI class, it doesn't need to be yours. It only must define the same interface (or at least the parts you use). As for going through the main loop, that all depends on which class manages what. If your graphics class needs to draw something on the screen, I see no problem to let it make a direct call to the UI. But if a button press should launch some command, I don't think the UI (which detects the button press) needs to know about the meaning of its buttons. So then it makes sense to let MainLoop decide which command to call. – Bas Wijnen Dec 02 '12 at 19:38
  • Thanks! Don't really feel comfortable using so many pointers, references and forward declarations everywhere, but I guess it'll all start to feel more natural as I get more practice doing "larger projects" like this one. – user1870725 Dec 02 '12 at 22:41