【问题标题】:Passing platform-specific data in a platform independent design?在独立于平台的设计中传递特定于平台的数据?
【发布时间】:2010-01-25 15:00:11
【问题描述】:

我有一个用 C++ 编写的游戏引擎设计,其中独立于平台的游戏对象包含在特定于平台的应用程序对象中。

我要解决的问题是我需要将特定于操作系统的数据从应用程序传递到游戏。在这种情况下,我需要将用于 DirectX 的 Windows 的主要 HWND 或用于其他平台的 OpenGL 上下文传递给我正在使用的渲染器。不幸的是,我对渲染器几乎没有控制权,它可以期待特定于平台的数据。

我意识到我可以在应用程序端初始化渲染器,但我宁愿让游戏决定何时何地执行此操作。一般来说,我可以控制应用程序端,但不能控制游戏端。游戏编写者可能会选择使用不同的渲染器。

我也有过使用某种“属性管理器”的想法,我可以在其中通过字符串传递数据,但我不太喜欢这个想法。

有什么想法吗?

【问题讨论】:

  • 如果游戏对象是平台无关的,那为什么还要传递平台依赖变量呢?
  • 好问题!长话短说,我们可以控制 Application 类,但不能控制渲染器或游戏类。这只是我们目前正在探索的东西。

标签: c++ design-patterns cross-platform platform


【解决方案1】:

请记住,您只需要在编译时知道目标平台。有了这些信息,您就可以为正确的平台“换入和换出”组件。

在一个好的设计中,游戏应该需要任何关于其平台的信息;它应该只包含逻辑和相关组件。

您的“引擎”类应该担心平台。

游戏类只能通过非特定于平台的公共函数与引擎对象交互;您可以为每个平台拥有多个版本的引擎对象,并在编译时选择使用哪一个。

例如,您可以有一个 Texture 'engine' 类来表示游戏中的纹理。如果您支持 OS X 和 Windows,您可以有一个“Texture.h”,其中包括“Windows/Texture.h”或“OSX/Texture.h”,具体取决于您正在编译的平台。两个标头都将定义一个具有相同接口的 Texture 类(即它们都具有相同的公共函数和相同的参数),但它们的实现将是特定于平台的。

为了澄清,游戏应该告诉应用程序初始化渲染器;游戏逻辑和实现细节之间应该有严格的界限。渲染器是一个实现细节,而不是游戏逻辑的一部分。游戏类应该对系统一无所知,只对游戏世界一无所知。

【讨论】:

    【解决方案2】:

    一个通过的 SystemContext 类怎么样?你会有一个 Win32Context、LinuxContext 等。这就是 OGRE 处理它的方式(在这种情况下是 RenderContext)。

    Renderer 类采用 SystemContext 指针。

    在内部,DirectXRenderer(Renderer 的后代)dynamic_casts(一次)指向 Win32Context 的指针并从中挑选出所有与平台相关的数据。

    【讨论】:

      【解决方案3】:

      查看模板模式(使用抽象基类和可在派生类中配置的纯虚函数)。

      http://en.wikipedia.org/wiki/Template_pattern

      如果您更喜欢可控性更强(面向对象更少)的方式,游戏部分应在应用部分调用可配置的回调函数,以执行特定于平台的配置。

      例如:

      // in Application:
      static void SetWindowHandle(GameEngine const& p_game_engine, void* p_callback_data)
      {
        p_game_engine.DoSomethingWithHandle(static_cast<ApplicationManager*>(p_callback_data)->GetHWND());
      }
      
      void Initialize() {
        this->m_game_engine.Initialize(this, &Application::SetWindowHandle);
      }
      
      // ...
      // in Game Engine:
      // ...
      
      typedef void (*TSetWindowsHandleCallback)(GameEngine const*, void*);
      
      void* m_application_data;
      TSetWindowsHandleCallback m_windows_handle_callback;
      
      void Initialize(void *p_application_data, TSetWindowsHandleCallback p_callback)
      {
        this->m_application_data = p_application_data;
        this->m_windows_handle_callback = p_callback;
      }
      
      void SetWindowsHandle()
      {
        this->m_windows_handle_callback(*this, m_application_data);
      }
      

      【讨论】:

      • +1,但我更喜欢 dauphic 的方法,因为使用派生类可以一次性封装多个特定于平台的行为。实际上,基 1234562 中的每个公共方法 == “一个可配置的回调函数”。
      【解决方案4】:

      我喜欢做的是让所有实现与公共数据成员共享一个基类。然后我有一个本地类,其中包含基类本身包含的平台特定信息。这需要特定的目录结构。例如你有:

      code
        renderer
          context.h
        platforms
          win32
            renderer
              context_native.h
          osx
            renderer
              context_native.h
      
      code/renderer/context.h
      class RenderContextBase { /* shared members */ };
      #include "renderer/context_native.h"
      
      code/platform/win32/renderer/context_native.h
      class RenderContext : public RenderContextBase { /*win32 specific */ };
      
      code/platform/osx/renderer/context_native.h
      class RenderContext : public RenderContextBase { /*osx specific */ };
      

      使用您的编译器“附加包含目录”,您只需根据平台添加适当的目录。例如,在 win32 上,您添加“code/platform/win32”作为附加目录。当包含 renderer/renderer_native.h 时,它不会在“默认”位置找到,并会尝试使用附加目录。

      在代码中的任何地方,RenderContext 都是本机实现。您不需要指向基类和新的本地类的指针,因为您确实有 1 个实现。当您真正拥有给定平台的 1 个实现时,这可以避免使用基本虚函数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-03-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-25
        相关资源
        最近更新 更多