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.
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 viewreceivedMouseEvent(Event e)
, that will receive mouse eventsactivate()
and deactivate()
to do actions that should be done when the view is pushed or popped in the view managerNow 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();
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With