Front Page
Tutorials
Spotifind
Downloads
PicoDrive
Unlocking

Smartphone Game Programming

Welcome to the fourth gamesforsmartphones tutorial. In this tutorial we're going to draw bitmaps (finally), and we'll also see how to read the status of the keypad, so we can interact with our new program.

First, however, we're going to fix the bug that remains from our previous tutorial. If you look at the screenshot at the end of tutorial 3, you can see a white box has been superimposed on our otherwise-beautiful graphic. This is because the code that prints "Hello Smartphone" is still firing.

We don't want to remove this code entirely though getting rid of the "Hello Smartphone" message might be no bad thing. If we remove the code entirely, then the main window won't even be drawn as a big white rectangle, and therefore we'll be able to see "through" the window onto whatever was running before. Try it and see, if you like, or take my word for it. What we really need to do is make sure we only draw the Hello Smartphone text if the game window isn't open. That way, the user interface you get on startup will still look the same, but the user interface won't show through when we start drawing things via GAPI.

So, we'll start where we left off last time. You can download the starting source code for this tutorial using this link

. Start up EVC4 and load up the project. You need to create a new boolean global variable which we'll use to track whether we've opened the GAPI display yet. Declare the new global variable in the same way as before:

bool GameDisplayOpen = false;

Notice that we're setting the variable to start as false. Now find the code that opens the display:

if ( ! GXOpenDisplay(MainWindow, 1))

{ MessageBox(MainWindow, L"Cannot open display", L"fatal error...",MB_OK);

return 0;}

and set the global variable to be true, after the display has been opened: if ( ! GXOpenDisplay(MainWindow, 1))

{ MessageBox(MainWindow, L"Cannot open display", L"fatal error...",MB_OK);

return 0;}

GameDisplayOpen = true;

Do the opposite with the GXCloseDisplay code:

GXCloseDisplay();

GameDisplayOpen = false;

Finally, find the code that draws the main user interface:

case WM_PAINT:

and change it so it only runs if GameDisplayOpen is false: case WM_PAINT:

