flipCode - Tech File - Peter White [an error occurred while processing this directive]
Peter White
Click the name for some bio info

E-Mail: po.white2@ukonline.co.uk



   06/30/1999, No Subject


Yawn. Exams are so tedious. Anyway, since they're nearly finished I'll update my tech file. Platform independent code seems all the rage with Linux (and other operating systems) gaining popularity. The key to writing code that will work on many platforms with few changes seems to be to use universal technologies such as OpenGL and BSD sockets (rather than DirectPlay). However, OpenGL support isn't brilliant on Windows yet, with 3dfx still providing beta drivers. Even with OpenGL there's quite a bit of platform-specific code to set the whole thing up.

To get around all of these problems in my Asteroids clone (or 'real-time 2dish space battle simulator' as I like to think of it) I used some of the nice features of C++. I tucked all of the platform specific code in a directory, which was clearly marked 'platform specific' and hid it all in a class somewhere. You then create a standard 'interface' to this platform specific code via an abstract base class. For controlling the graphics hardware I created a base display class, which looked a bit like:

class Display
{ 
public:
    virtual void DrawTriangle( const Triangle& nTriangle ) = 0;
    virtual void DrawText( char *text ) = 0;
    // etc, etc.
};
Now you just need to derrive that class for all of the display hardware you want to support, for example:

class OpenGL : public Display
{ 
public:
    void DrawTriangle( const Triangle& nTriangle )
    {
        glBegin( GL_TRIANGLES );
        glVertex2f( nTriangle.point[0].x, nTriangle.point[0].y );
        glVertex2f( nTriangle.point[1].x, nTriangle.point[1].y );
        glVertex2f( nTriangle.point[2].x, nTriangle.point[2].y );
        glEnd();
    }
    // etc, etc.
};
Now, right at the start of the program you can choose OpenGL, Direct3d, Glide, or whatever technology you've derrived a class for. I have a global variable, which is a pointer to Display. Anything that needs to draw to the screen does it through the global display object. I have a base class for the 'system', which contains member functions for accessing system timers and stuff like that. In fact, anything that might need to be written differently on a different platform is put into a class like the Display class.

That's the relatively easy stuff out of the way... Now, I can move on to the real meat of the update. I was playing around in BeOS a couple of days ago and was very impressed with the 'translators'. Basically, BeOS supports a sort of operating system plug-in that translates media files (like image files) into something that's easy to deal with. You could load up any image file (pcx, jpg, gif, png, etc) that there was a translator for very very easily. You could also save something like a screen dump to any image file supported. The reason that I was excited about this was because I realized that I could use this feature for loading up the textures in the game. That would mean that BeOS users would be able to add textures (new skins) in any image file format they wanted and, under BeOS, the game would be able to load them up. The immediate response is to create an abstract version of the texture class, and write a special BeOS version. However, what if you already use the texture class throughout the program (like I had)? It becomes unfeasible to check your program for all of the 'new Texture' lines and change them to something like 'new BeOSTexture'.

I finally got around the problem by only instantiating classes via the system class. You have a base system class, which has a member function for creating objects for you:

class BaseSystem
{ 
public:
    void *CreateObject( int nType );
};
This would let your get a texture object by doing something like:

Texture *myTexture = (Texture*)gSystem->CreateObject( OBJ_TEXTURE );
(gSystem is a global pointer to the system class actually being used by the game.)

Since it would be very tedious to handle every single class in a given system's system class, the base system class handles the creation of default instances of each type. In the previous example, BaseSystem::CreateObject() would return a pointer to a new texture class. The BeOS system object would return a pointer to a special BeOS texture object. The code for the BeOS CreateObject() member function would look a little like:

void *BeOSSystem::CreateObject( int nType )
{ 
    switch( nType )
    {
    case OBJ_TEXTURE:
        return new BeOSTexture;
  
    default:
        return BaseSystem::CreateObject(nType);
    }
} 
With that architecture it's relatively easy to modify a given platform's code to allow it to support some additional operating system feature. Since the switch statement is going to be fairly slow you could just provide an inline member function called 'CreateTexture()', another one called 'CreateCDPlayer()' for the cd player. An inline member function for every class that can be instantiated via the system class.

It's unfeasible to do *every* class used in your engine like this. The performance penalty will be really noticeable in inner loops, but it's probably a good idea to do it for every class that access the world outside of your game in some way. Anything that handles file formats, graphics hardware, sound is a good candidate for this technique. It's probably better to play it safe just in case you find some operating system which can vastly improve your program in some way.

Of course, you could always just target Windows.

Peter White





  • 06/30/1999 - No Subject
  • 06/10/1999 - Introduction

  • This document may not be reproduced in any way without explicit permission from the author and flipCode. All Rights Reserved. Best viewed at a high resolution. The views expressed in this document are the views of the author and NOT neccesarily of anyone else associated with flipCode.

    [an error occurred while processing this directive]