Introduction to Windows Game Programming
Terms, Concepts, Functions
by PhilVaz
COORDINATE SYSTEMS (2D and 3D)
MATH CONCEPTS (Vectors, Formulas)
GRAPHICS CONCEPTS (GDI, DirectDraw, OpenGL)
- GameInit (function called only once at very beginning)
- set up screen display and resolution (GDI, DirectDraw, OpenGL, or other)
- initialize and load all game objects, sprites (if 2D), and/or models or world geometry (if 3D)
- initialize and load all sounds and sound buffers
- initialize joystick (if applicable)
- initialize or clear any game variables (game_score, game_level, arrays, pointers, etc)
- GameMain (function called repeatedly during main game loop)
- keep track of game_state (menu, demo init/run, game init/run, or pause)
- a typical simple main game loop during "game run" state would include:
- move all enemies (perform A.I.) and enemy bullets
- move all particles for explosions, etc
- get and process input (keyboard, mouse, or joystick)
- move player and player bullets and/or position camera (if 3D)
- display score or other 2D text or 2D HUD (if applicable)
- check all potential collisions with player, bullets, or game objects
- when everything moved render one scene or draw one game "frame"
- change game_state if reached next level, or "game over", or pause, etc
- the GameMain loop must be called once every game frame and kept at a constant frame rate (30 to 60 FPS if possible) no matter the computer speed for smooth and consistent animation
- GameQuit (function called only once at very end)
- release or delete all game objects or resources (if necessary)
- release or delete all pointers (device context for GDI, rendering context for OpenGL)
- release or delete all sound buffers
- release or delete joystick (if applicable)
- return display to original resolution and settings
A more advanced game architectural design might include the following: Windows Message handling, Game Input, Logic, Networking, Output, Graphics, Sound, Physics, Resources and Databases, and more
see GameLoop1.cpp for where GameInit, GameMain, GameQuit functions go in a Win32 game loop
- WinMain
- where Win32 C / C++ programs start -- this is main( ) in DOS / console programs
- calls the functions GameInit (once), GameMain (repeatedly 30 to 60 FPS), and GameQuit (once)
- sample code: (notice where GameInit, GameMain, and GameQuit go)
int WINAPI WinMain( .... )
{ // fill in the window class structure here
// save the game instance handle
game_instance = hinstance;
// register the window class
RegisterClassEx( ..... );
// create the window
CreateWindowEx( ..... );
// save the game window handle
game_window = hwnd;GameInit( ); // game initialization function
// enter main event loop using PeekMessage() to retrieve messages
while(TRUE)
{
PeekMessage( ..... ); // check for Windows messages
TranslateMessage ( ... );
DispatchMessage( ... );GameMain( ); // game main processing function
} // end while
GameQuit( ); // game quit function and clean up before exit// return to Windows
} // end WinMain
see GameLoop1.cpp for a sample WinMain and WinProc
- WinProc
- processes Windows "messages" and checked using PeekMessage( ) every game frame
- checks for quit or exit (triggered by ESC key) to destroy the game window
- also can read the mouse or keyboard if you are using Win32 (slower) rather than DirectInput (faster)
- sample code:
LRESULT CALLBACK WinProc( ..... )
{ // main message handler of the system
switch(msg) // what is the message
{
case WM_CREATE: // do initialization
case WM_PAINT: // validate window
case WM_DESTROY: // kills application
case WM_MOUSEMOVE: // read mouse
{
// Loword is X pos
Xmouse = LOWORD(lparam);
// Hiword is Y pos
Ymouse = HIWORD(lparam);
}
} // end switch
// process any default messages
return (DefWindowProc( ..... ));
} // end WinProcsee GameLoop1.cpp for a sample WinMain and WinProc
2D GAMES
- Local Coordinates (sometimes)
the X (left and right) and Y (up and down) coordinates local to your game objects or sprites (not required if bmp or other image sprites), for example: the rocks and ship in my Vazteroids.cpp used Local coordinates for vertices (4 points for the ship, 9 points for the rocks) and were drawn using the GDI function Polygon( )
- World Coordinates (sometimes)
if we have a side-scrolling or vertical-scrolling game or otherwise large "world" that is larger than the screen we'll need X, Y World coordinates (or Tile coordinates) in addition to X, Y Screen coordinates, see my Megaman4.cpp for a side-scrolling demo that uses Tile coordinates
- Screen Coordinates
the X, Y coordinates corresponding to the size of your screen display resolution (normally 640 x 480 or 800 x 600) where your sprites are placed on screen
3D GAMES
- Local Coordinates (or Model Coordinates)
the X (left and right), Y (up and down), Z (into or out of the screen) coordinates local to your game objects (or 3D models), for example: see my LoadASCModel.zip for a simple copter ASC model demo around 3000 polygons (or 3000 triangles and vertices)
- World Coordinates
the X, Y, Z coordinates for where the game object is placed or positioned in the "world" (normally the World coordinates for your 3D object or model would be the exact center of the model which makes it easier for collision detection), for example: the copter below (and its 3000 triangles) is placed in the 3D world using its single X, Y, Z World coordinates
- Camera Coordinates
the X, Y, Z coordinates for where your "camera" is or where the player is currently looking (in "first-person" shooters typically the camera and the player's look direction would be the same)
- Screen Coordinates
the X, Y coordinates corresponding to your screen display resolution (normally 640 x 480 or 800 x 600), taken care of by OpenGL at set up (glViewport) and automatically when drawing your 3D scene
- Vectors and Scalars
- "Vectors" have both magnitude and direction -- they are points or positions in 2D or 3D space with a direction on X, Y (2D) or X, Y, Z (3D), example vectors:
- player x, y is position and dx, dy is direction of player
- bullet x, y, z is position and dx, dy, dz is direction of bullet
- gravity is also a vector since it has both magnitude and direction (downward)
- "Scalars" have just magnitude (no direction)
- example scalars: distance or weight
FORMULAS
- Distance Between Points or Objects
To find the distance (also called magnitude or length) between two points or objects:
- in 2D space (square root of the difference between x, y points squared and added)
- distance = SQRT ( ( X2-X1)*(X2-X1) + (Y2-Y1)*(Y2-Y1) )
- in 3D space (square root of the difference between x, y, z points squared and added)
- distance = SQRT ( (X2-X1)*(X2-X1) + (Y2-Y1)*(Y2-Y1) + (Z2-Z1)*(Z2-Z1) )
- Aiming an Enemy Bullet at Player or Moving Enemy in the Direction of Player
To aim a enemy bullet directly at an object, or move enemy in the direction of player:
- in 2D:
- // get the difference in x and y between player and enemy
dx = Player.x - Enemy.x and dy = Player.y - Enemy.y- // compute the magnitude or distance between player and enemy
m = SQRT (dx * dx + dy * dy)- // divide differences by magnitude multiplied by speed to get respective x, y vectors
xmove = (dx / m) * SPEED and ymove = (dy / m) * SPEED- xmove and ymove vectors would be how much to add to the x, y coordinates of bullet or enemy each game frame
- 3D is similar with the addition of z-axis
- example code:
// From Vazteroids.cpp
// aim UFO bullet directly at ship
dx = Ship.xcenter - UFO.xbullet; // difference in x
dy = Ship.ycenter - UFO.ybullet; // difference in y
m = (float)sqrt(dx*dx + dy*dy); // magnitude (distance)
UFO.xbmove = (dx/m) * BULLET_SPEED;
UFO.ybmove = (dy/m) * BULLET_SPEED;// From VazTank.cpp
// enemy tank chasing player on ground (note: y-axis becomes z-axis instead)
dx = Player.x - x; dz = Player.z - z; // get diff between enemy and player
m = (GLfloat)sqrt(dx*dx + dz*dz); if (m == 0.0f) m = .0001f; // get magnitude (distance)
if (Enemy[i].type == ENEMY_TANK) // only enemy tanks will change direction
{
Enemy[i].dx = (GLfloat)((rand( )%40) - 20.0f) * .01f; // move random on x
Enemy[i].dz = (GLfloat)((rand( )%40) - 20.0f) * .01f; // move random on z
if (game_state == GAME_STATE_GAME_RUN) // move toward player also
{ Enemy[i].dx += (dx/m) * .3f; Enemy[i].dz += (dz/m) * .3f; }
} // end if enemy tank// From VazTank.cpp
EBullets[i].x = x; EBullets[i].y = y; EBullets[i].z = z;
// aim bullet directly at player, if copter bullet then need y vector too
dx = Player.x - x; dz = Player.z - z; dy = 0; if (y > 0) dy = Player.y - y;
m = (GLfloat)sqrt(dx*dx + dy*dy + dz*dz); if (m == 0.0f) m = .0001f;
// adjust aim to miss slightly based on difficulty level
ax = (GLfloat)((rand( )%40) - 20.0f) * .005f; az = (GLfloat)((rand( )%40) - 20.0f) * .005f;
if (game_difficulty == DIFFICULTY_EASY) { ax *= 2.0f; az *= 2.0f; }
if (game_difficulty == DIFFICULTY_NORMAL) { ax *= 1.5f; az *= 1.5f; }
EBullets[i].dx = (dx/m) * BULLET_SPEED + ax; // save bullet dx plus miss
EBullets[i].dz = (dz/m) * BULLET_SPEED + az; // save bullet dz plus miss
EBullets[i].dy = (dy/m) * BULLET_SPEED; // straight ahead if tank, downward if copter
EBullets[i].count = BULLET_DURATION - 25; // shorter range than player
- Moving Player in Facing Direction
- To move a player in the direction (or angle) that player is facing requires the following Trig formula:
- xd = cos(angle) * PLAYER_SPEED and yd = -sin(angle) * PLAYER_SPEED
- xd and yd (x and y difference or "delta") would be how much to add to player x and y each game frame
- example code:
// From Vazteroids.cpp
// note: here I already pre-computed the cos and sin values in an arrayShip.xmove += xdir[Ship.angle] * THRUST_SPEED;
Ship.ymove += ydir[Ship.angle] * THRUST_SPEED;// From VazTank.cpp
GLdouble xd,zd; // x and z delta
// note: y-axis becomes the z-axis since tank is on the groundxd = steps * cos(Player.Angle); // moves on X plane in player direction
zd = -steps * sin(Player.Angle); // moves on Z plane in player direction
Player.x += (GLfloat)xd; // do the move on X
Player.z += (GLfloat)zd; // do the move on Z
- Firing a Bullet in Facing Direction
- To fire a bullet in the direction (or angle) that player is facing requires the following Trig formula:
- xd = cos(angle) * BULLET_SPEED and yd = -sin(angle) * BULLET_SPEED
- xd and yd (x and y difference or "delta") would be how much to add to bullet x and y each game frame
- 3D is similar with the addition of z-axis
- example code:
// From Vazteroids.cpp
// note: cos and sin were pre-computed in an arrayBullets[i].xmove = xdir[Ship.angle] * BULLET_SPEED;
Bullets[i].ymove = ydir[Ship.angle] * BULLET_SPEED;
Bullets[i].duration = DURATION_FOR_SHIP_BULLET;// From VazTank.cpp
Bullets[i].dx = BULLET_SPEED * (GLfloat)cos(Player.Angle); // x direction
Bullets[i].dy = Player.Look / 16.5f; // y direction
Bullets[i].dz = -BULLET_SPEED * (GLfloat)sin(Player.Angle); // z direction
- Rotation (2D only, but 3D is similar)
To rotate a point around the origin (0,0) requires the following Trig formula:
xnew = x * cos(angle) - y * sin(angle)
ynew = y * cos(angle) + x * sin(angle)see also my Rotate Points in 2D space which allows you to rotate a point and specify the number of angle positions -- this is how I pre-computed the rotation positions for the 4 points of my ship in Vazteroids.cpp
In OpenGL using 3D this math is taken care of using the glRotatef( ) function.
example: glRotatef(Enemy[i].Angle, 0.0f, 1.0f, 0.0f); // rotate by angle degrees on y axis
- GDI (Graphic Device Interface)
- for 2D graphics in Windows (Win32)
- easier and less set up than DirectDraw or OpenGL
- much slower than DirectDraw or OpenGL
- sample functions: see GDI games VazBreak.cpp or Vazteroids.cpp or VazPac.cpp
// go full screen
ChangeDisplaySettings(&game_screen, CDS_FULLSCREEN);// get the GDI device context
game_dc = GetDC(game_window);// select an object (such as white brush)
SelectObject(back_dc, white_brush);// plot a single pixel with color c
SetPixel(back_dc, xi, yi, c);// fill rectangle (erase the back buffer)
FillRect(back_dc, &back_rect, black_brush);// draw an ellipse (circular) to back buffer
Ellipse(back_dc, x1, y1, x2, y2);// draw a polygon (9 points) to back buffer
Polygon(back_dc, points, 9);// blit (bit block transfer) or copy back buffer to front buffer
BitBlt(game_dc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, back_dc, 0, 0, SRCCOPY);// blit a single sprite or block of pixels (no transparency)
BitBlt(back_dc, x, y, BLOCK_SIZE, BLOCK_SIZE, bmp_dc, 0, 0, SRCCOPY);// blit a single sprite with transparent background color (bug in Win98, only Win ME/XP work)
TransparentBlt(back_dc, Vaz.x, Vaz.y, BLOCK_SIZE, BLOCK_SIZE, bmp_dc, 0, 0, BLOCK_SIZE, BLOCK_SIZE, RGB(0,0,0));
- DirectDraw (DirectX)
- for fast 2D graphics
- however dropped by Microsoft after DirectX 7
- but still available in DX8, DX9 +
- sample data types and functions: see Megaman4.cpp my side-scroll demo
LPDIRECTDRAW game_draw_main = NULL; // dd main object
LPDIRECTDRAWSURFACE game_draw_surface = NULL; // dd primary surface
LPDIRECTDRAWSURFACE game_draw_back = NULL; // dd back surface
DDSURFACEDESC game_draw_desc; // dd surface description
DDSCAPS game_draw_caps; // dd surface capabilities
LPDIRECTDRAWCLIPPER game_draw_clip; // dd surface clipper// create base IDirectDraw interface
DirectDrawCreate(NULL, &game_draw_main, NULL);// set cooperation to full screen
IDirectDraw_SetCooperativeLevel(game_draw_main, game_window, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);// set display mode to specific resolution
IDirectDraw_SetDisplayMode(game_draw_main, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_BPP);// create the primary surface with a back buffer
IDirectDraw_CreateSurface(game_draw_main, &game_draw_desc, &game_draw_surface, NULL);// get the pointer to the back buffer
IDirectDrawSurface_GetAttachedSurface(game_draw_surface, &game_draw_caps, &game_draw_back);// set the clipper on left and right sides of screen
IDirectDraw_CreateClipper(game_draw_main, 0, &game_draw_clip, NULL);
IDirectDrawClipper_SetHWnd(game_draw_clip, 0, game_window);
IDirectDrawSurface_SetClipper(game_draw_back, game_draw_clip);// clear the back buffer to black
IDirectDrawSurface_Blt(game_draw_back,NULL,NULL,NULL,DDBLT_COLORFILL | DDBLT_WAIT, &back_fill);// blit (bit block transfer) a tile or sprite to back buffer (no transparency)
IDirectDrawSurface_Blt(game_draw_back,&temp_rect,hsky,&tile_rect,DDBLT_WAIT,NULL);// blit a tile or sprite with transparency
IDirectDrawSurface_Blt(game_draw_back,&temp_rect,hbmp[meg_pos + meg_dir],&bmp_rect,DDBLT_WAIT | DDBLT_KEYSRC, NULL);// copy/swap/flip back buffer to front buffer
IDirectDrawSurface_Flip(game_draw_surface,NULL,DDFLIP_WAIT);
- OpenGL
- for fast 2D or 3D graphics
- cross-platform, Windows, Linux, or other
- sample functions: see VazTank.cpp my 3D OpenGL game
ChoosePixelFormat(game_dc, &pfd); // match the pixel format
SetPixelFormat(game_dc, pf, &pfd); // set the pixel format
wglCreateContext(game_dc); // create the rendering context
wglMakeCurrent(game_dc, game_rc); // make it current// set up screen width and height
glViewport(0, 0, width, height);// set up 3D camera perspective for 3D games
gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 1.0f, 5000.0f);// set up 2D display for 2D games (or 2D HUD or text)
glOrtho(0, width, height, 0, -1, 1);glBegin(GL_TRIANGLES);
// specify vertices and draw triangles to back buffer here
glEnd();glBegin(GL_QUADS);
// specify vertices and draw quads (rectangles) to back buffer here
glEnd();// swap (flip) back buffer to front buffer
SwapBuffers(game_dc);
back to http://www.bringyou.to/games