Refactoring: Making a game engine more modular and how
My game engine consists of a range of loosely coupled modules, which can be loaded and unloaded.
Some examples are: The base module, handling window management and responding to OS events, entity manager, Lua manager, physics manager.
Right now, these modules are organized as namespaces, and their state is defined through local variables in the respective source files. Each of the namespaces has an Open(), Close() and Update() function.
Now, I don't really like the solution with namespaces anymore.
It's not flexible enough
Even if it might not be needed in reality, having the plain ability of creating multiple instances of a module seems proper
It seems like I'm not making use of OOP here - a module base class with a virtual Update() member function would sound more reasonable
It's harder to ensure that when the module is closed and reopened, all of the variables will be reset too (A class with constructors and destructors would be easier)
You can't properly have the modules managed without explicitly calling Open(), Close() and Update()
So, my idea would have been to use classes for each of the modules, derived from a module base class. The module class instances would be handled by the ModuleManager class, which updates them.
But the solution with OOP brings up the problem of how the modules should communicate. Right now, the base module told the console module to print something via console::print()
How can I work around this problem without having to use something like g_ModuleManager.GetConsoleModule()->print()
?
How could this module manager work in detail?
And my final question:
Do you have any further tips for me on the topic of writing a modular game engine in C++ with OOP?
Are there any design patterns that could help me in this situation, or maybe even concrete reading material?
Namespaces become very inflexible very quickly.
One way to keep things loosely coupled is to use messaging through a central message dispatcher; rather than saying console::print
you would say messenger->sendMessage(PRINT_MESSAGE, stuffToPrint)
.
The console would register itself as a listener to PRINT_MESSAGE
s and act the way it wants to. The sender doesn't need to care whether someone's listening, and there could even be several listeners to each message (useful for debugging).
Regarding reading materials, Jason Gregory's "Game Engine Architecture" is pretty good, discussing the pros and cons of several architectures.
First as a general reminder, just remember to use inheritance for substitutability, not code reuse. Roughly this means that anything that inherits from your base module needs to support (mostly) the same operations, and that if a user says open
, close
, or update
on any module, it will provide expected results.
Do you really want a module manager? It might be better instead to draw a diagram of the relationships between your different classes/modules and create the linkage there rather than a top-level manager. Then the application's main loop would know about one or more high level modules and call the appropriate methods on them directly. If desired there could even be a high-level module to manage this high level of delegation.
For your console example, assuming you don't want to support multiple consoles, you're probably better off just keeping the namespace: It gives you the ability to directly refer to the single console that you're interfacing with. Alternately you could require a "parent console" be passed in to each module that's created and it uses that associated console to do its I/O as appropriate. This gives you rather more flexibility at the expense of needing to make sure that you have to maintain the console within your base module.
g_ModuleManager.GetConsoleModule()->print()
simple, as stated before me, you are most likely going to have to refactor a lot of things to take advantage of OOP. You want each class to have a distinctive relationship, not all managed on an equal playing field. Whoever is telling the the ConsoleModule to print should have its own print function that tell the ConsoleModule to print. This way you just say print()
also think in term of "has a" for composition and "is a" for inheritance. I would read the entire section 8: http://www.learncpp.com/cpp-tutorial/81-welcome-to-object-oriented-programming/
As an example my current project: I have a board that "has" beatles
, walls
, and apples
. Each of those "is an" icon
上一篇: 资源布局(最优策略)
下一篇: 重构:使游戏引擎更加模块化,以及如何实现