Initializing Your Game


We're now ready to work our way through the initialization checklist. There are a number of details to work through thus you might find you'll need to read through this section once and then go back and review some of the specifics. After I get through all of the initialization tasks, I'll dig into the main loop.

Checking System Resources

Checking system resources is especially important for Windows games, but console developers don't get off scott-free. Permanent storage, whether it is a hard disk or a memory card, should be checked for enough space to store game data before the player begins. Windows and console games that support special hardware like steering wheels or other input devices must check for their existence and fall back to another option, like the Gamepad, if nothing is found. System RAM and VRAM checks or calculating the CPU speed is clearly a job for the Windows programmer.

Here is code you can use to check the available disk space on the current drive:

 // Check for enough free disk space on the current disk. int const drive = _getdrive(); struct _diskfree_t diskfree; _getdiskfree(drive, &diskfree); unsigned int const neededClusters =    DISK_SPACE_NEEDED /(diskfree.sectors_per_cluster*diskfree.bytes_per_sector); if (diskfree.avail_clusters < neededClusters) {     // if you get here you don't have enough disk space! } 

This code will work on any ANSI compatible system. Since disk space is calculated in clusters and not bytes you have to do a little legwork. You must set DISK_SPACE_NEEDED to the number of bytes you need.

Checking for system RAM under Windows is a little trickier; sadly you need to leave ANSI behind. You should check the total physical memory installed, as well as the available virtual memory using Win32 calls. Virtual memory is a great thing to have on your side. You can think of it as having a near infinite bank account, with a very slow bank. If your game uses virtual memory in the wrong way, it will slow to a crawl. You might as well grab a pencil and sketch a storyboard of the next few minutes of your game; you'll see it faster. You can check both kinds of memory using this code:

 MEMORYSTATUS status; GlobalMemoryStatus(&status); if (status.dwTotalPhys < (TOTAL_PHYSICAL_MEMORY_NEEDED)) {     // You don't have enough physical memory. Tell the player to go get a real     // computer and give this one to his mother. } // Check for enough free memory. if (status.dwAvailVirtual < AVAILABLE_VIRTUAL_MEMORY_NEEDED) {    // You don't have enough virtual memory available.    // Tell the player to shut down the copy of Visual Studio running in the    // background, or whatever seems to be sucking the memory dry. } char *buff = new char[AVAILABLE_VIRTUAL_MEMORY_NEEDED]; if (buff)    delete[] buff; else {    // The system lied to you. When you attempted to grab a block as big    // as you need the system failed to do so. Something else is eating    // memory in the background; tell them to shut down all other apps    // and concentrate on your game. } 

The call to GlobalMemoryStatus() is supported under Win9x, but it can return incorrect information if there is more than 2Gb of RAM installed. You could call GlobalMemoryStatusEx() instead, but that call is not supported on Win9x operating systems like Win98 and WinME. Lovely. Luckily there are very few machines out there with that much memory installed, and by the time there are we'll be able to put Win9x machines out to pasture. Until then, you'd better stick with the older API.

The last paragraph of code is actually allocating and immediately releasing a huge block of memory. This has the effect of making Windows clean up any garbage that has accumulated in the memory manager and double checks that you can allocate a contiguous block as large as you need. If the call succeeds, you've essentially run the equivalent of a Zamboni machine through your systems memory, getting it ready for your game to hit the ice.

Calculating CPU Speed

You'd think that grabbing the CPU speed from a Wintel box would be as easy as reading the system information. It seems completely crazy that this value has to be calculated. Here's a great piece of code to do just that:

 //================================================================== // CPUSPEED // // CPU Timer for the Action, Arcade, Strategy Games Group, a part of // the Entertainment Business Unit at Microsoft. // // (c) Copyright 1999-2000 Microsoft Corporation. // Written by Michael Lyons (mlyons@microsoft.com) // //=================================================================== #include "cpu.h" #include <windows.h> #define SLEEPTIME   0 //=================================================================== // define static variables //=================================================================== static int s_milliseconds; static __int64       s_ticks; static int s_milliseconds0; static ___int64      s_ticks0; //=================================================================== // fabs // // floating point absolute value function //=================================================================== float inline fabs(float a) {    if (a < 0.0f)       return -a;    else       return a; } //=================================================================== // StartTimingCPU // // Call this function to start timing the CPU. It takes the CPU tick // count and the current time and stores it. Then, while you do other // things, and the OS task switches, the counters continue to count, and // when you call UpdateCPUTime, the measured speed is accurate. // //=================================================================== int StartTimingCPU() {    //    // detect ability to get info    //    __asm    {      pushfd                 ; push extended flags      pop    eax             ; store eflags into eax      mov    ebx, eax        ; save EBX for testing later      xor    eax, (1<<21)    ; switch bit 21      push eax               ; push eflags      popfd                  ; pop them again      pushfd                 ; push extended flags      pop    eax             ; store eflags into eax      cmp    eax, ebx        ; see if bit 21 has changed      jz   no_cpuid          ; make sure it's now on    }    //    // make ourselves high priority just for the time between    // when we measure the time and the CPU ticks    //    DWORD dwPriorityClass = GetPriorityClass(GetCurrentProcess());    int dwThreadPriority = GetThreadPriority(GetCurrentThread());    SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);    //    // start timing    //    s_milliseconds0 = (int)timeGetTime();    __asm    {      lea    ecx, s_ticks0          ; get the offset      mov    dword ptr [ecx], 0     ; zero the memory      mov    dword ptr [ecx+4], 0   ;      rdtsc                         ; read time-stamp counter      mov    [ecx], eax             ; store the negative      mov    [ecx+4], edx           ; in the variable    }    //    // restore thread priority    //    SetThreadPriority(GetCurrentThread(), dwThreadPriority);    SetPriorityClass(GetCurrentProcess(), dwPriorityClass);    return 0; no_cpuid:    return -1; } //================================================================== // UpdateCPUTime // // This function stops timing the CPU by adjusting the timers to account // for the amount of elapsed time and the number of CPU cycles taked // during the timing period. //================================================================== void UpdateCPUTime() {    //    // make ourselves high priority just for the time between    // when we measure the time and the CPU ticks    //    DWORD dwPriorityClass = GetPriorityClass(GetCurrentProcess());    int dwThreadPriority = GetThreadPriority(GetCurrentThread());    SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);    //    // get the times    //    s_milliseconds   = -s_milliseconds0;    s_ticks           = -s_ticks0;    s_milliseconds    += (int)timeGetTime();    __asm    {       lea    ecx, s_ticks         ; get the offset       rdtsc                       ; read time-stamp counter       add    [ecx], eax           ; add the tick count       adc    [ecx+4], edx         ;    }    //    // restore thread priority    //    SetThreadPriority(GetCurrentThread(), dwThreadPriority);    SetPriorityClass(GetCurrentProcess(), dwPriorityClass);    return; } //================================================================== // CalcCPUSpeed // // This function takes the measured values and returns a speed that // represents a common possible CPU speed. //================================================================== int CalcCPUSpeed() {    //    // get the actual cpu speed in MHz, and    // then find the one in the CPU speed list    // that is closest    //    const struct tagCPUSPEEDS    {       float  fSpeed;       int    iSpeed;    } cpu_speeds[] =    {      //      // valid CPU speeds that are not integrally divisible by      // 16.67 MHz      //      {  60.00f,      60 },      {  75.00f,      75 },      {  90.00f,      90 },      { 120.00f,     120 },      { 180.00f,     180 },    };    //    // find the closest one    //    float  fSpeed=((float)s_ticks)/((float)s_milliseconds*1000.0f);    int iSpeed=cpu_speeds[0].iSpeed;    float  fDiff=(float)fabs(fSpeed-cpu_speeds[0].fSpeed);    for (int i=1 ; i<sizeof(cpu_speeds)/sizeof(cpu_speeds[0]) ; i++)    {       float fTmpDiff = (float)fabs(fSpeed-cpu_speeds[i].fSpeed);       if (fTmpDiff < fDiff)       {         iSpeed=cpu_speeds[i].iSpeed;         fDiff=fTmpDiff;       }    }    //    // now, calculate the nearest multiple of fIncr    // speed    //    //    // now, if the closest one is not within one incr, calculate    // the nearest multiple of fIncr speed and see if that's    // closer    //    const float fIncr=16.66666666666666666666667f;    const int iIncr=4267; // fIncr << 8    //if (fDiff > fIncr)    {      //      // get the number of fIncr quantums the speed is      //      int iQuantums = (int)((fSpeed / fIncr) + 0.5f);      float fQuantumSpeed = (float)iQuantums * fIncr;      float  fTmpDiff = (float)fabs(fQuantumSpeed - fSpeed);      if (fTmpDiff < fDiff)      {              iSpeed = (iQuantums * iIncr) >> 8;        ifDiff=fTmpDiff;      }    }    return iSpeed; } //================================================================== // GetCPUSpeed // // Gets the CPU speed by timing it for 3 seconds. //================================================================== int GetCPUSpeed() {    static int CPU_SPEED = 0;    if(CPU_SPEED!=0)    {      //This will assure that the 0.5 second delay happens only once      return CPU_SPEED;    }    if (StartTimingCPU())       return 0;    //This will lock the application for 1 second    do    {       UpdateCPUTime();       Sleep(SLEEPTIME);    } while (s_milliseconds < 1000);    CPU_SPEED = CalcCPUSpeed();    return CPU_SPEED; } 

The only thing you have to do is call GetCPUSpeed(). The first call will start the timer, which takes a few seconds to run. The longer it runs the more accurate the timing, but there's no reason to run it any longer than two seconds, and one second will provide a pretty accurate count. You can use the results of this calculation to turn off certain CPU sucking activities like decompressing MP3 files or drawing detailed animations. It's not completely crazy to save the value off in a game options setting, so you don't have to calculate it each time your game runs.

Estimating VRAM

Something every game must do on Windows platforms is estimate the size of VRAM. From DirectX 4 to DirectX 7, an API is available to get this through the IDirectDraw interface, GetAvailableVidMem. Here's an example of how you can use this API to estimate VRAM size:

 DDSCAPS2 ddsCaps; ZeroMemory(&ddsCaps, sizeof(ddsCaps)); ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY; DWORD dwUsedVRAM = 0; DWORD dwTotal=0; DWORD dwFree=0; // lp_DD points to the IDirectDraw object HRESULT hr = lp_DD->GetAvailableVidMem(&ddsCaps, &dwTotal, &dwFree); // dwUsedVRAM holds the number of bytes of VRAM used dwUsedVRAM = dwTotal-dwFree; 

In DirectX 9, however, this useful API was replaced. The DirectX 9 documentation advertises that the best way to gain access to the amount of available video RAM is to use a method of the IDirect3DDevice9 interface, GetAvailableTextureMem as shown here:

 UINT free=0; // lp_D3DDev points to the IDirect3DDevice9 object, // and returns free VRAM in MB free = lp_D3DDev->GetAvailableTextureMem(); 

Don't be fooled. Read the docs more carefully and you'll se that this API returns the amount of available texture memory, which turns out to be completely different from the amount of VRAM. Not exactly an improvement, is it? I'm sticking with using the old DX7 API, even if all I'm doing is making this one call. Don't forget that even if you are using DX9, you can always grab the useful bits of DX7, DX6, and so on, if you find a good enough reason to do it.

Gotcha

Just because you can call QueryInterface and grab an ancient DirectX 4 object, it doesn't mean that it is safe to do it. The word from some hardware manufacturers and driver writers is thisthey aren't proactively testing their latest drivers to support old DirectX code. In other words, make sure you test your code against the newest cards and the newest drivers, which is always a good idea anyway.

Loading Game Options for Debugging

I may be completely old school but I still like using INI files. As long as you only use them for loading options or some other rare event, they work just fine and you can edit INI files with any text editor. The exact contents of this file are completely up to you, but it should definitely hold anything that you would want to switch from run to run without recompiling. Here's an example:

 ; game.ini ; ; [OPTIONS] ; Run at full speed (default 0) instead of frame limiting artificially Fullspeed=0 ; Use hardware acceleration ( default software = 0) Hardware_Acceleration=1 ; Page Flipping (default 1) PageFlip=1 ; Antialias primitive edges (default 1) Antialias=1 ; Edge antialiasing (default 0) Edge_AntiAliasing=0 ; Dithering (default 1) Dithering=1 ; Texture Perspective (default 1) Texture_Perspective=1 

Windows programmers can use the GetPrivateProfileInt API to easily read this file. The rest of you can write the parser yourself. Since the default values are sent into the function call, the INI file doesn't even have to exist. That can be really convenient. Here's an example:

 m_useHardwareAccel = ::GetPrivateProfileInt(    _T("OPTIONS"), _T("Hardware_Acceleration"), false, path ) ? true : false; m_usePageFlipping = ::GetPrivateProfileInt(    _T("OPTIONS"), _T("PageFlip"), true, path ) ? true : false; m_useDithering = ::GetPrivateProfileInt(    _T("OPTIONS"), _T("Dithering"), true, path ) ? true : false; m_useAntialiasing = ::GetPrivateProfileInt(    _T("OPTIONS"), _T("AntiAliasing"), true, path ) ? true : false; m_useEgdeAntiAliasing = ::GetPrivateProfileInt(    _T("OPTIONS"), _T("Edge_AntiAliasing"), false, path ) ? true : false; m_useVRAM = ::GetPrivateProfileInt(    _T("OPTIONS"), _T("Enable_VRAM"), true, path ) ? true : false; m_runFullSpeed = ::GetPrivateProfileInt(    _T("OPTIONS"), _T("Fullspeed"), false, path ) ? true : false; m_useTexturePerspective = ::GetPrivateProfileInt(    _T("OPTIONS"), _T("Texture_Perspective"), true, path ) ? true : false; 

I've used these settings to debug hardware compatibility issues, and easily control what settings are on by default for demos. I hate recompiles, so making data-driven debug settings like these is a winning proposition all around.

Do You Have a Dirtbag on Your Hands?

If you are lucky (or probably unlucky) enough to be working on a mass-market title, you have to support computers that should really be at the business end of a boat's anchor chain. Everyone wants a game to look really good, but when you have to support machines that have only 15% of the CPU speed as the top end rigs something has to give. You can use any benchmark to determine what makes a computer a dirtbag and what doesn't. For 2003 products, a dirtbag CPU is anything less than a 300MHz or anything that had less than 32Mb of system RAM. For your game, a dirtbag CPU might have to rely on the streaming speed of the DVD or whether the video card has hardware transform and lighting. Whatever you use, it is important to set your standards and determine if the computer the player is using is at the shallow end of the wading pool.

Best Practice

Once you figure the computer is at the bottom end, you should set your game defaults for new players accordingly. A good start would be to turn off any CPU intensive activities like decompressing MP3 streams or scale back animations. If the player decides to bring up the options screen and turn some of these features back on, my suggestion is to let him do it if it's possible. Maybe they'll be inclined to retire their old machine.

Initializing Your Resource Cache

I covered general memory management in Chapter 3 and resource caching is covered in Chapter 8. Make sure that you bone up on these topics. Initializing the resource cache will be a gateway to getting your game up and running. The size of your resource cache is totally up to your game design and the bottom end hardware you intend to support. It's a good idea to figure out if your player's computer is a dirtbag or flamethrower and set your resource cache memory accordingly.

Gotcha

You can't impress a player with fantastic graphics until you reserve a nice spot in system and video memory for your textures, models, and sprites. You can't even bring up a nice dialog box telling a loser player they are low on memory. If your resource cache allocation fails, you know there's something horribly wrong. The game should fail as elegantly as possible.

Using CreateWindow to Create Your Window

Win32 programmers can't put off the task of creating their window any longer. Creating a game window is easy enough, every DirectX sample shows you how to do it. What they don't show you is how to make sure you only create one instance of your game.

If your game takes a moment to get around to creating a window, a player might get a little impatient and double click the game's icon a few times. If you don't take the precaution of handling this problem, multiple copies of your game initializing code will overwhelm the computer system. Here's a piece of code that will take care of this problem under Win32:

 // Find the window.  If active, set and return false // Only one game instance may have this mutex at a time... const char *GAME_TITLE = "Yet Another Shooter v3.141"; HANDLE handle = CreateMutex(NULL, TRUE, GAME_TITLE); // Does anyone else think 'ERROR_SUCCESS' is a bit of a dichotomy? if (GetLastError() != ERROR_SUCCESS) {    CWnd *pWnd = CWnd::FindWindow(NULL, GAME_TITLE);   if (pWnd)    {     // An instance of your game is already running.     pWnd->ShowWindow(SW_SHOWNORMAL);     pWnd->SetFocus();     pWnd->SetForegroundWindow();     pWnd->SetActiveWindow();     return false;    }    // There can be only one!    // Imagine cool window creation code here    return true; } 

Make sure the GAME_TITLE is a string that uniquely identifies your game. A mutex is a process synchronization mechanism and is common to any multitasking operating system. This code is guaranteed to create one mutex with the identifier GAME_TITLE for all processes running on the system. If it can't be created, then another process has already created it.

The very next thing a Windows game needs to do is create a window class. Here's a good example that creates a simple window with no frills.

 WNDCLASS wndcls; memset(&wndcls, 0, sizeof(WNDCLASS));   // start with NULLS wndcls.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS; wndcls.hIcon = NULL;                // icons and cursors can be loaded later wndcls.hCursor = NULL; wndcls.hbrBackground = HBRUSH(GetStockObject(NULL_BRUSH)); wndcls.lpszMenuName = NULL; // Specify your own class name for using FindWindow later wndcls.lpszClassName = GAME_TITLE; #ifdef MFC_VER    wndcls.lpfnWndProc = ::DefWindowProc;   // MFC has their own functions.    wndcls.hInstance = AfxGetInstanceHandle();    // Register the new class and exit if it fails    if(!AfxRegisterClass(&wndcls))    {       // Yikes! Try to fail elegantly. You are doomed.    } #else    wndcls.lpfnWndProc = MyWindowProc;// you can specify your own window procedure    wndcls.hInstance = hInstance;     // get hInstance from WinMain    // Register the new class and exit if it fails    if(!RegisterClass(&wndcls))    {       // Yikes! Try to fail elegantly. You are doomed.    } #endif 

This code will work just fine for MFC apps or plain Win32 apps. If you are working in MFC, the code that registers the window class and tweaks the values of the CREATESTRUCT should appear in the PreCreateWindow method of your main frame. Plain old Win32 programmers can put this code right above the call to CreateWindow:

 // This fixes the problem of running 800x600 going into the // game, and not having controls aligned with the screen. cs.cx = min( SCREEN_WIDTH, ::GetSystemMetrics( SM_CXFULLSCREEN ) ); cs.cy = min( SCREEN_HEIGHT, ::GetSystemMetrics( SM_CYFULLSCREEN ) ); cs.x = 0; //Set the position to the origin cs.y = 0; cs.dwExStyle &= ~WS_EX_CLIENTEDGE; cs.lpszClass = GAME_TITLE; 

Gotcha

Notice that the size of the window is set to the smaller of the expected screen size or the maximum width of the screen. If your player has done something stupid and set their desktop to 640x480 and your game is assuming an 800x600 window minimum, then any dialog box on an 800x600 window will appear cut off on the right side. Win32 will happily create a window of any size, 8000x6000 perhaps, and any dialog box appearing in the center of that huge window will never be seen by man or beast.

There's one more trick to making a perfect MFC game window. An MFC application is really two windows at a minimum. The main application window is called a main frame, and the client area is called a child view. The child view for a game needs a special window class. You can accomplish this by overloading the call to PreCreateWindow and create a game friendly window class as shown here:

 BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) {    if (!CWnd::PreCreateWindow(cs))       return FALSE;    cs.style &= ~WS_BORDER;    cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,       NULL, HBRUSH(GetStockObject(NULL_BRUSH)), NULL);    return TRUE; } 

