390 likes | 410 Views
3D Game Programming. CIS 488/588 Bruce R. Maxim UM-Dearborn. Recommended Tools - 1. C/C++ Compilers Visual C++ produces fastest .exe code 2D Bitmap Art Paintshop Pro or Adobe Photoshop 2D Vector Art Adobe Illustrator Postproduction Image Processing Adobe Photoshop or Corel Photo-Paint.
E N D
3D Game Programming CIS 488/588 Bruce R. Maxim UM-Dearborn
Recommended Tools - 1 • C/C++ Compilers • Visual C++ produces fastest .exe code • 2D Bitmap Art • Paintshop Pro or Adobe Photoshop • 2D Vector Art • Adobe Illustrator • Postproduction Image Processing • Adobe Photoshop or Corel Photo-Paint
Recommended Tools - 2 • Sound Processing • Sound Forge or Cool Edit Pro • 3D Modelers • Caligari trueSpace or 3D Studio Max • 3D Level Editors • Warcraft Level Editor or equivalent • Midi Sequencing • Cakewalk
Using Visual C++ • DirectX 9.0 SDK is required • Plan on building Win32 .exe application programs (not .dll or console application) • Use debug .exe files during development and release .exe files when finished • Use “add files” option to add source files to the project node itself • Include search paths for DirectX header files and .lib using “directory” option in main menu
Raiders 3D • Change into graphics mode using DirectX • Draw colored lines, pixels, and text strings • Get keyboard input • Play .wav file from disk • Play mid from from disk • Timing functions to synchronize game loop • Copy double buffer to display screen
RAIDERS.CPP T3DLIB1.CPP T3DLIB2.CPP T3DLIB3.CPP DDRAW.LIB DINPUT.LIB DINPUT8.LIB DSOUND.LIB DXGUID.LIB DINPUT.DLL DINPUT8.DLL DDRAW.DLL DSOUND.DLL DMUSIC.DLL 3D Resources Needed
#include <windows.h> #include <windowsx.h> #include <mmsystem.h> #include <iostream.h> #include <conio.h> #include <stdlib.h> #include <malloc.h> #include <memory.h> #include <string.h> #include <stdarg.h> #include <stdio.h> #include <math.h> #include <io.h> #include <fcntl.h> #include <ddraw.h> #include <dsound.h> #include <dmksctrl.h> #include <dmusici.h> #include <dmusicc.h> #include <dmusicf.h> #include <dinput.h> #include "T3DLIB1.h" // game library includes #include "T3DLIB2.h" #include "T3DLIB3.h" Include Files
WinMain - 1 int WINAPI WinMain( HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow) { WNDCLASS winclass; // our windows class HWND hwnd; // generic window handle MSG msg; // generic message HDC hdc; // generic dc PAINTSTRUCT ps; // generic paintstruct
WinMain - 2 // first fill in the window class stucture winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; winclass.lpfnWndProc = WindowProc; winclass.cbClsExtra = 0; winclass.cbWndExtra = 0; winclass.hInstance = hinstance; winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); winclass.hCursor = LoadCursor(NULL, IDC_ARROW); winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); winclass.lpszMenuName = NULL; winclass.lpszClassName = WINDOW_CLASS_NAME;
WinMain - 3 // register the window class if (!RegisterClass(&winclass))return(0); if (!(hwnd = CreateWindow(WINDOW_CLASS_NAME, WINDOW_TITLE, (WINDOWED_APP ? (WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION) : (WS_POPUP | WS_VISIBLE)), 0,0, // x,y, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, // handle to parent NULL, // handle to menu hinstance, // instance NULL))) // creation parms return(0);
WinMain - 4 // save the window handle and instance in a global main_window_handle = hwnd; main_instance = hinstance; // resize the window so client is really width x height if (WINDOWED_APP) { RECT window_rect = {0,0,WINDOW_WIDTH-1,WINDOW_HEIGHT-1}; AdjustWindowRectEx(&window_rect, GetWindowStyle(main_window_handle), GetMenu(main_window_handle) != NULL, GetWindowExStyle(main_window_handle));
WinMain - 5 // save the global client offsets, they are needed in DDraw_Flip() window_client_x0 = -window_rect.left; window_client_y0 = -window_rect.top; // now resize the window MoveWindow(main_window_handle, 0, // x position 0, // y position window_rect.right - window_rect.left, // width window_rect.bottom - window_rect.top, // height FALSE); // show window, so there's no garbage on first render ShowWindow(main_window_handle, SW_SHOW); } // end if windowed
WinMain - 6 Game_Init(); // disable CTRL-ALT_DEL, ALT_TAB, comment this line out SystemParametersInfo(SPI_SCREENSAVERRUNNING, TRUE, NULL, 0); // enter main event loop while(1) { if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { if (msg.message == WM_QUIT)break; TranslateMessage(&msg); // translate accelerator keys DispatchMessage(&msg); } // end if Game_Main(); } // end while
WinMain - 7 // shutdown game and release all resources Game_Shutdown(); // enable CTRL-ALT_DEL, ALT_TAB, comment this line out SystemParametersInfo(SPI_SCREENSAVERRUNNING, FALSE, NULL, 0); // return to Windows return(msg.wParam); } // end WinMain
Callback Function - 1 LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // this is the main message handler of the system PAINTSTRUCT ps; // used in WM_PAINT HDC hdc; // handle to a device context switch(msg) { case WM_CREATE: { return(0); } break;
Callback Function - 2 case WM_PAINT: { hdc = BeginPaint(hwnd,&ps); EndPaint(hwnd,&ps); return(0); } break; case WM_DESTROY: { PostQuitMessage(0); return(0); } break; default:break; } // end switch return (DefWindowProc(hwnd, msg, wparam, lparam)); } // end WinProc
Game_Init - 1 int Game_Init(void *parms) { int index; // used for looping Open_Error_File("error.txt"); // start up DirectDraw (replace the parms as you desire) DDraw_Init (WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_BPP, WINDOWED_APP); DInput_Init(); DInput_Init_Keyboard(); // acquire the keyboard DSound_Init(); // load in sound fx explosion_id = DSound_Load_WAV("exp1.wav"); laser_id = DSound_Load_WAV("shocker.wav"); DMusic_Init();
Game_Init - 2 // load and start main music track main_track_id = DMusic_Load_MIDI("midifile2.mid"); DMusic_Play(main_track_id); // add calls to acquire other directinput devices here... if (!WINDOWED_APP)ShowCursor(FALSE); // hide the mouse srand(Start_Clock()); // seed random // all your initialization code goes here... // create system colors rgb_green = RGB16Bit(0,255,0); rgb_white = RGB16Bit(255,255,255); rgb_blue = RGB16Bit(0,0,255); rgb_red = RGB16Bit(255,0,0);
Game_Init - 3 // create the starfield for (index=0; index < NUM_STARS; index++) { stars[index].x = -WINDOW_WIDTH/2 + rand()%WINDOW_WIDTH; stars[index].y = -WINDOW_HEIGHT/2 + rand()%WINDOW_HEIGHT; stars[index].z = NEAR_Z + rand()%(FAR_Z - NEAR_Z); stars[index].color = rgb_white; } // end for index
Game_Init - 4 // the vertex list for the tie fighter POINT3D temp_tie_vlist[NUM_TIE_VERTS] = // color, x,y,z { {rgb_white,-40,40,0}, // p0 {rgb_white,-40,0,0}, // p1 {rgb_white,-40,-40,0}, // p2 {rgb_white,-10,0,0}, // p3 {rgb_white,0,20,0}, // p4 {rgb_white,10,0,0}, // p5 {rgb_white,0,-20,0}, // p6 {rgb_white,40,40,0}, // p7 {rgb_white,40,0,0}, // p8 {rgb_white,40,-40,0}}; // p9
Game_Init - 5 // copy the model into the real global arrays for (index=0; index<NUM_TIE_VERTS; index++) tie_vlist[index] = temp_tie_vlist[index]; // the edge list for the tie fighter LINE3D temp_tie_shape[NUM_TIE_EDGES] = // color, vertex 1, vertex 2 { {rgb_green,0,2 }, // l0 {rgb_green,1,3 }, // l1 {rgb_green,3,4 }, // l2 {rgb_green,4,5 }, // l3 {rgb_green,5,6 }, // l4 {rgb_green,6,3 }, // l5 {rgb_green,5,8 }, // l6 {rgb_green,7,9 } }; // l7
Game_Init - 6 // copy the model into the real global arrays for (index=0; index<NUM_TIE_EDGES; index++) tie_shape[index] = temp_tie_shape[index]; // initialize position of each tie fighter and it's velocity for (index=0; index<NUM_TIES; index++) { Init_Tie(index); } // end for index return(1); } // end Game_Init
Game_Main - 1 int Game_Main(void *parms) { // all the calls for your game go here! int index; // looping var Start_Clock(); // start the timing clock // clear the drawing surface DDraw_Fill_Surface(lpddsback, 0); DInput_Read_Keyboard(); // game logic here...
Game_Main - 2 if (game_state==GAME_RUNNING) { // move players crosshair if (keyboard_state[DIK_RIGHT]) { // move cross hair to right cross_x+=CROSS_VEL; // test for wraparound if (cross_x > WINDOW_WIDTH/2) cross_x = -WINDOW_WIDTH/2; } // end if // repeat for left, up, down
Game_Main - 2 // speed of ship controls if (keyboard_state[DIK_A]) player_z_vel++; else if (keyboard_state[DIK_S]) player_z_vel--; if (keyboard_state[DIK_SPACE] && cannon_state==0) { cannon_state = 1; // fire the cannon cannon_count = 0; target_x_screen = cross_x_screen; // last target position target_y_screen = cross_y_screen; DSound_Play(laser_id); } // end if } // end if game running
Game_Main - 3 // process cannon, simple FSM ready->firing->cool // firing phase if (cannon_state == 1) if (++cannon_count > 15) cannon_state = 2; // cool down phase if (cannon_state == 2) if (++cannon_count > 20) cannon_state = 0; Move_Starfield(); Process_Ties(); // move and perform ai for ties Process_Explosions();
Game_Main - 4 // lock the back buffer and obtain pointer and width DDraw_Lock_Back_Surface(); Draw_Starfield(); Draw_Ties(); Draw_Explosions(); // draw the crosshairs // first compute screen coords of crosshair // note inversion of y-axis cross_x_screen = WINDOW_WIDTH/2 + cross_x; cross_y_screen = WINDOW_HEIGHT/2 - cross_y;
Game_Main - 4 // draw the crosshair in screen coords Draw_Clip_Line16(cross_x_screen-16,cross_y_screen, cross_x_screen+16,cross_y_screen, rgb_red,back_buffer,back_lpitch); Draw_Clip_Line16(cross_x_screen,cross_y_screen-16, cross_x_screen,cross_y_screen+16, rgb_red,back_buffer,back_lpitch); Draw_Clip_Line16(cross_x_screen-16,cross_y_screen-4, cross_x_screen-16,cross_y_screen+4, rgb_red,back_buffer,back_lpitch); Draw_Clip_Line16(cross_x_screen+16,cross_y_screen-4, cross_x_screen+16,cross_y_screen+4, rgb_red,back_buffer,back_lpitch);
Game_Main - 5 if (cannon_state == 1) // draw the laser beams { if ((rand()%2 == 1)) // right beam { Draw_Clip_Line16(WINDOW_WIDTH-1, WINDOW_HEIGHT-1, -4+rand()%8+target_x_screen,4+rand()%8+target_y_screen, RGB16Bit(0,0,rand()),back_buffer,back_lpitch); } // end if else // left beam { Draw_Clip_Line16(0, WINDOW_HEIGHT-1, -4+rand()%8+target_x_screen,-4+rand()%8+target_y_screen, RGB16Bit(0,0,rand()),back_buffer,back_lpitch); } // end if } // end if
Game_Main - 6 // done rendering, unlock back buffer surface DDraw_Unlock_Back_Surface(); // draw the informtion sprintf(buffer, "Score %d Kills %d Escaped %d", score, hits, misses); Draw_Text_GDI(buffer, 0,0,RGB(0,255,0), lpddsback); if (game_state==GAME_OVER) Draw_Text_GDI("G A M E O V E R", WINDOW_WIDTH/2-8*10, WINDOW_HEIGHT/2,RGB(255,255,255), lpddsback); // check if the music has finished, if so restart if (DMusic_Status_MIDI(main_track_id)==MIDI_STOPPED) DMusic_Play(main_track_id);
Game_Main - 7 DDraw_Flip(); // flip the surfaces Wait_Clock(30); // sync to 30ish fps // check for game state switch if (misses > 4*NUM_TIES) game_state = GAME_OVER; // check of user is trying to exit if (KEY_DOWN(VK_ESCAPE) || keyboard_state[DIK_ESCAPE]) { PostMessage(main_window_handle, WM_DESTROY,0,0); } // end if return(1); } // end Game_Main
Game_Shutdown int Game_Shutdown(void *parms) { DSound_Stop_All_Sounds(); DSound_Shutdown(); DMusic_Delete_All_MIDI(); DMusic_Shutdown(); DInput_Shutdown(); DDraw_Shutdown(); return(1); } // end Game_Shutdown
Enemy AI • Ship generated at random position beyond viewport • Ship follows fixed trajectory until it collides with player view point • No evasion is attempted
3D Projection • Orthographic projection Xproj = X Yproj = Y • Perspective projection (more realistic) Xper = zd * X/Z Yper = zd * Y/Z • The Z information can also be used to determine the brightness as Ties fighters get closer to gunner
Collision Detection • Laser cannon are simply two projected lines that converge on on cross-hair cursor • If Tie fighter bounding box intersects these lines a hit is scored
Explosions • When hit the Tie fighter 3D line segments are copied to a secondary data structure • The line segments are then randomly moved apart (similar to particle explosion) • After a couple of seconds of movement the line segments vanish and the explosion terminates