Ravuya's Articles!

Tips for Writing Portable Games

Views: 11245

Tips for Writing Portable Games


No, not games for your Gameboy. Games that you can port to and from different
computing architectures and operating systems. That is, games that get you
the most market possible, rather than just limiting yourself to one platform.

I'll not go over the reasons to write portable games in this section; the
reasons should be pretty obvious by now. It's really not that hard to make your
games portable in the first place, even if you use proprietary APIs. Just keep
these rules in mind.

10. Think Endians


Some computers, like those based on the PowerPC and SPARC, treat streams of
bytes differently than processors like the Intel IA32. That is... backwards.

For example, the number 0xDEADBEEF is stored on a big-endian (i.e. PowerPC)
computer as 0xDE, 0xAD, 0xBE, and 0xEF. The "big" byte (i.e. 0xDE) is stored
first.

On a little endian machine (i.e. Intel Pentium) it would be stored as 0xEF,
0xBE, 0xAD and 0xDE. The "little" or "least significant" byte is stored
first.

Why is this a problem? Well, imagine trying to read a number backward from a
printed page. If you want to order 123 widgets and end up ordering 321 widgets,
you'll probably end up fired. Or if you're writing a C program and you want to
mask against something like 0xFF000000 -- which byte is actually selected,
the least significant or the most significant*?

To avoid these kinds of difficulties, there are a number of routines available
to the discerning game programmer. From the BSD Sockets/WinSock functions like
htons and the like, to SDL's robust endian functions, to even writing
your own, it is not difficult to keep endianness in mind.

When should you watch out for endian difficulties? More or less any time you
are sharing data (files, packets) between platforms! It is not too hard to
think endian when you are reading raw binary data.

If you are porting code and you see weird models, screwed up colours or
blown out textures, chances are the guy who wrote the original code
neglected to pay attention to endianness. Fix it.

*The compiler actually swizzles the value for you if you were to
hardcode it, so 0xFF000000 should always return the most significant.
However, you should watch out for this particularly if you write a lot of
assembly language code.


9. ANSI is there for a reason


When writing C/C++, try to follow the standards as closely as possible. Older
versions of Visual C and the like have difficulties with proper for-loop
scoping among severe difficulties with their implementation of STL.

When you need to insert a compiler specific hack, DO NOT branch the code.
#ifdef and #ifndef are your friends, use them to separate platform specific
code and make future porting efforts easy and clean.

The closer to the standard you follow, the better hope you have of success
across even extremely rustic compilers on obscure architectures.

8. Wrap your code


When you need to use an OS or API-dependent function, wrap it up in a
function (i.e. drawPixel(x,y,r,g,b) instead of using SDL's internal functions).
This makes it easier for future people to replace the internals with the
API of their choice, without having to go on a huge search-and-replace quest
all throughout your game logic.

Hopefully it'll make your code easier to maintain as well.

This also has the added benefit of offering choices to the end user -- many
popular games offer both a Direct3D and OpenGL renderer, for example. By
producing a standardized set of wrapper functions, you can swap out your
renderer at will for whatever new API comes along, or even give the user
a choice.

7. WinSock is evil


If you use WinSock, the syntax is 98% compatible with BSD sockets. That makes
lots of sense, except in the parts where it doesn't (like closing streams,
etc). As a result, either get really used to #ifdef stuff for WinSock, or
consider using a third-party cross-platform network library like HawkNL in
the first place.

6. Assembly Language


Compilers have gotten a lot smarter lately. Assembly language isn't all that
useful in today's day and age -- even the lowliest desktop computers are now
fast enough for you to convert all that unportable ASM in your programs
to portable C/C++ and not really lose that much performance.

Assembly fans -- sorry, guys. Really cool and fast games are well and good,
but the point of the new age of game development is portability and
most modern C/C++ compilers should be able to produce ASM at least as good
(if not better) than your hand-written optimizations.

You can always write different assembly for each supported architecture and
leave C/C++ for the other ones using a clever #ifdef/#ifndef (there it is again)
structure.

5. Pick your battles


At the outset of your project, you should consider what APIs and technologies
to use. I highly recommend you pick portable ones like OpenGL and SDL to start
with. Of course, if you don't have any experience with them, it may take too
much time to get started with them.