The CChildView class will be the main receiver of all the MFC messages for keyboard and mouse input. You'll most likely translate these messages into something useful for your game.

Now that your application has a window handle you can initialize all the other parts of your game that require it such as your audio system or graphics system.

Initializing Your Audio

Audio systems are usually multithreaded at the lowest level. This is one of the reasons you have to wait until you are sure your game isn't running multiple instances of itself when your game initializes. The only thing you want to do at the initialization stage is get your audio system ready to play; don't start playing anything just yet. Most games have user settable game options, which will be read next if they exist. Those game options will definitely set audio options such as turning music or character speech off. If the gamer has turned all the audio options off in favor of playing some old Pink Floyd, you don't want a few milliseconds of audio to chirp the speaker before the options are loaded.

Loading User Settable Game Options

Finding the right directory for user settable game options used to be easy. A programmer would simply store user data files close to the EXE and use the GetModuleFileName API. Windows XP Home makes the Program Files directory off limits by default, and applications are not allowed to write directly to this directory tree. Instead, applications must write user data to the C:\Documents and Settings\{User name}\Application Data directory. This directory is completely different from one version of Windows to another, so you have to use a special API to find it: SHGetSpecialFolderPath. Windows XP Pro is more forgiving, and doesn't limit access to these directories by default. XP Home was designed this way to keep the casual, home user from stomping though the Program Files directory in a ham-fisted attempt to solve various problems.

