Cross-Platform Development
Cross-Platform Development
For a time, I pursued the holy grail of game development, cross-platform development, and Wire Whizz was functional on the PC and on the iPhone with no change (that I can recall) to game code. I would add before going on, that maintaining any cross-platform functionality is a potentially painful process and I have had to largely abandon this for Wire Whizz (I’ll cover the reasons why later though).
There are a number of ways to achieve this:
- #if/#else/#endif blocks
- Include paths
- Virtual interfaces
- A Combination of both
I’ll cover the first set, the #if pre-processor lot. This is the last resort to me, although I have heard that many people have done this sort of thing and presumably still do. Maintaining the interface cleanly, reading the code, debugging any issues and maintaining compatibility is a complete nightmare. My advice? Don’t do it! Sure, there are small cases where this makes some sense and indeed with iPhone development, I often throw compile time tests in to detect the simulator (#if TARGET_IPHONE_SIMULATOR).
Include Paths
Include paths can be very flexible - you define an abstract interface for your cross-platform class (i.e. Model3D) and you then use that interface in your applications/games. You include the header but you don’t specify the header’s location. In your build settings you specify the correct path where the headers can be found, your file structure might be:
\generic\ <generic interaces>
\iPhone\Model3D.h
\Win32\Model3D.h
So your additional include paths would specify iPhone or Win32, depending on the device you are building for.
You can extend so that you have a generic interface as follows:
class Model3D
{
private:
void *m_pInternal;
public:
// constructor
Model3D();
// interface
void Draw();
};
You can then add the CPP file to your project that defines the implementation (from the source folder for your target platform). So you might have a iPhone_Model3D.cpp and it may define a structure for the internals of the Model3D class and a copy can be instantiated:
struct iPhoneModel3D
{ // define your iPhone specific model data
};
Model3D::Model3D()
{
m_pInternal = (void *) new iPhoneModel3D();
}
The above keeps your interfaces simple, but you always need to have to cast them from one thing to another. You could keep these casts external, or instead of a void * you could have a real type and define the type in a header, and use include paths to pull in the right types. You can also be a bit naughty and use include paths and add an include to pull in the object’s inside. It’s not as nice in my opinion.
Of course the above results in a second level of indirection, but that is usually acceptable overhead unless the object is tiny and often used, i.e. a vector class.
Virtual Interfaces
Virtual interfaces do a similar thing, you define an abstract class for the interface (i.e. Model3D) and you then derive from this to create a type for the platform you are working on (i.e. iPhoneModel3D). Some or all the methods in your base type will be abstract and you then implement these in your derived type.
This is great, but you then need a factory method to create the class(es) and this should be implemented in your platform’s cpp.
Again there is an overhead to this, but this cannot be helped and is typically minimal. The more effort you employ to create cross-platform code, the more complex the system may become. There are other methods you can employ but most involve a v-table/indirection or more untidyness.
My advice would be to avoid doing this for your first application. I did all of this for Wire Whizz, but it became hard to keep up with it all as s I am the only one working on this at present. I have however continued to insulate the Application from direct OpenGL calls. Doing this allows me to check state before applying a call to the API, but I think there’s some tidy up I’ll be pursuing when I get time and not before Wire Whizz’s release!