重构:使游戏引擎更加模块化,以及如何实现
我的游戏引擎由一系列松散耦合的模块组成,可以加载和卸载。
一些示例是:基本模块,处理窗口管理和响应操作系统事件,实体管理器,Lua管理器,物理管理器。
现在,这些模块被组织为名称空间,它们的状态通过各自源文件中的局部变量来定义。 每个名称空间都有一个Open(),Close()和Update()函数。
现在,我不再喜欢使用命名空间的解决方案。
它不够灵活
即使实际上可能不需要它,但具有创建模块的多个实例的普通能力似乎是适当的
看起来好像我没有在这里使用OOP - 具有虚拟Update()成员函数的模块基类听起来更合理
很难确保当模块关闭并重新打开时,所有的变量也将被重置(一个具有构造函数和析构函数的类会更容易)
如果没有显式调用Open(),Close()和Update(),您无法正确地管理模块。
所以,我的想法会一直使用类为每个模块,从模块基类派生。 模块类实例将由ModuleManager类处理,该类将更新它们。
但OOP解决方案带来了模块应该如何通信的问题。 现在,基本模块告诉控制台模块通过console::print()
打印某些内容
我怎样才能解决这个问题,而不必使用像g_ModuleManager.GetConsoleModule()->print()
?
这个模块管理器怎么能够详细工作?
我的最后一个问题是:
关于使用OOP在C ++中编写模块化游戏引擎的问题,您是否还有任何提示?
在这种情况下是否有任何设计模式可以帮助我,或者甚至是具体的阅读材料?
命名空间非常快速地变得非常不灵活。
让事情松散耦合的一种方法是通过中央消息分派器使用消息传递; 而不是说console::print
你会说messenger->sendMessage(PRINT_MESSAGE, stuffToPrint)
。
控制台将自己注册为PRINT_MESSAGE
的监听器,并按照自己的PRINT_MESSAGE
行事。 发件人不需要关心是否有人在倾听,甚至可能会有几个听众收听每条消息(对调试有用)。
关于阅读材料,Jason Gregory的“游戏引擎架构”非常好,讨论了几种体系结构的优缺点。
首先作为一般性提醒,只要记住使用继承来代替可重用性,而不是代码重用。 粗略地说,这意味着从基本模块继承的任何东西都需要支持(大部分)相同的操作,并且如果用户说在任何模块上open
, close
或update
,它将提供预期的结果。
你真的想要一个模块管理器吗? 最好是绘制一个关于你的不同类/模块之间关系的图表,然后创建一个链接,而不是一个顶级的经理。 然后,应用程序的主循环将知道一个或多个高级模块,并直接调用适当的方法。 如果需要,甚至可以有一个高级模块来管理这种高级别的委派。
对于您的控制台示例,假设您不想支持多个控制台,那么保留名称空间可能会更好:它使您能够直接引用与之连接的单个控制台。 或者,您可能需要将“父级控制台”传递给每个创建的模块,并使用该关联的控制台根据需要执行其I / O。 这样可以提供更多的灵活性,但需要确保必须将控制台维护在基本模块中。
g_ModuleManager.GetConsoleModule()->print()
简单的说,正如我之前所说的,你很可能不得不重构很多东西来利用OOP。 你希望每个班级都有独特的关系,并不是所有班级都在同一个竞赛场上进行管理。 谁告诉ConsoleModule打印谁应该有自己的打印功能,告诉ConsoleModule打印。 这样你就可以说print()
也可以用“有”一词来表达,而“是一种”用于继承。 我会阅读整个第8节:http://www.learncpp.com/cpp-tutorial/81-welcome-to-object-oriented-programming/
举个例子,我目前的项目是:我有一个“有” beatles
, walls
和apples
的董事会。 每一个“是一个” icon