开发者

Passing platform-specific data in a platform independent design?

I have a game engine design written in C++ where a platform-independent game object is contained within a platform-specific Application object.

The problem I'm trying to solve is the case where I need to pass OS-specific data from the Application to the game. In this case, I'd need to pass the main HWND from Windows for DirectX or an OpenGL context for the other platforms to the renderer I'm using. Unfortunately I have little control o开发者_开发百科ver the renderer, which can expect platform-specific data.

I realize I could initialize the renderer on the Application side, but I'd rather have the game decide when and where to do it. Generally, I have control over the Application side but not the game side. The game writer might choose to use a different renderer.

I've also entertained the idea of having some kind of "Property Manager" where I can pass data around through strings, but I don't like that idea very much.

Any ideas?


Remember that you only need to know the target platform at compile time. With this information, you can 'swap in and out' components for the correct platform.

In a good design, the Game should not require any information about it's platform; it should only hold the logic and related components.

Your 'Engine' classes should worry about the platform.

The Game classes should only interface with the Engine objects via public functions that aren't specific to the platform; you can have multiple versions of the Engine objects for each platform, and choose which one to use at compile time.

For example, you could have a Texture 'engine' class that represents a texture in the game. If you support OS X and Windows, you could have a "Texture.h" which includes "Windows/Texture.h" or "OSX/Texture.h" depending on the platform you're compiling on. Both headers will define a Texture class with the same interface (i.e. they'll both have the same public functions with the same arguments), but their implementation will be platform-specific.

To clarify, the Game should tell the Application to initialize the Renderer; there should be a strict line between the game logic and the implementation details. The renderer is an implementation detail, not part of the game logic. The game classes should know nothing about the system and only about the game world.


How about a SystemContext class that gets passed? You'd have a Win32Context, LinuxContext etc. This the way that OGRE handles it (RenderContext in it's case).

Renderer class takes a SystemContext pointer.

Internally, a DirectXRenderer (descendant of Renderer) dynamic_casts (once) the pointer to a Win32Context and picks out all platform dependent data from it.


See the Template Pattern (use an abstract base class with pure virtual functions that can be configured in a derived class).

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

If you prefer a more controllable (and less object-oriented) way, the Game part should call a configurable callback function in the Application part, to perform the platform-specific configurations.

E.g.:

// 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);
}


What I like to do is to have a base class shared by all implementation with common data members. Then I have a native class with platform specific information included by the base class itself. This required a particular directory structure. For example you have:

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 */ };

Using your compiler "Additional include directories" you simply add the proper directory depending on the platform. For example, on win32, you add "code/platform/win32" as an additional directory. When including renderer/renderer_native.h, it won't be found in the "default" location and will try to use the additional directory.

From anywhere in the code RenderContext is the native implementation. You don't need to have a pointer to the base class and new the native class since you really have 1 implementation. This avoids having base virtual functions when you really have 1 implementation of a given platform.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