How to write a cross platform game engine

Started by
8 comments, last by Chad Seibert 16 years, 8 months ago
Hello Community, For the last few years, I've been learning how to write game code in various API's on different platforms. I've been writing simple demo apps demonstrating various concepts of the API, with extensibility in mind. For example, I abstracted a render class, window class, input class, etc. and all this code could be simply replaced with something else with minimal effort. Then I started using inheritance and wrote simple base classes with the main one inheriting from them. Then, when I wrote a function or class that needed a renderer, I could simply use IRenderBase and all inherited classes could be plugged in. I could simply use a factory to create the renderer and could even switch between OpenGl and D3D9 in real time. But now I'm curious: can entire operating system api be abstracted away this way? Could I create a IOperatingSystemBase, with a function EmitDebugMessage, which would indirectly call OutputDebugMessage for win32 apis, or printk(not that I would recommend it[smile]) for linux based ones? If I wanted to implement multithreading into it, I could. Otherwise using Boost wouldn't be any more difficult. It all sounds fine in theory, however, am I missing something painfully obvious? Thanks for any advice[smile], Chad Seibert
Advertisement
You can just use cross-platform APIs and then you're done before you start. OpenGL is cross-platform (DirectX is not). OpenAL, PhysX, SDL, pthreads and many other fundamental libraries are all cross-platform. So just make smart choices about what APIs you use and your app will be fine.

-me
PhysX isn't truly cross-platform when talking about PCs. It only officially runs on Windows I believe (though, OTEE did port it to Mac OS X for their own use).
SDL does a lot of the OS abstraction for you already, so if you want to use that, the majority of your work is already done for you (including threading, CDROM access, CDROM audio, USB joysticks, and a bunch of other stuff).

It doesn't handle stuff like dialogue boxes, etc, so I have a few classes with #ifdefs that provide the OS-specific methods for various OS stuff. You could definitely do it with polymorphism.
Abstracting the operating system is certainly possible.
With your renderer you used a run-time polymorphic base class IRenderBase and the implementation was plugged in at run-time. However with an operating system using a run-time polymorphic base class is unnecesary overhead, you cannot change OS at run-time and just plug-in a different IOperatingSystem, in-fact chances are your entire program will need re-compiling for the new platform. For this reason its probably better to use compile-time polymorphism.
There are a few ways to do this, but a good way to do this is something like:

#if defined(WINDOWS_PLATFORM)  typedef CWindowsPlatform OSPlatform;#elif defined(OSX_PLATFORM)  typedef COSXPlatform OSPlatform;#elif defined(LINUX_PLATFORM)  typedef CLinuxPlatform OSPlatform;#else# error Operating system could not be identified.#endif// Instantiate the platformOSPlatform system_platform;


Then just use system_platform everywhere
e.g.
system_platform.outputDebugMessage(DEBUG_CONSOLE_NOTIFY, "Blood sucking monster created.");
system_platform.outputDebugMessage(DEBUG_FATAL_ERROR, "@ CD3D9_Renderer::createRenderTarget");
dmatter is right; this is pretty much how I do things (not so much for the virtual call overhead, but because there's no reason to switch them out at runtime).
I'd build an engine on top of ClanLib, if you can. Much like SDL, but it's a bit higher level and a little more of a full featured SDK (has built in threads, networking, graphics, GUI, and more).
"Creativity requires you to murder your children." - Chris Crawford
You can:
1) use cross platform libraries such as SDL
2) Abstract parts of your game engine that are OS specific.
3) Use a modular structure for your game engine.
e.g. my renderer is OpenGL, but its abstracted. Its a class whose methods receive meshes to be rendered. As such, if OpenGL support drops for a particular platform I could write a renderer for whatever graphics library works on that platform.
My input system works likewise.

This also has other benefits. My current knowledge of rendering is primitive. If I learn something new, I already have a game engine which I can test it on.
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
Quote:Original post by Ravuya
dmatter is right; this is pretty much how I do things (not so much for the virtual call overhead, but because there's no reason to switch them out at runtime).

Assuming you are not using cross platform libraries, and are rolling your own cross platform engine.

I would extend this one step further. For small code chunks a couple of lines or so in some 'mostly' platform independent function, it makes perfect sense to use #defines. In the case where the API is mostly platform dependant you would make an awful mess and create very large source files by scattering #defines right through the source file. For the small cost of some initial workspace set up you can create platform specific source files. Ideal candidates for these are Rendering, Input, Threading, Sound and FileIO

Rendering example.

rendering.h - Contains the API declarations
rendering_multi.cpp - multi platform implementation (empty stubs) will compile on any platform but when called return doing nothing
rendering_pc.cpp - PC specific implementation of the API
rendering_mac.cpp - Macintosh specific implementation of the API
rendering_linux.cpp - Nix specific implementation of the API

It is most likely that you have a specific workspace set up for each platform, as each platforms build will need to be compiled separate from each other. Simply add the correct platform source file to your workspace for that platform. Ignore or exclude all the others. One of the extreme bonuses with this cross platform structure is that you can add a new platform quickly and have it compiling with very little work by linking to the _multi.cpp files for new platforms. It is then only a matter of filling in the stubs and renaming to the new platform.
Quote:Original post by dmatter
Abstracting the operating system is certainly possible.
With your renderer you used a run-time polymorphic base class IRenderBase and the implementation was plugged in at run-time. However with an operating system using a run-time polymorphic base class is unnecesary overhead, you cannot change OS at run-time and just plug-in a different IOperatingSystem, in-fact chances are your entire program will need re-compiling for the new platform. For this reason its probably better to use compile-time polymorphism.
There are a few ways to do this, but a good way to do this is something like:


I completely agree, there's no need for runtime polymorphism. I'll use it for the render classes, though.

Thanks for the great advice, I hope it works [smile].
Chad Seibert

This topic is closed to new replies.

Advertisement