Environment: [eg VC6 SP4, embeddedVC3, winCE 3.0/PocketPC2000/2002
Asteroids is an implementation of the classic game of the same name.
The game was developed out of a curiosity to see just how difficult it
was to develop a graphical application on a PocketPC system.
As it turned out, I found an excellent, GPL licensed, graphics library that allowed
the development of graphical applications using Visual C++ 6 on a
desktop computer. The Pocket Frog library, located at
http://frog.flipcode.com
comes complete with quality source code and documentation.
Asteroids
The Asteroids game is fairly straight-forward the real interest is in the
Pocket Frog library itself. Below is a description of the library by its creator
Thierry Tremblay.
Pocketfrog Functions
PocketFrog also provide you with some functions:
Display* FrogDisplay();
POINT FrogButtonLocation( int button );
void FrogShutdown();
Pixel FrogColor( int red, int green, int blue );
Surface* FrogLoadImage( const TCHAR* szFilename );
Surface* FrogLoadImageResource( unsigned resourceID,
const TCHAR* szClass = RT_BITMAP,
HINSTANCE hModule = 0 );
As with callbacks, continue reading to learn about the different functions
and their usage.
Game Initialization
All the windows (and GAPI) initialization is done for you by PocketFrog. But
before it start initializing everything it needs, PocketFrog will call you to
let you have the chance to configure things. This is done through a callback
called “GameInit”:
void GameInit( FrogConfig& config );
PocketFrog will provide you with a FrogConfig structure filled with default
values. Here is the definition of this structure:
struct FrogConfig { enum Screen { SCREEN_FASTEST, // Select the fastest screen // mode available SCREEN_PORTRAIT, // Select a 240x320 screen SCREEN_LANDSCAPE // Select a 320x240 screen }; LPCTSTR szWindowClass; // Window class name used // for registration LPCTSTR szWindowTitle; // Window title int iconID; // Icon resource ID bool bSplashScreen; // Show the "PocketFrog" // splash screen? Screen screen; // Screen orientation // (default = portrait) bool bGDISupport; // GDI support wanted // for backbuffer? };
The default values provided by PocketFrog have been chosen as to ease the life
of less (or not so less) experienced developpers. For example, GDI support is
disabled by default. If a user doesn’t know how (or doesn’t want to) use GDI,
there is no reason to enable it and incur a speed overhead.
The first three fields deal with Windows specific stuff that you can safely
ignore, although it is nice to have something else then the default icon for
your game. To change the game’s icon, you need to add an icon in the resources
and set the “iconID” field to it’s resource ID. If this doesn’t make sense,
forget about all that. The samples provide examples on how to do this.
Here is a simple example of providing the GameInit() callback, where we want
to enable GDI support:
void GameInit( FrogConfig& config ) { // We want GDI support to output text config.bGDISupport = true; }
Note that you don’t need to do anything if you are happy with the default
values provided by PocketFrog.
The default value for the “screen” is set to SCREEN_PORTRAIT, meaning that a
240×320 pixels screen. If you want a 320×240 screen, change the “screen” value
to SCREEN_LANDSCAPE. The last possible value is “SCREEN_FASTEST” and will
create a back buffer with the same orientation as the display buffer. This is
for more experienced developers, as there is no way to know if a 240×320 or a
320×240 screen will be returned.
Warning: the PocketFrog initialization process has not started yet! This means
that all the API functions are not safe to call (don’t do it!).
Game Start
Once PocketFrog has asked you your preferences through GameInit(), it will
proceed to initialize the display and everything else that needs it. When this
is done, another of your callback will be called to notify you that the game
is starting:
bool GameStart();
This is where the bulk of your own initialization must take place. This is
where you load your bitmaps, this is where you allocate memory and gain access
to all the resources you need. This is the last callback that is called before
that game loop starts.
Since everything has been initialized on the PocketFrog side, you can safely
call API functions such as FrogDisplay() to access the display.
The return value specifies whether initialization was successfull or not. If
this value is false, the game will exit. If it is true, the game will start.
The Game Loop
The internal game loop will make sure that all Windows messages are processed
as needed. Some of these messages will trigger events (example: a button is
pressed, the screen is tapped, and so on). These events translate to calls to
some callback. When there is no event to handle (ie: idle time is available),
a special but very important callback is called:
void GameLoop();
This is where the bulk of your game processing time should be spent. In this
callback you compute physics, do some AI and update the display. This is
where your game code is.
This callback will be called as often as possible, meaning that no timing is
done for you. If you want to limit your frame rate to a fixed number, you have
to do it yourself (at the beginning of this callback).
Ending The Game
The game is running, everything is nice, but one needs to stop the fun at some
point. This is done by calling the FrogShutdown() function. By doing so, you
ask PocketFrog to exit the game loop and free all allocated resources.
Of course, the library can’t free the resource you allocated yourself since it
doesn’t know about them. So just before PocketFrog shuts down itself, it will
call one last callback:
void GameEnd();
In this callback, do all the cleaning that needs to be done. An alternative is
to do nothing and let Windows sort it out! (I never wrote that).
Button Callbacks
The following callbacks are called when a button is pressed (down) or when it
is released (up):
void ButtonDown( int button ); void ButtonUp( int button );
The parameter specifies which button is pressed. See the PocketFrog.h file for
button definitions.
The following function is related to button and is used to get the position of
buttons:
POINT FrogButtonLocation( int button );
The returned POINT structure contain the location (in pixels) of the specified
button. Of course, the coordinates will fall outside the range of the display.
The position of the buttons are taken from GAPI and I am not sure they are
properly set for all devices. In any case, it works well on my Casio E-125.
Stylus Callbacks
The last three callbacks deal with stylus events:
void StylusDown( int x, int y ); void StylusMove( int x, int y ); void StylusUp( int x, int y );
StylusDown() is called when the stylus is put on the screen, the second one
when it is dragged over the screen surface, and the last one when the stylus
is lifted from the screen. In all cases, the x and y parameters report the
location of the stylus on the screen.
Note that PocketFrog will transform the stylus coordinates returned by GAPI to
screen coordinates for you.
Surfaces
A surface is the basic entity used to handle graphics. To create a surface,
you must specify it’s dimensions (width and height) in pixel. For example,
to create a 128×64 pixels surface, you could use:
Surface* pSurface = new Surface( 128, 64 );
class Surface provides different methods to draw on the surface. Suppose you
wanted to clear the above surface with the color blue and then draw a red
rectangle on it. You would use:
pSurface->Clear( FrogColor(0,0,255) ); pSurface->FillRect( Rect(100,10,150,30), FrogColor(255,0,0) );
The “Rect” class is a helper that wraps Window’s own RECT structure. Finally,
the FrogColor() function is used to map the usual r,g,b color components to
a Pixel color.
Direct access to the surface’s memory is possible using the GetPixels() and
GetPitch() methods. Here we are not using the GAPI convention to return the
surface pointer. Instead, the pointer to the beginning of the surface is
returned. This means that there is no need for two “pitch” values, only one
will suffice and the value returned by GetPitch() is the usual “y” pitch.
The Display And The Backbuffer
Access to the display is gained by calling FrogDisplay() which will return a
pointer to the Display object. This in turn can be used to access the back-buffer, which happens to be of class Surface. So if you wanted to clear the screen in green, you would do:
FrogDisplay()->GetBackBuffer()->Clear( FrogColor(0,255,0) ); FrogDisplay->Update();
Note the call to Update(), it is really important. This call will update the
screen (what the user sees) with the content of the backbuffer. It should be
called each time you finish rendering a frame. The concept of Update() is
really similar to the concept of a “flip”.
GDI Support
It is possible to create a surface with GDI support. To do so, simply create
your surface using the GDISurface class instead of Surface:
GDISurface* pGDISurface = new GDISurface( 128, 64 );
Accessing and releasing the surface’s DC is simple:
HDC hdc = pGDISurface->GetDC(); //... do some drawing ... pGDISurface->ReleaseDC( hdc );
Note that each time you call GetDC(), a new DC is created for the surface. This
can have a major impact on your performances, so be carefull when using GDI.
Finally, if you want your backbuffer to have GDI support, you have to ask for
it in the callback GameInit(). The reason to do so is that PocketFrog always
create the backbuffer for you and there is no way to change it.
Sound Support
The sound module design is not yet finish as music and repeating sounds have
not been included yet. For this reason, no documentation is yet available.
For more information about sound support, look at the “Sound” sample.
Hints And A Few Pointers
In this section I will provide advice to get the most out of PocketFrog.
- Always use surfaces that have multiple-of-8 dimensions (both width and
height) for optimal performances. Lot of primitives are (or will be)
optimized for multiple-of-8 surfaces. Some of the optimizations that are
possibles are multiple pixels read/write instructions and loop unrolling. - Some PocketPC are faster when using a 240×320 screen, and others are
faster with a 320×240 display. To get the best performance possible, you
should select “SCREEN_FASTEST” as the screen orientation in GameInit().
Unfortunately, doing so means that you have to handle screen orientation
yourself. - GDI support on the backbuffer might be nice, but it may be doing more
harm then goods as you frame rate will drop considerably, even more on
devices that already provide buffering such as the Casio E-125. If you
really want to use GDI for fonts and text rendering, my advice is to
create a GDISurface to do the GDI rendering, and the blit this surface
to the backbuffer. Of course, the backbuffer should not have GDI support
enabled.