-
Book Overview & Buying
-
Table Of Contents
Android NDK Game Development Cookbook
By :
The main idea is the possibility of cross-platform development in What You See (on a PC) is What You Get (on a device), when most of the application logic can be developed in a familiar desktop environment like Windows, and it can be built for Android using the NDK whenever necessary.
To perform what we just discussed, we have to implement some sort of abstraction on top of the NDK, POSIX, and Windows API. Such an abstraction should feature at least the following:
App1). A minimalistic Windows GUI application is the one that creates a single window and starts the event loop (see the following example in Win_Min1/main.c):#include <windows.h>
LRESULT CALLBACK MyFunc(HWND h, UINT msg, WPARAM w, LPARAM p)
{
if(msg == WM_DESTROY) { PostQuitMessage(0); }
return DefWindowProc(h, msg, w, p);
}
char WinName[] = "MyWin";int main()
{
OnStart();
const char WinName[] = "MyWin";
WNDCLASS wcl;
memset( &wcl, 0, sizeof( WNDCLASS ) );
wcl.lpszClassName = WinName;
wcl.lpfnWndProc = MyFunc;
wcl.hCursor = LoadCursor( NULL, IDC_ARROW );
if ( !RegisterClass( &wcl ) ) { return 0; }
RECT Rect;
Rect.left = 0;
Rect.top = 0;ImageWidth and ImageHeight constants. However, the WinAPI function CreateWindowA() accepts not the size of the client area, but the size of the window, which includes caption, borders, and other decorations. We need to adjust the window rectangle to set the client area to the desired size through the following code:Rect.right = ImageWidth; Rect.bottom = ImageHeight; DWORD dwStyle = WS_OVERLAPPEDWINDOW; AdjustWindowRect( &Rect, dwStyle, false ); int WinWidth = Rect.right - Rect.left; int WinHeight = Rect.bottom - Rect.top; HWND hWnd = CreateWindowA( WinName, "App3", dwStyle,100, 100, WinWidth, WinHeight,0, NULL, NULL, NULL ); ShowWindow( hWnd, SW_SHOW ); HDC dc = GetDC( hWnd );
hMemDC = CreateCompatibleDC( dc ); hTmpBmp = CreateCompatibleBitmap( dc,ImageWidth, ImageHeight ); memset( &BitmapInfo.bmiHeader, 0,sizeof( BITMAPINFOHEADER ) ); BitmapInfo.bmiHeader.biSize = sizeof( BITMAPINFOHEADER ); BitmapInfo.bmiHeader.biWidth = ImageWidth; BitmapInfo.bmiHeader.biHeight = ImageHeight; BitmapInfo.bmiHeader.biPlanes = 1; BitmapInfo.bmiHeader.biBitCount = 32; BitmapInfo.bmiHeader.biSizeImage = ImageWidth*ImageHeight*4; UpdateWindow( hWnd );
MSG msg;
while ( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
…
}>gcc -o main.exe main.c -lgdi32
To render a framebuffer on the screen, we need to create a so-called device context with an associated bitmap, and add the WM_PAINT event handler to the window function.
To handle the keyboard and mouse events, we add the WM_KEYUP and WM_MOUSEMOVE cases to the switch statement in the previous program. Actual event handling is performed in the externally provided routines OnKeyUp() and OnMouseMove(), which contain our game logic.
The following is the complete source code of the program (some omitted parts, similar to the previous example, are omitted). The functions OnMouseMove(), OnMouseDown(), and OnMouseUp() accept two integer arguments that store the current coordinates of the mouse pointer. The functions OnKeyUp() and OnKeyDown() accept a single argument—the pressed (or released) key code:
#include <windows.h> HDC hMemDC; HBITMAP hTmpBmp; BITMAPINFO BmpInfo;
In the following code, we store our global RGBA framebuffer:
unsigned char* g_FrameBuffer;
We do all OS-independent frame rendering in this callback. We draw a simple XOR pattern (http://lodev.org/cgtutor/xortexture.html) into the framebuffer as follows:
void DrawFrame()
{
int x, y;
for (y = 0 ; y < ImageHeight ; y++)
{
for (x = 0 ; x < ImageWidth ; x++)
{
int Ofs = y * ImageWidth + x;
int c = (x ^ y) & 0xFF;
int RGB = (c<<16) | (c<<8) | (c<<0) | 0xFF000000;
( ( unsigned int* )g_FrameBuffer )[ Ofs ] = RGB;
}
}
}The following code shows the WinAPI window function:
LRESULT CALLBACK MyFunc(HWND h, UINT msg, WPARAM w, LPARAM p)
{
PAINTSTRUCT ps;
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYUP:
OnKeyUp(w);
break;
case WM_KEYDOWN:
OnKeyDown(w);
break;
case WM_LBUTTONDOWN:
SetCapture(h);
OnMouseDown(x, y);
break;
case WM_MOUSEMOVE:
OnMouseMove(x, y);
break;
case WM_LBUTTONUP:
OnMouseUp(x, y);
ReleaseCapture();
break;
case WM_PAINT:
dc = BeginPaint(h, &ps);
DrawFrame(); Transfer the g_FrameBuffer to the bitmap through the following code:
SetDIBits(hMemDC, hTmpBmp, 0, Height,g_FrameBuffer, &BmpInfo, DIB_RGB_COLORS);
SelectObject(hMemDC, hTmpBmp);And copy it to the window surface through the following code:
BitBlt(dc, 0, 0, Width, Height, hMemDC, 0, 0, SRCCOPY);
EndPaint(h, &ps);
break;
}
return DefWindowProc(h, msg, w, p);
}Since our project contains a make file the compilation can be done via a single command:
>make all
Running this program should produce the result as shown in the following screenshot, which shows the Win_Min2 example running on Windows:

The main difference between the Android and Windows implementation of a main loop can be summarized in the following way. In Windows, we are in control of the main loop. We literally declare a loop, which pulls messages from the system, handles input, updates the game state, and render s the frame (marked green in the following figure). Each stage invokes an appropriate callback from our portable game (denoted with blue color in the following figure). On the contrary, the Android part works entirely differently. The main loop is moved away from the native code and lives inside the Java Activity and GLSurfaceView classes. It invokes the JNI callbacks that we implement in our wrapper native library (shown in red). The native wrapper invokes our portable game callbacks. Let's summarize it in the following way:

The rest of the book is centered on this kind of architecture and the game functionality will be implemented inside these portable On...() callbacks.
There is yet another important note. Responding to timer events to create animation can be done on Windows with the SetTimer() call and the WM_TIMER message handler. We get to that in Chapter 2, Porting Common Libraries, when we speak about rigid body physics simulations. However, it is much better to organize a fixed time-step main loop, which is explained later in the book.
Change the font size
Change margin width
Change background colour