The point is that when you start a project, you have a golden opportunity to
make enormous changes in technology for the benefit of cross-platform support
as well as whatever else you want, without having to tear out wads of code
and take years off your life trying to port it after the fact.

If possible, consider using cross-platform middleware like the OGRE graphics
engine to follow John Hattan's advice of writing games, not engines, and my
advice of writing portable code that everyone will love.

4. Document your guts


Commenting code kind of sucks, I know. You were annoyed in school when people
forced you to do it, and you're annoyed now. However, if you're going through
your code even weeks after you've left it, trying to port it to a new platform,
you are invariably going to run into difficulties.

The rule of thumb I use for commenting is to comment public functions with
a brief discussion of their usage (including registers they may stomp on,
bugs they may die on and assumptions you made during the course of creation)
as well as a listing of the variables it expects and how those are used.

It doesn't have to be long -- just long enough for you to figure out how these
functions all interconnect and work to pull your engine out of the muck. That
way, it is easier to replace non-portable functions with portable ones after
the fact and still have the modularity work properly.

Another recently popular method is to use unit testing. If you are provided
with a working nonportable version of a function while porting, you may wish
to develop tests for it. Comparing these tests to the results of your newly
created portable version of the function may help you to isolate problems
faster than "known issue" comments. In fact, you should be using unit tests
already, all over your game code. Make life easy for yourself!

3. Paths


This is a big one that I notice a lot when porting code. For whatever reason,
Windows NT is not case-sensitive. This means that #include "FILE.H" is the same
as #include "file.h" regardless of what the file is named.

If I had a quarter for every piece of code I've compiled that looked for a file
named SOUND.wav when the actual file was named sound.wav, I'd probably have
some sort of giant yachty thing. With supermodels.

Watch your cases, and be extra careful.

In addition, a lot of Windows users like to use \ for delimiting paths.
I don't know why, because / works just fine for them too. So use it.
Consider writing a wrapper function for mangling filenames; this way you
can easily change the function from platform to platform if you DO run into
a compiler difficulty with weird paths (i.e. classic Mac OS's ':' directory
delimiter).

2. Text files and newlines


For some reason, Windows and classic Mac OS like to use CR. Unix likes to use
just LF. This tends to generate problems for you if you re-use text files
from one platform on another (or allow your users to trade data files,
mod scripts, map files, save games...).

There are a few simple awk scripts to cut out the extra characters when running
on UNIX; invest in a few of these.

Make sure your file reading functions are flexible, allowing for many types of
EOL characters. You may also want to decide upon a 'standard' and write all
your files in this fashion (I use just 'LF').

1. Bad Assumptions


I have seen a lot of Windows code that assumes a directory is writable;
under UNIX this is not always true, and it is not always true under
Windows NT anymore either. Almost everyone has some horror story about moving to
Windows XP and noticing that much of your old software assumes that you have
Administrator rights or relies on some Stone Age DOS call to work properly.

Making bad assumptions about the state of a filesystem, memory layout of
a graphics adaptor, or any kind of direct access is usually a great way to
end yourself up in bus-error or segmentation-fault hell. Whenever you make
an assumption in code about the state of a file, device, or block of memory,
make sure to document it so that future porters can ensure that piece of code
is problem-free before they start looking for bugs in other modules during a
crash.

Think hard about what kinds of things may fail, and slap an assert or a
warning comment on them. For example, even upgrading to Win XP SP2 from
SP1 broke a hell of a lot of code due to some applications assuming they
could still read from deleted blocks of memory.

Play it safe, even if that makes your code slightly slower or uglier.

Conclusion


Writing portable code is something you should be thinking about the
first time you write code, not when you are at porting time. By
properly wrapping API- and OS-dependent functions, being conservative
with your assumptions about the platform, and even removing performance-
critical assembly language code, you too can keep your code going from
platform to platform (and even new versions of your favourite OS) with
minimal trouble.

Links


Mozilla C++ Portability Guide
Write Portable Code
SDL library
Guide to Writing Portable & Efficient C Programs
Gaming With Least-Privileged User Accounts (DirectX specific)
Zen & The Art of Game Porting
Icculus.org
Some scripts for converting textfiles between OSes

Thanks to


Richard "Superpig" Fine for editing and technical support


See other articles!
Bad Game Ideas III, Bad Game Ideas II, Freezer 2 post-mortem

back
Copyright 2003-2008 ravuya. Front page web design originally by Josh Powell.