views:

108

answers:

4

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 over 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?

+3  A: 

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.

dauphic
+1  A: 

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);
}
e.tadeu
+1, but I prefer dauphic's approach since using a derived class allows you to encapsulate more than one platform-specific behaviour in one go. Effectively, each public method in the base class == "a configurable callback function".
j_random_hacker
+1  A: 

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.

Kornel Kisielewicz
A: 

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.

Francis Boivin