{

if ( ! GameDisplayOpen)

{ HDC hdc;

PAINTSTRUCT ps;

RECT rect;

hdc = BeginPaint(hwnd, &ps);

GetClientRect(hwnd, &rect);

DrawText(hdc, g_szMessage, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

EndPaint (hwnd, &ps);

}

}

break;

If you now build and run the code on the emulator, you'll find the bug is now fixed. Hooray! Of course, if you get the old "Cannot execute program" error instead, then you've probably forgotten to copy GX.DLL across to the emulator - see the other tutorials for a refresher on how and why.

To start with, we'll strip out some of the junk we've got lying around from previous tutorials. Find the

while (VerticalPixel < 220)

code, and rip out the entire clause - that's right up to the line before

void* MemPtr = GXBeginDraw();

- also get rid of

case IDTEST:

... MessageBox(hwnd,ScreenMessage,L"...",MB_OK);

break;

and use the Resource Viewer to get rid of the "Test" menu item - we don't need it any more. Compile your code to check it still works.

Bitmaps!

- and not a moment before time. FIrst, catch your bitmap. By that I mean you need to fire up the paint program of your choice, create a bitmap and save it. You can use any paint program you like, but be warned, whenever I use Adobe Photoshop to create my bitmaps, they load OK into EVC, the code compiles fine... but the bitmaps just don't appear. I get round this by loading and resaving the bitmap in Microsoft Paint.

For this tutorial we're going to use a picture of a duck (all will become clear...) - make sure it's 16 pixels square if you want to make following the tutorial easy, and save it as a 24-bit BMP bitmap. If you can't be bothered to draw a bitmap yourself, you can use this one:

... well, I never said I'd teach you graphics...!

Once you've got your bitmap, you must add it to your project. Go to the Resource Viewer, right-click on "tutorial3 resources", and choose "Import". Change "files of type" to "All Files", then browse to your duck.bmp file and open it. EVC will respond with "The bitmap has been imported correctly, however because it contains more than 256 colors it cannot be loaded in the bitmap editor", which is fair enough - the bitmap editor in EVC is a simple affair and not well-designed for True Colour operations!

Click OK on that dialog, and notice that your bitmap has been given a default identifier name of "IDB_BITMAP1". Let's change this to something more meaningful - right-click on "IDB_BITMAP1" and choose properties; change the ID to "IDB_DUCK".

Before we can use the bitmap, we have to convert it to native format. This means that when we want to draw it, it's already in the format that can be drawn most quickly. This is, of course, a Good Thing. In practice we'll probably want to convert lots of bitmaps to native format, so we'll create a function to do it for us. Copy and paste this into your code before GameThreadProc:

CNativeBitmap* LoadBitmap(int resource)

{ CNativeBitmap* bitmap = NULL;

HBITMAP hBitmap = ::LoadBitmap(g_hInst, MAKEINTRESOURCE(resource));

bitmap = MemoryBuffer.CreateNativeBitmap(hBitmap);

::DeleteObject(hBitmap);

return(bitmap);

}

We're going to handle our native bitmaps using pointers. That means that the native bitmap will be held somewhere in memory, and we're going to refer to that native bitmap by the memory location it starts at. So our function doesn't just return a CNativeBitmap, but a pointer to a CNativeBitmap. That's what the asterisk in "CNativeBitmap*" is for - it means "pointer toCNativeBitmap".

The first thing our function does is create a new CNativeBitmap pointer called bitmap. We use this as our return value from the function. When we call the function, we pass it the name of a resource (IDB_DUCK, for instance), and the second thing our function does is to create an instance of that resource ("LoadBitmap(g_hInst...").

Having done that, we call the function that will convert our resource instance into a Native Bitmap (CreateNativeBitmap), and store the result in the variable "bitmap". We throw away the instance of the resource we created ("DeleteObject(hBitmap)", because we don't need it any more, and it's taking up valuable memory. Then the function returns the pointer to our Native Bitmap, and quits.

Don't worry if that was all a bit confusing - now we've got the functionw e can use it without worrying about it any more.

When we call the function, it'll return a pointer to a native bitmap, and we'll need somewhere to store that pointer. So go and create a new global variable called pDuck (meaning "pointer to Duck"):

CNativeBitmap* pDuck;

Between the definitions of LoadBitmap and GameThreadProc, create a new function called LoadAllBitmaps. Trust me, our coding will be easier if we load all our bitmaps in the same place. The function should look like this:

void LoadAllBitmaps()

{ pDuck = LoadBitmap(IDB_DUCK);

}

Go to the NewGame function, and load the bitmaps before we create the new thread:

LoadAllBitmaps();

hGameThread = CreateThread(NULL, 0, GameThreadProc, NULL, 0, 0);

Now all we have to do is ask for the native bitmap we've created, stored and made referenceable, to be drawn. Find the GXBeginDraw statement, and add the following line of code immediately before it:

MemoryBuffer.BitBlt(80,102,16,16,pDuck);

Run this code, and you should be able to see our duck appear in the middle of the screen! The function simply says, copy our pDuck bitmap into the memory buffer, starting at (top-left coordinates) x=80 and y=102. The bitmap is 16 pixels horizontally by 16 pixels vertically - you must give the function the correct size of the bitmap in question, or you'll find the bitmap isn't drawn properly. The application might even crash. This is a really easy thing to forget when you're copying-and-pasting code to build a game, so watch out. Remember also that position (0,0) is in the TOP-LEFT of your screen.

Before we can start reading keypresses, we need to work around an annoying "feature" of GXOpenInput. At the moment it's not working - if you start our program, let it draw the duck then immediately hit the left soft button, the program quits as if we were still on the front screen. This seems to happen because we're calling GXOpenInput (and GXCloseInput) inside our new thread. If we call them before and after the thread instead, we don't get this problem. Calling GXOpenInput before the thread runs is easy - move the code into our NewGame function instead: void NewGame(HWND hwnd)

{

MainWindow = hwnd;

MemoryBuffer.CreateMemoryBuffer();

LoadAllBitmaps();

if ( ! GXOpenInput())

{ MessageBox(MainWindow, L"Cannot open input", L"fatal error...",MB_OK);

return;}

hGameThread = CreateThread(NULL, 0, GameThreadProc, NULL, 0, 0);

SetThreadPriority(hGameThread, THREAD_PRIORITY_ABOVE_NORMAL);

}

Calling GXCloseInput after the thread has terminated is a bit harder, because nothing actually happens after the thread terminates, so there's nowhere to put our code. We can get around this by sending a message from the game thread to the main application window, telling it that the thread is about to quit. If we then wait a few milliseconds, we can expect that the thread has finished. We could explicitly test whether the thread has shut down, but I can't be bothered! We're not rewriting Unreal Tournament here!

First we need to create an identifier for the message we'll be sending. The identifier only represents an abritary integer, but it will make our code more readable. Go to the File Viewer, expand the Header Files node, and double-click "resource.h". You should now be able to see all the identifiers that EVC has already created for us. Resources 101 to 105 are simple identifiers, while resources 40001 to 40005 are linked to our menu items (and note that EVC hasn't cleared up the IDTEST identifier, even though we deleted the menu item - this doesn't matter but it does show that EVC isn't in full and perfect control of things).

Under the IDB_DUCK line, add:

#define ID_THREADSHUTTINGDOWN 106

- you should also update the _APS_NEXT_RESOURCE_VALUE line to read 107, not 106. This is how EVC tracks which ID number can be assigned next, so it'll try to assign 106 again if you don't tell it you've manually assigned 106. This will make a whole bunch of odd things happen. Save your changes, and click "OK" on any annoying "resource.h file has changed" messages - EVC does get annoyed when you start manually editing resource.h.

Go back to tutorial3.cpp, and replace

GXCloseInput();

with:

SendMessage(MainWindow,WM_COMMAND,ID_THREADSHUTTINGDOWN,NULL);

- this tells EVC to send a COMMAND message to the application's MainWindow (remember, we stored a pointer to the main window in the HWND global variable MainWindow), of type "ID_THREADSHUTTINGDOWN".

Now go to the existing code that captures and processes COMMAND messages for the main window:

case WM_COMMAND:

switch (wp)

{

and add code to handle the ID_THREADSHUTTINGDOWN message: case WM_COMMAND:

switch (wp)

{

case ID_THREADSHUTTINGDOWN:

GXCloseInput();

break;

Now you should find that the left soft button does nothing, while the duck is on-screen, but works normally in the front-end user interface. This means that while the game is running, we have (almost) full control over the keypad, which is of course a Good Thing. There are some things we don't yet control, and some things we can't control, but it's a start.

This is probably a good time to make a cup of tea and be pleased with our efforts so far, but if it's all gone a bit odd and you can't work out why, you can download the source code you should have ended up with by this point, here.

Transparency

Let's fix one small annoyance - we can see the white square round the outside of the duck, when that part of the bitmap should be transparent. Amend your duck.bmp file so that the background is some horrible colour you're not going to use in a million years, for instance the RGB mix ( 200, 100, 100 ). You can use this bitmap:

but if you do, make sure you rename it to duck.bmp not duck2.bmp. There's no need to make any other changes in order to make EVC spot the updated graphic - simply recompile the code and run it, and you should see the duck appear with a nasty mauve background.

All we need to do now is change the bitmap-drawing command so it knows that the horrible mauve shade should be treated as transparent. To do this we need to refer to the mauve colour, and we could be doing that a lot in the future. So create a new global variable called TransparentColour, of type DWORD (DWORD is just a particular kind of integer):

DWORD TransparentColour;

We need to set this variable to the right colour - but we need only do that once. So go to the NewGame code, and add the following line to the top:

void NewGame(HWND hwnd)

{

TransparentColour = MemoryBuffer.GetNativeColor(RGB(200, 100, 200));

Here we've converted the colour from an RGB system into a native format, again so it's quicker to use when we need to use it. All we need to do now is change the BitBlt bitmap-drawing command to the transparent version:

MemoryBuffer.TransparentBltEx(80,102,16,16,pDuck,TransparentColour);

- note this is a very similar function to BitBlt, but adds an extra parameter for the transparent colour. Run the code and you should see transparency working properly.

Reading the keypad

At the moment our "game" funs for five seconds, then quits. In practice, this isn't how most games work - they continue until some event (such as player death). So let's take out the five-second pause, and replace it with a quit button. If we don't press the quit button, our game will continue to run. In GameThreadProc, immediately before the TransparentBltEx statement, add the following while statement:

while (!GetAsyncKeyState(0x30))

{

GetAsyncKeyState reads the state of a key at that moment, and returns true if it's being pressed. 0x30 is the virtual key code for the zero button - see if you can guess what the codes are for the other number buttons! So the while loop will continue while the zero key is not pressed. In other words, pressing zero will quit the loop.

There's one other thing we need to do - allow Windows some processor time! If we steal all the CPU time, then when there's an incoming call, Windows won't be able to respond. So we make sure that the thread goes to sleep occasionally so there's time for Windows to do its own processing. Replace the "Sleep(5000);" command with:

Sleep(25);

}

You can play with the (25) value to see what works for your game. The longer the while loop takes to run, the higher this value will need to be. You can also add code so that the game does something sensible when the phone rings, like shutting down or saving the game, but that's a bit more advanced. Anyway, try out what we've got, and you should be able to see that zero is now the quit key. If we don't press quit, the game will run forever (or until the battery dies...!)

What we've ended up with is effectively animation - we're redrawing the same picture every 30 milliseconds or so. You can't see it's animated because each frame is identical. Now we'll change that.

Simple animation

Declare a new global variable, called DuckHorizontalPosition:

int DuckHorizontalPosition = 80;

Now find the TransparentBltEx call, and replace it with:

MemoryBuffer.TransparentBltEx(DuckHorizontalPosition,102,16,16,pDuck,TransparentColour);

DuckHorizontalPosition--;

if(DuckHorizontalPosition < -15) {DuckHorizontalPosition = 176;}

If you run this code, you can probably see where I'm going with this project...!

Repeat until dead...

We can now add a bunch of things to turn this into a simple game, much of it similar to what we've already done. We obviously need to add a crosshair bitmap, and make it move around the screen in response to the joystick (virtual key codes VK_LEFT, VK_RIGHT, VK_UP and VK_DOWN). When a fire button is pressed, we need to see if the crosshair is over the duck. If it is, we show a "splat!" animation for a bit. We can create a variable to count the score , and we can put in a timer so that if the player doesn't hit the duck within a time limit, the game is over.

Here's the crosshair bitmap, with the same transparency colour:

... here's the splat bitmap:

... and here's the global variables we'll need:

int CrosshairHorizontalPosition = 30;

int CrosshairVerticalPosition = 30;

int PlayerScore = 0;

CNativeBitmap* pCrosshair;

CNativeBitmap* pSplat;

bool DuckIsDead = false;

We must import the new bitmap sin the resource editor, and give them sensible names, like IDB_CROSSHAIR and IDB_SPLAT. We need to add two more lines of code to LoadAllBitmaps, so that IDB_CROSSHAIR is rendered into pCrosshair, and similar for pSplat:

pCrosshair = LoadBitmap(IDB_CROSSHAIR);

pSplat = LoadBitmap(IDB_SPLAT);

In GameThreadProc, we draw the crosshair in the correct position (after the duck is drawn, so the crosshair will appear on top of the duck):

MemoryBuffer.TransparentBltEx(CrosshairHorizontalPosition,CrosshairVerticalPosition,16,16,pCrosshair,TransparentColour);

Here's some code to update the crosshair, based on user input:

if (GetAsyncKeyState(VK_LEFT)) CrosshairHorizontalPosition--;

if (GetAsyncKeyState(VK_RIGHT)) CrosshairHorizontalPosition++;

if (GetAsyncKeyState(VK_UP)) CrosshairVerticalPosition--;

if (GetAsyncKeyState(VK_DOWN)) CrosshairVerticalPosition++;

... and here's some code which fires when the left soft button is pressed. If, when that happens, the crosshair is over the duck, then the score is increased by one, and the duck is marked as being dead.

if ( GetAsyncKeyState(VK_F1)

&& (CrosshairHorizontalPosition+8) // Middle of crosshair

> DuckHorizontalPosition // Left hand side of duck

&& (CrosshairHorizontalPosition+8) < (DuckHorizontalPosition+16) // Right hand side of duck

&& (CrosshairVerticalPosition + 8) > 102 // Top of duck (duck's vertical position is currently fixed); && (CrosshairVerticalPosition + 8) < 118 // Bottom of duck; 102+16 = 110

&& !DuckIsDead // Can't kill a duck that's already dead... ) // This is the left soft button

{

PlayerScore++;

DuckIsDead = true;

}

By making a change to the code that draws the duck, we can make sure that the dead duck is drawn as a SPLAT symbol, instead of a duck:

if (DuckIsDead)

{ MemoryBuffer.TransparentBltEx(DuckHorizontalPosition,102,16,16,pSplat,TransparentColour);

}

else

{ MemoryBuffer.TransparentBltEx(DuckHorizontalPosition,102,16,16,pDuck,TransparentColour);

}

Now we're starting to get something a bit like a game. Let's create a "timer" (of sorts) to recreate the duck. New global variable:

int DuckRespawnTimer = 0;

After the duck's position is updated, we consider respawning it (but only if it's dead at the moment):

if (DuckIsDead)

{ DuckRespawnTimer ++;

if (DuckRespawnTimer == 100) // 100 is utterly arbitrary

{ DuckRespawnTimer = 0;

DuckIsDead = false;

}

}

Here's a function which will print out the final score:

void PrintScore()

{ TCHAR ScoreMessage[200];

wsprintf(ScoreMessage,L"Score is %d",PlayerScore);

MessageBox(MainWindow,ScoreMessage,L"Game over...",MB_OK);

}

- call this from the code that receives the ThreadShuttingDown message, so that it appears after the game screen has been closed.

Don't forget to reset the score to zero in the NewGame function, otherwise points will be accumulated over many games.

We can now create a timer to end the game if no duck is shot within (say) five seconds; here's the global variable:

int TimeSinceLastKill = 0;

Increment TimeSinceLastKill by one every time we go round the main loop in the Game Thread. Set it back to zero every time a duck is killed, and change the WHILE condition so it quits if it's more than (say) 300 game loops since a duck was killed:

while (!GetAsyncKeyState(0x30) && TimeSinceLastKill < 200)

Again, don't forget to reset the TimeSinceLastKill timer when the player starts a new game.

Finally, a trick to get rid of the trails that appear when you move the crosshair. They're left-over bits of the previous frame. If we draw a black background every frame, we'll get rid of this. Under the declaration of MemoryBuffer, declare a new buffer:

CSTGapiBuffer BackdropBuffer;

In NewGame, after we create the memory buffer, create another memory buffer for BackdropBuffer:

BackdropBuffer.CreateMemoryBuffer();

- now all we have to do is copy that (all-black) backdrop buffer to the memory buffer every frame, before we draw anything else:

while (!GetAsyncKeyState(0x30) && TimeSinceLastKill < 200)

{

MemoryBuffer.BitBlt(&BackdropBuffer);

- you should see that the game is now a bit slower (we're drawing many more pixels now) but the trails are gone forever, as are the multi- coloured pixels in the top-left corner of the screen (did you spot them?).

Don't forget, if things went strange you can download the finished source code using the link below.

The End!

And that's it! You've now got everything you need to build simple arcade games. In the next tutorial - whenever that is - we'll look at smartening up the front end, and we might even pop an enter-your-name box on screen. There might even be a mention of how to annoy the hell out of your players by adding sound effects!

Help!

Are you having problems with the above tutorial?

First off, try "getting out of the car, then getting back in again" - which is to say, reboot your PC then try again (yeah, I know...). The eVC development tools (both versions 3 and 4) are buggy and prone to errors like "Platform Manager failed" for no (apparent) reason. Many of these problems can be cleared with the aid of a simple reboot.

Having other problems? Why not email me and let me know what the problem is. I certainly can't guarantee I can help, but if I can, I will. If I can't help with a particular problem, I'll put a description of the problem here. Maybe some kind-hearted soul will mail me an answer or suggestion - if they do, I'll put it up alongside the original question.

Questions and Answers

No questions have yet been submitted on this tutorial.

Source Code

Tutorial 4 Source Code mid-point

Tutorial 4 Source Code to end of project

Front Page
Tutorials
Spotifind
Downloads
PicoDrive
Unlocking
Google
 
Web www.gamesforsmartphones.co.uk
www.modaco.com www.msmobiles.com

all content copyright unless otherwise stated

source code may be used freely for any purpose
binaries must not be posted to any other site or distributed on any medium; however you are allowed to download binaries from this site for personal use only