Simultaneous Cross-Platform Game Development
Contents |
|
Publishing game titles for multiple platforms is no new idea, but it is usually the case that the title is developed for a single operating system and the source code is subsequently ported to additional platforms. Ports can be costly, time-consuming, and downright ugly, however, if the original code is heavily dependent on interfaces or data types which are not present on the platform targeted by the port. The only alternative is to develop for multiple platforms simultaneously from the very beginning of a project. Simultaneous development requires that a system architect have some expertise in each of the platforms for which a title is targeted for release. It is the focus of this article to demonstrate how major subsystems of a game engine can be written to function on both Windows and Macintosh operating systems.
General Design Philosophy
When programming for multiple platforms, it is usually desirable to hide code that is dependent on a particular operating system by using a layered design. Platform-specific code should be safely tucked away at the lowest level possible, and code inhabiting the higher levels should require no knowledge of how the lower levels are implemented. This black box approach should be used to encapsulate major subsystems of a game engine as well as any miscellaneous functions and data types which may be depend on the underlying operating system. Once these lowest layers have been written, an applications programmer should be able to write code that interacts only with the interface that the game engine itself exposes to higher layers. The game engine is then responsible for communicating the right information to and from the operating system.
There appear
to be two popular ways of organizing platform-dependent code. The first is
to have separate Windows and Macintosh versions of a file that implements
a particular subsystem. Ports invariably lead to this arrangement. This
approach, however, usually leads to duplication of a lot of code which is
actually common to both platforms. It also carries the drawback of having
to touch multiple files to make a small change which requires that each
implementation be altered. The examples in this article shall use a second
approach: conditional compilation. We can define two flags, named
WINDOWS
and MACOS
, which when used with
#if
… #elif
… #endif
blocks tell
the preprocessor to either keep a block of code, if the corresponding flag
is defined to be 1, or remove a block of code, if the flag is defined to
be 0.
Consider the following simple data type abstraction example which uses the above scheme.
#if
WINDOWS
typedef HWND WindowReference;
#elif
MACOS
typedef WindowPtr WindowReference;
#endif
This creates
a small layer between the operating system and higher levels of the game
engine which hides the platform-dependence of the
WindowReference
type. Any functions or derivative data types
that need to refer to a window should use the WindowReference
name instead of the name defined by the operating system’s API. This way,
anything that refers to a window does not require separate definitions for
each platform since it does not directly depend on a platform-specific
data type.
This practice of isolating platform dependencies at the lowest possible level will be apparent throughout the code accompanying this article. The remainder of this article will be spent building abstraction layers for 3D graphics, sound, and networking subsystems upon which higher levels can rest without knowledge of what operating system they are running on. The differences between the two platforms will be pointed out, and methods for avoiding common pitfalls will be presented.
__________________________________________________