我想创建一个游戏引擎作为培训&投资组合项目和模块化方法听起来很有希望,但我在模块设计方面遇到了一些问题。
首先,我想创建像渲染,应用程序,实用程序等低级模块,并在像Terrain这样的高级模块中使用它们。 所以依赖关系有点像这个游戏< -Engine< -Terrain< -Rendering。
我想创建多个渲染"子模块"比如Rendering.Direct3D11和Rendering.OpenGL。那就是我有循环依赖的地方。子模块将使用渲染和渲染的接口需要管理子模块,对吧? 游戏和LT; - 发动机< -Terrain< -Rendering< - > Rendering.Direct3D11
我可能会创建一个像RenderingInterfaces这样的模块并打破循环依赖,但这似乎是一个hacky解决方法。我打算使用"子模块设计"多次喜欢: 游戏和LT; - 发动机< -Application< - > Application.Windows
子模块设计难看吗?有没有办法在没有循环依赖的情况下使用子模块设计?
答案 0 :(得分:1)
在您的设计中不应该需要反向依赖。
这完全是关于接口的。您的渲染模块需要一个本机渲染API(在您的术语中为子模块),但它不应该关心它是OpenGL还是Direct3D11。 API子模块只需暴露一个通用的API;像CreatePrimitiveFromResource()
,RenderPrimitive()
这样的子模块......这些子模块不应该知道上层,它们只是公开它们的公共API。
换句话说,唯一的"依赖"需要的是渲染模块依赖于渲染子模块(使用公共接口),渲染子模块不依赖于任何东西(在引擎中),它们只是暴露了一个公共接口。
简单示例:
我们有一个渲染模块" IntRenderer"这会产生整数。它的工作是将整数转换为字符并打印它们。现在我们想要子模块" IntRenderer.Console"和" IntRenderer.Window",在控制台或窗口中打印。
有了这个,我们定义了我们的接口:子模块必须是一个导出函数void print( const char * );
的DLL。
整个描述是我们的界面;它描述了我们所有的int渲染器子模块必须具有的共同公共面。以编程方式,您可以说界面只是函数定义,但这仅仅是术语问题。
现在每个子模块都可以实现接口:
// IntRenderer.Console
DLLEXPORT void print( const char *str ) {
printf(str);
}
// IntRenderer.Window
DLLEXPORT void print( const char *str ) {
AddTextToMyWindow(str);
}
使用它,int渲染器可以只使用导入子模块,并使用printf(myFormattedInt);
,无论子模块如何。
您可以根据需要明确定义您的界面,如果需要,可以使用C ++多态
示例:子模块X必须是导出函数CreateRenderer()
的DLL,该函数返回继承类Renderer
的类,并实现其所有虚函数。
答案 1 :(得分:1)
你可以抽象地解决这个问题。假设您有三个dylib:Game.dll
,Renderer.dll
,SubRenderer.dll
。
渲染器界面可能如下所示(简化):
// Renderer.h
class SubRenderer
{
public:
virtual ~SubRenderer() {}
virtual void render() = 0;
};
class API Renderer
{
public:
explicit Renderer(SubRenderer* sub_renderer);
void render();
private:
SubRenderer* sub_renderer;
};
您可以将其添加到Renderer.h
或类似的内容中,并且可以在render
中实现呈现器构造函数和Renderer.cpp
方法,其中包含输出Renderer.dll
的项目}。
现在在SubRenderer.dll
,你可能有这样的功能:
// SubRenderer.h
class SubRenderer;
API SubRenderer* create_opengl_renderer();
这可以在SubRenderer.cpp
中实现,它被编译/链接到输出`SubRenderer.dll。它可能看起来像这样:
// SubRenderer.cpp
#include "SubRenderer.h"
#include <Renderer.h>
class OpenGlRenderer: public SubRenderer
{
public:
virtual void render() override {...}
};
SubRenderer* create_opengl_renderer()
{
return new OpenGlRenderer;
}
最后但并非最不重要的是,在Game.dll的某些源文件中,您可以在某些Game.cpp
内执行此类操作:
// Game.cpp
#include <Renderer.h>
#include <SubRenderer.h>
int main()
{
SubRenderer* opengl_renderer = create_opengl_renderer();
Renderer renderer(opengl_renderer);
renderer.render(); // render a frame
...
delete opengl_renderer;
}
...当然希望有一个符合RAII的更安全的设计。
使用这种系统,你有这些头依赖:
`Game.cpp->Renderer.h`
`Game.cpp->SubRenderer.h`
`SubRenderer.cpp->Renderer.h`
就模块依赖性而言:
`Game.dll->Renderer.dll`
`Game.dll->SubRenderer.dll`
就是这样 - 任何地方都没有循环依赖。 Game.dll
取决于Renderer.dll
和SubRenderer.dll
,但Renderer.dll
和SubRenderer.dll
完全相互独立。
这是有效的,因为这个Renderer
可以使用SubRenderer
给定其虚拟接口而不确切知道它是什么(因此不需要依赖于具体类型的'子渲染器')。
您可以将Renderer.h
置于可通过公共包含路径(例如:SDK
目录内)的所有三个项目集中访问的位置。没有必要复制它。