![]()
|
|
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;
if ( ! GXOpenDisplay(MainWindow, 1))
{ MessageBox(MainWindow, L"Cannot open display", L"fatal error...",MB_OK);
return 0;}
{ MessageBox(MainWindow, L"Cannot open display", L"fatal error...",MB_OK);
return 0;}
GameDisplayOpen = true;
GXCloseDisplay();
GameDisplayOpen = 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;
To start with, we'll strip out some of the junk we've got lying around from previous tutorials. Find the
while (VerticalPixel < 220)
void* MemPtr = GXBeginDraw();
case IDTEST:
...
MessageBox(hwnd,ScreenMessage,L"...",MB_OK);
break;
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);
}
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;
void LoadAllBitmaps()
{ pDuck = LoadBitmap(IDB_DUCK);
}
LoadAllBitmaps();
hGameThread = CreateThread(NULL, 0, GameThreadProc, NULL, 0, 0);
MemoryBuffer.BitBlt(80,102,16,16,pDuck);
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);
}
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
Go back to tutorial3.cpp, and replace
GXCloseInput();
SendMessage(MainWindow,WM_COMMAND,ID_THREADSHUTTINGDOWN,NULL);
Now go to the existing code that captures and processes COMMAND messages for the main window:
case WM_COMMAND:
switch (wp)
{
switch (wp)
{
case ID_THREADSHUTTINGDOWN:
GXCloseInput();
break;
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;
void NewGame(HWND hwnd)
{
TransparentColour = MemoryBuffer.GetNativeColor(RGB(200, 100, 200));
MemoryBuffer.TransparentBltEx(80,102,16,16,pDuck,TransparentColour);
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))
{
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);
}
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;
MemoryBuffer.TransparentBltEx(DuckHorizontalPosition,102,16,16,pDuck,TransparentColour);
DuckHorizontalPosition--;
if(DuckHorizontalPosition < -15) {DuckHorizontalPosition = 176;}
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;
pCrosshair = LoadBitmap(IDB_CROSSHAIR);
pSplat = LoadBitmap(IDB_SPLAT);
MemoryBuffer.TransparentBltEx(CrosshairHorizontalPosition,CrosshairVerticalPosition,16,16,pCrosshair,TransparentColour);
if (GetAsyncKeyState(VK_LEFT)) CrosshairHorizontalPosition--;
if (GetAsyncKeyState(VK_RIGHT)) CrosshairHorizontalPosition++;
if (GetAsyncKeyState(VK_UP)) CrosshairVerticalPosition--;
if (GetAsyncKeyState(VK_DOWN)) CrosshairVerticalPosition++;
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;
}
if (DuckIsDead)
{ MemoryBuffer.TransparentBltEx(DuckHorizontalPosition,102,16,16,pSplat,TransparentColour);
}
else
{ MemoryBuffer.TransparentBltEx(DuckHorizontalPosition,102,16,16,pDuck,TransparentColour);
}
int DuckRespawnTimer = 0;
if (DuckIsDead)
{ DuckRespawnTimer ++;
if (DuckRespawnTimer == 100) // 100 is utterly arbitrary
{ DuckRespawnTimer = 0;
DuckIsDead = false;
}
}
void PrintScore()
{ TCHAR ScoreMessage[200];
wsprintf(ScoreMessage,L"Score is %d",PlayerScore);
MessageBox(MainWindow,ScoreMessage,L"Game over...",MB_OK);
}
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;
while (!GetAsyncKeyState(0x30) && TimeSinceLastKill < 200)
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;
BackdropBuffer.CreateMemoryBuffer();
while (!GetAsyncKeyState(0x30) && TimeSinceLastKill < 200)
{
MemoryBuffer.BitBlt(&BackdropBuffer);
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
all content copyright unless otherwise stated source code may be used freely for any purpose |