If it were that easy I wouldn't have to show you the next code block. If you open Windows Explorer to your application data directory, you'll see plenty of companies who play by the rules, writing application data in the spot that will keep Windows XP from freaking out. Clearly you can't just stick your data files in the top level. Our last Microsoft product used this path:

 GAME_APP_DIRECTORY = "Microsoft\\Microsoft Games\\Bicycle Casino\\2.0"; 

Best Practice

The value for your GAME_APP_DIRECTORY is also a great value for a registry key. Don't forget to add the version number at the end. You might as well hope for a gravy train: 2.0, 3.0, 4.0, and so on.

It's up to you to make sure you create the directory if it doesn't exist. This can be a hassle, since you have to walk down the directory tree, creating all the way down. Since I already went to the trouble of writing this code for you, feel free to take it. Your time is better spent writing something fun instead of annoying Windows homework:

 CString m_ProfilesDirectory; HRESULT hr; TCHAR userDataPath[MAX_PATH]; Hr = SHGetSpecialFolderPath(hWnd, userDataPath, CSIDL_APPDATA, true); m_ProfilesDirectory = profilesPath; m_ProfilesDirectory += "\\"; m_ProfilesDirectory += GAME_APP_DIRECTORY; // Does our directory exist? if (0xffffffff == GetFileAttributes(m_ProfilesDirectory)) {    // Nope - we have to go make a new directory to store application data.    CString current = profilesPath;    CString myAppData = GAME_APP_DIRECTORY;    CString token;    do {       int left = myAppData.Find('\\');       if (left==-1)       {          token = myAppData;          myAppData = _T("");       }       else       {         assert(myAppData.GetLength()>=left);         token = myAppData.Left(left);         myAppData = myAppData.Right(myAppData.GetLength()-(left+1));       }       if (token.GetLength())       {         current += "\\";         current += token;         if (false == CreateDirectory(current, NULL))         {            int error = GetLastError();            if (error != ERROR_ALREADY_EXISTS)            {               return false;            }         }       }    } while (myAppData.GetLength()); } m_ProfilesDirectory += _T("\\"); 

This code will make sure your application data directory exists, and set the value of m_ProfilesDirectory to the name of that directory—drive letter and all.

Your game options are completely up to you and the design of your game. There's no hard and fast rule of what game options you need to provide. You can count on needing volume controls for various audio tracks in your game such as music or sound effects. It's also a great idea to store a game option for fullscreen mode.

Best Practice

Make sure that the DEBUG game.ini file has some way to override the fullscreen option in the user settings. If you need to debug a saved game and you don't happen to have a multi-monitor setup, any breakpoints will cause trouble if the game is in fullscreen mode. Any programmer who attempts to debug a fullscreen game with only one monitor is sure to lose their mind and start a killing spree.

Creating Your Drawing Surfaces

It's finally time to create the drawing surfaces. If your game can run in a window, the first thing you need to check is whether or not the current display settings are compatible with the drawing surface your game will use. There are two things you need to check: display size and bit depth. If your game assumes an 800x600 drawing surface, the display settings must be set to anything higher than 800x600 such as 1024x768. In a windowed mode, you have to leave some room for the window border and title bar. If you don't, the player will be forced to use the keyboard to move the window around, because the only part of the window that can accept a drag command from the mouse is displayed offscreen.

Bit depth is easier. The only incompatibility is in 24-bit mode because you can't count on enumerating a 24-bit Direct3D device because very few cards support it. 3D hardware draws optimally onto 16- or 32-bit render targets. Of course, you could create a 32-bit Direct3D device and have Windows do the translation from 32-bit to 24-bit, but that isn't going to make your game faster.

If either of these incompatibilities exist the best thing to do is notify the player with a dialog box. You'll have to use a nasty GDI dialog, of course since you don't have your drawing surface up, so just call the Win32 MessageBox API. Tell them that they can't run in windowed mode with their current display settings and reinitialize the drawing surface in fullscreen mode.

Initializing Your Game Objects

Everything is ready for your game to start. The hardware checks out, your data files are open, the resource cache is running, and the audio and video systems are ready to play. Most likely you'll have some special systems such as physics, user interface, or AI to initialize depending on your game architecture. The order in which you create these objects is entirely up to you and your game design.

With initialization done control passes to the main loop.




Game Coding Complete
Game Coding Complete
ISBN: 1932111751
EAN: 2147483647
Year: 2003
Pages: 139

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net