游戏编程之六 游戏编程的特点

第七章 游戏编程的特点
第一节 概述:


电脑游戏在计算机发展使用中可以说扮演了一个极为有趣的角色,一方面不为很多人所赞同,认为是一种浪费;而另一方面电脑游戏却是推动计算机的各项技术迅速发展的最有力的力量之一。


这一点,可以从3d类游戏对硬件无止境的需求,游戏迷对游戏图像的质量、游戏的交互性、人机界面的友好性等方面的需求体现出来(当然游戏迷对游戏的的构思、创意的要求也是苛刻且无止境的,但这一点只有靠您自己的想象力,我们是爱莫能助了)。


从游戏近期的发展中,我们从3d游戏的发展,可以看到从Doom到现在的古墓丽影2、雷神之锤2,3d游戏的画面从生硬单调的多边形到今天柔和复杂几进乱真的场景、道具、怪物,敌人从只会疯狂向你冲来到今天会偷袭、会审时度势地采取合适的方式方法向你进攻;游戏无论从硬件支持还是编程技术方面都有突飞猛进的进展。在游戏发展的过程中,很多技术也随之发展起来了,例如各种图形加速卡的出现和发展,directx的出现,和各个成功游戏中采用的各种优化技术都推动了计算机技术的发展。


游戏可以说是集合了每个时期计算机行业中最先进的硬件技术和最新的编程思想,比如近期的游戏都是采用了面向对象的编程思想的基于Windows的软件,大部分图象要求高的游戏都要求或支持图形加速卡。同时游戏编程中也有自己基本的方式方法、结构和理论,在这一章的学习中我们将讨论这些问题。
在这一章中我们将讨论下面几个问题:


程序入口 即是游戏获取外部操作的讯息,得到下次刷新所需的新参数的手段。如同一般的SDK Windows应用程序一样,程序的入口为WINMAIN()。


游戏初始化 包括创建标准的WINDOWS程序所需的初始化程序以及游戏内部的初始化程序,例如游戏系统初始化、游戏图形的装入、游戏声音的装入等。


游戏内部循环: 游戏的循环入口是WINDOWS消息循环内部的一个函数调用,游戏内部循环包括刷新游戏单位、画游戏单位两部分。


刷新游戏单位: 用于每一帧刷新游戏单位的状态,例如改变游戏单位的状态、改变游戏单位的位置、获取外部信息等。


画游戏单位: 用于每一帧往屏幕上画游戏单位的图象,并进行特殊处理以提高速度。


计算机人工智能: 主要用于受计算机处理的游戏单位的行为控制算法,程序部分位于刷新计算机游戏单位部分中。


游戏内存管理:  这一部分对于优质高效的游戏软件是十分重要的,内存管理不当会导致游戏性能的降低,甚至引起死机。


游戏交互设计: 交互设计是游戏可玩性的关键,友好的交互界面和交互方式可以使游戏增色不少。


游戏图象底层设计: 游戏软件的主要处理时间花在处理图象和画图象上,所以游戏图象底层的设计对于游戏的最终效果是十分重要的。


游戏多媒体设计: 主要包括图形界面设计、游戏音乐音效设计、游戏动画设计、游戏影象设计的几个方面,更广泛的说还包括游戏所有运行过程的功能设计。




第二节 程序入口
这个标题看起来似乎很难理解,它的意思就是当游戏被启动时,计算机从什么地方开始运行程序的。在Windows的应用程序上,Winmain()函数一般就是程序入口。游戏开始后,就调用Winmain()函数,然后再按语句的顺序或所接受到的消息调用相应的函数。


从第三章Windows编程基础中我们了解到Winmain()函数的的结构、运行过程,现在我们就游戏编程的角度来讨论Winmain()函数的编制。


int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
                    int nCmdShow )
{
    MSG msg;


    while( lpCmdLine[0] == '-' || lpCmdLine[0] == '/')
    {
        lpCmdLine++;


        switch (*lpCmdLine++)
        {
        case 'e':
            bUseEmulation = TRUE;
            break;
        case 'w':
            bFullscreen = FALSE;
            break;
        case 'f':
            bFullscreen = TRUE;
            break;
        case '1':
            CmdLineBufferCount = 1;
            break;
        case '2':
        case 'd':
            CmdLineBufferCount = 2;
            break;
        case '3':
            CmdLineBufferCount = 3;
            break;
        case 's':
            bStretch = TRUE;
            break;
        case 'S':
            bWantSound = FALSE;
            break;
        case 'x':
            bStress= TRUE;
            break;
        case '?':
            bHelp= TRUE;
            bFullscreen= FALSE;  // give help in windowed mode
            break;
        }


        while( IS_SPACE(*lpCmdLine) )
        {
            lpCmdLine++;
        }
    }


    GameMode.cx = getint(&lpCmdLine, 640);
    GameMode.cy = getint(&lpCmdLine, 480);
    GameBPP = getint(&lpCmdLine, 8);


    /*
     * create window and other windows things
     */
    if( !initApplication(hInstance, nCmdShow) )
    {
        return FALSE;
    }


    /*
     * Give user help if asked for
     *
     * This is ugly for now because the whole screen is black
     * except for the popup box.  This could be fixed with some
     * work to get the window size right when it was created instead
     * of delaying that work. see ddraw.c
     *
     */


    if( bHelp )
    {
        MessageBox(hWndMain,
                   "F12 - Quit\n"
                   "NUMPAD 2  - crouch\n"
                   "NUMPAD 3  - apple\n"
                   "NUMPAD 4  - right\n"
                   "NUMPAD 5  - stop\n"
                   "NUMPAD 6  - left\n"
                   "NUMPAD 7  - jump\n"
                   "\n"
                   "Command line parameters\n"
                   "\n"
                    "-e   Use emulator\n"
                    "-S   No Sound\n"
                    "-1   No backbuffer\n"
                    "-2   One backbuffer\n"
                    "-4   Three backbuffers\n"
                    "-s   Use stretch\n"
                    "-x   Demo or stress mode\n",
                   OUR_APP_NAME, MB_OK );
    }


    /*
     * initialize for game play
     */


    if( !InitGame() )
    {
        return FALSE;
    }
    dwFrameTime = timeGetTime();
   
    while( 1 )
    {
        if (PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE))
        {
            if (!GetMessage( &msg, NULL, 0, 0))
            { 
                break;
            }
            TranslateMessage(&msg); 
            DispatchMessage(&msg);
        }
        else if (!bPaused && (bIsActive || !bFullscreen))
        {
            ProcessFox(lastInput);
            lastInput=0;
        }
        else
        {
            WaitMessage();
        }
    }


    if (AveFrameRateCount)
    {
        AveFrameRate = AveFrameRate / AveFrameRateCount;
        Msg("Average frame rate: %d", AveFrameRate);
    }


    return msg.wParam;


} /* WinMain */




我们知道在消息循环之中只有一个消息----WM_QUIT可以结束这个循环,退出WINDOWS。所以我们要在消息循环之前完成所有的工作即所有的初始化。关于初始化这个概念在下一节我们将详细讨论。在我们提供的例程(在光盘的sample目录中)中的foxbear.c里的WMain()中我们可以看到在消息循环之前先运行DDint()函数对DirectDraw进行初始化,检测命令行参数并对相关的参数进行赋值和确定显示模式,进行窗口的初始化,检测bhelp的值以确定是否显示帮助对话框,进行游戏的初始化。
在一个游戏的消息循环中除了包含一般Windows应用程序的消息循环所应包含的部分外还应有调用有关检测游戏单位状态位置、刷新游戏单位和重画新图以及有关人工智能的函数的部分。在例程中的消息循环部分包含了一个关于检测游戏单位状态位置、刷新游戏单位和重画新图函数的调用。


在这些调用中一般有两种方法:
1.在消息循环中直接调用有关函数。比如说在一个RPG的游戏中每个循环都检测主角的的位置是否发生改变,若改变了则在新位置上重画主角的图。
2.通过检测WM_TIMER消息,以决定是否调用有关函数。即是每隔一段时间(若干个时钟周期),检测一次,然后决定函数的调用与否。


在上面的两种方法里,第一种是现在较常用的,它的缺点是CPU资源占用相对较多,但对不同的机型适应性较强,较稳定。第二种在一些较老的游戏或对速度要求不高的游戏中较常见,与第一种相比它的CPU资源占用相对较少,但在不同的机型中表现差异极大。


在谈WinMain()的编制时,窗口函数(WINPROC)的编制是必须说的。窗口函数可以说是把功能不同的函数通过Switch-Case结构连起来组成一个复杂程序的线索。它的基本编写方法在Windows编程基础中我们就已经谈到了。仔细阅读例程中的MainWndProc()函数相信对您是有相当大的帮助的。


/*
 * MainWndProc
 *
 * Callback for all Windows messages
 */
long FAR PASCAL MainWndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    HDC         hdc;


    switch( message )
    {
    case WM_SIZE:
    case WM_MOVE:
        if (IsIconic(hWnd))
        {
            Msg("FoxBear is minimized, pausing");
            PauseGame();
        }


        if (bFullscreen)
        {
            SetRect(&rcWindow, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
        }
        else
        {
            GetClientRect(hWnd, &rcWindow);
            ClientToScreen(hWnd, (LPPOINT)&rcWindow);
            ClientToScreen(hWnd, (LPPOINT)&rcWindow+1);
        }
        Msg("WINDOW RECT: [%d,%d,%d,%d]", rcWindow.left, rcWindow.top, rcWindow.right, rcWindow.bottom);
        break;


    case WM_ACTIVATEAPP:
        bIsActive = (BOOL)wParam && GetForegroundWindow() == hWnd;


        if (bIsActive)
            Msg("FoxBear is active");
        else
            Msg("FoxBear is not active");


        //
        // while we were not-active something bad happened that caused us
        // to pause, like a surface restore failing or we got a palette
        // changed, now that we are active try to fix things
        //
        if (bPaused && bIsActive)
        {
            if (RestoreGame())
            {
                UnPauseGame();
            }
            else
            {
               if (GetForegroundWindow() == hWnd)
               {
                    //
                    //  we are unable to restore, this can happen when
                    //  the screen resolution or bitdepth has changed
                    //  we just reload all the art again and re-create
                    //  the front and back buffers.  this is a little
                    //  overkill we could handle a screen res change by
                    //  just recreating the front and back buffers we dont
                    //  need to redo the art, but this is way easier.
                    //
                    if (InitGame())
                    {
                        UnPauseGame();
                    }
                }
            }
        }
        break;


    case WM_QUERYNEWPALETTE:
        //
        //  we are getting the palette focus, select our palette
        //
        if (!bFullscreen && lpPalette && lpFrontBuffer)
        {
            HRESULT ddrval;


            ddrval = IDirectDrawSurface_SetPalette(lpFrontBuffer,lpPalette);
            if( ddrval == DDERR_SURFACELOST )
            {
                IDirectDrawSurface_Restore( lpFrontBuffer );


                ddrval= IDirectDrawSurface_SetPalette(lpFrontBuffer,lpPalette);
                if( ddrval == DDERR_SURFACELOST )
                {
                   Msg("  Failed to restore palette after second try");
                }
            }


            //
            // Restore normal title if palette is ours
            //


            if( ddrval == DD_OK )
            {
                SetWindowText( hWnd, OUR_APP_NAME );
            }
        }
        break;


    case WM_PALETTECHANGED:
        //
        //  if another app changed the palette we dont have full control
        //  of the palette. NOTE this only applies for FoxBear in a window
        //  when we are fullscreen we get all the palette all of the time.
        //
        if ((HWND)wParam != hWnd)
        {
            if( !bFullscreen )
            {
                if( !bStress ) 
                {
                    Msg("***** PALETTE CHANGED, PAUSING GAME");
                    PauseGame();
                }
                else
                {
                    Msg("Lost palette but continuing");
                    SetWindowText( hWnd, OUR_APP_NAME 
                                 " - palette changed COLORS PROBABLY WRONG" );
                }
            }
        }
        break;


    case WM_DISPLAYCHANGE:
        break;


    case WM_CREATE:
        break;


    case WM_SETCURSOR:
        if (bFullscreen && bIsActive)
        {
            SetCursor(NULL);
            return TRUE;
        }
        break;


    case WM_SYSKEYUP:
        switch( wParam )
        {
        // handle ALT+ENTER (fullscreen)
        case VK_RETURN:
            bFullscreen = !bFullscreen;
            ExitGame();
            DDDisable(TRUE);        // destroy DirectDraw object
            GameMode.cx = 320;
            GameMode.cy = 200;
            InitGame();
            break;
        }
        break;
 
    case WM_KEYDOWN:
        switch( wParam )
        {
        case VK_NUMPAD5:
            lastInput=KEY_STOP;
            break;
        case VK_DOWN:
        case VK_NUMPAD2:
            lastInput=KEY_DOWN;
            break;
        case VK_LEFT:
        case VK_NUMPAD4:
            lastInput=KEY_LEFT;
            break;
        case VK_RIGHT:
        case VK_NUMPAD6:
            lastInput=KEY_RIGHT;
            break;
        case VK_UP:
        case VK_NUMPAD8:
            lastInput=KEY_UP;
            break;
        case VK_HOME:
        case VK_NUMPAD7:
            lastInput=KEY_JUMP;
            break;
        case VK_NUMPAD3:
            lastInput=KEY_THROW;
            break;
        case VK_F5:
            bShowFrameCount = !bShowFrameCount;
            if( bShowFrameCount )
            {
              dwFrameCount = 0;
              dwFrameTime = timeGetTime();
            }
        break;


        case VK_F6:
            {
            static i;
            //
            // find our current mode in the mode list
            //
            if(bFullscreen)
            {
                for (i=0; i<NumModes; i++)
                {
                    if (ModeList[i].bpp == (int)GameBPP &&
                        ModeList[i].w   == GameSize.cx &&
                        ModeList[i].h   == GameSize.cy)
                    {
                        break;
                    }
                }
            }else
            {
                for (i=0; i<NumModes; i++)
                {
                    if (ModeList[i].w   == GameSize.cx &&
                        ModeList[i].h   == GameSize.cy)
                    {
                        break;
                    }
                }
            }
            //
            // now step to the next mode, wrapping to the first one.
            //
            if (++i >= NumModes)
            {
                i = 0;
            }
            Msg("ModeList %d %d",i,NumModes);
            GameMode.cx = ModeList[i].w;
            GameMode.cy = ModeList[i].h;
            GameBPP     = ModeList[i].bpp;
            bStretch    = FALSE;
            InitGame();
            }
            break;
        case VK_F7:
            GameBPP = GameBPP == 8 ? 16 : 8;
            InitGame();
            break;


        case VK_F8:
            if (bFullscreen)
            {
                bStretch = !bStretch;
                InitGame();
            }
            else
            {
                RECT rc;


                GetClientRect(hWnd, &rc);


                bStretch = (rc.right  != GameSize.cx) ||
                           (rc.bottom != GameSize.cy);


                if (bStretch = !bStretch)
                    SetRect(&rc, 0, 0, GameMode.cx*2, GameMode.cy*2);
                else
                    SetRect(&rc, 0, 0, GameMode.cx, GameMode.cy);


                AdjustWindowRectEx(&rc,
                    GetWindowStyle(hWnd),
                    GetMenu(hWnd) != NULL,
                    GetWindowExStyle(hWnd));


                SetWindowPos(hWnd, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top,
                    SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
            }
            break;
        case VK_F9:           DevIndex ++;
            bUseEmulation = FALSE;
            if (DevIndex >= MaxDevIndex)
                DevIndex = 0;


            ExitGame();
            DDDisable(TRUE);        // destroy DirectDraw object
            InitGame();
            break;
        case VK_F4:
            // treat F4 like ALT+ENTER (fullscreen)
            PostMessage(hWnd, WM_SYSKEYUP, VK_RETURN, 0);
            break;


        case VK_F3:
            bPaused = !bPaused;
            break;


        case VK_ESCAPE:
        case VK_F12:
            PostMessage(hWnd, WM_CLOSE, 0, 0);
            return 0;
        }
        break;


    case WM_PAINT:
        hdc = BeginPaint( hWnd, &ps );
        if (bPaused)
        {
            char *sz = "Game is paused, this is not a bug.";
            TextOut(ps.hdc, 0, 0, sz, lstrlen(sz));
        }
        EndPaint( hWnd, &ps );
        return 1;


    case WM_DESTROY:
        hWndMain = NULL;
        lastInput=0;
        DestroyGame();          // end of game
        DDDisable(TRUE);        // destroy DirectDraw object
        PostQuitMessage( 0 );
        break;
    }


    return DefWindowProc(hWnd, message, wParam, lParam);


} /* MainWndProc */




第三节 游戏初始化


游戏初始化包括三部分:
1.Windows的初始化。
2.游戏工具的初始化。
3.游戏的初始化。


在这三部分中Windows的初始化,也就是对窗口的注册、定义和初始化。我们在Win- dows编程基础中已经谈过,这里就不再详述了。


游戏工具的初始化,是指对游戏程序中用到的工具进行初始化。对于一个游戏而言我们需要针对游戏的需要使用一些对图形或声音管理绘制或播放的以及其他功能的系统,这些系统就是我们所说的游戏工具(有时人们也称之为游戏引擎)。这些工具有时是由一些游戏公司提供的,比如MICROSOFT的DirectX5 SDK,有时是自己针对游戏需要编制的或使用上一部作品中用过的系统。在本例程中是指对Directdraw和DirectSound进行初始化,您可以通过阅读DDinit()和  InitSound()以及InitGame()函数的一部分的原代码以及阅读我们提供有关Directdraw和DirectSound的章节来理解。


DDinit()和  InitSound()以及InitGame()函数的代码:
/*
 * InitGame
 *
 * Initializing current game
 */
BOOL InitGame( void )
{
    ExitGame();


    GameSize = GameMode;


    /*
     * initialize sound
     */
    InitSound( hWndMain );


    /*
     * init DirectDraw, set mode, ...
     * NOTE GameMode might be set to 640x480 if we cant get the asked for mode.
     */
    if( !PreInitializeGame() )
    {
        return FALSE;
    }


    if (bStretch && bFullscreen)
    {
        GameSize.cx     = GameMode.cx / 2;
        GameSize.cy     = GameMode.cy / 2;
        GameRect.left   = GameMode.cx - GameSize.cx;
        GameRect.top    = GameMode.cy - GameSize.cy;
        GameRect.right  = GameMode.cx;
        GameRect.bottom = GameMode.cy;


        if (lpStretchBuffer)
            Msg("Stretching using a system-memory stretch buffer");
        else
            Msg("Stretching using a VRAM->VRAM blt");
    }
    else
    {
        GameRect.left   = (GameMode.cx - GameSize.cx) / 2;
        GameRect.top    = (GameMode.cy - GameSize.cy) / 2;
        GameRect.right  = GameRect.left + GameSize.cx;
        GameRect.bottom = GameRect.top + GameSize.cy;
    }


    /*
     * setup our palette
     */
    if( GameBPP == 8 )
    {
        lpPalette = ReadPalFile( NULL );        // create a 332 palette


        if( lpPalette == NULL )
        {
            Msg( "Palette create failed" );
            return FALSE;
        }


        IDirectDrawSurface_SetPalette( lpFrontBuffer, lpPalette );
    }


    /*
     *  load all the art and things.
     */
    if( !InitializeGame() )
    {
        return FALSE;
    }


    /*
     * init our code to draw the FPS
     */
    makeFontStuff();


    /*
     * spew some stats
     */
    {
        DDCAPS ddcaps;
        ddcaps.dwSize = sizeof( ddcaps );
        IDirectDraw_GetCaps( lpDD, &ddcaps, NULL );
        Msg( "Total=%ld, Free VRAM=%ld", ddcaps.dwVidMemTotal, ddcaps.dwVidMemFree );
        Msg( "Used = %ld", ddcaps.dwVidMemTotal- ddcaps.dwVidMemFree );   }


    return TRUE;


} /* InitGame */


/*
 * InitSound
 *
 * Sets up the DirectSound object and loads all sounds into secondary
 * DirectSound buffers.  Returns FALSE on error, or TRUE if successful
 */
BOOL InitSound( HWND hwndOwner )
{
    int idx;
    DSBUFFERDESC dsBD;
    IDirectSoundBuffer *lpPrimary;


    DSEnable(hwndOwner);


    if (lpDS == NULL)
        return TRUE;


    /*
     * Load all sounds -- any that can't load for some reason will have NULL
     * pointers instead of valid SOUNDEFFECT data, and we will know not to
     * play them later on.
     */
    for( idx = 0; idx < NUM_SOUND_EFFECTS; idx++ )
    {
        if (SoundLoadEffect((EFFECT)idx))
        {
            DSBCAPS  caps;


            caps.dwSize = sizeof(caps);
            IDirectSoundBuffer_GetCaps(lpSoundEffects[idx], &caps);


            if (caps.dwFlags & DSBCAPS_LOCHARDWARE)
                Msg( "Sound effect %s in hardware", szSoundEffects[idx]);
            else
                Msg( "Sound effect %s in software", szSoundEffects[idx]);
        }
        else
        {
            Msg( "cant load sound effect %s", szSoundEffects[idx]);
        }
    }


    /*
     * get the primary buffer and start it playing
     *
     * by playing the primary buffer, DirectSound knows to keep the
     * mixer active, even though we are not making any noise.
     */


    ZeroMemory( &dsBD, sizeof(DSBUFFERDESC) );
    dsBD.dwSize = sizeof(dsBD);
    dsBD.dwFlags = DSBCAPS_PRIMARYBUFFER;


    if (SUCCEEDED(IDirectSound_CreateSoundBuffer(lpDS, &dsBD, &lpPrimary, NULL)))
    {
        if (!SUCCEEDED(IDirectSoundBuffer_Play(lpPrimary, 0, 0, DSBPLAY_LOOPING)))
        {
            Msg("Unable to play Primary sound buffer");
        }


        IDirectSoundBuffer_Release(lpPrimary);
    }
    else
    {
        Msg("Unable to create Primary sound buffer");
    }


    return TRUE;


} /* InitSound */




/*
 * DDInit
 */
BOOL DDInit( void )
{
    DirectDrawEnumerate(&DDEnumCallback,NULL);
    DDEnumCallback((GUID *)DDCREATE_EMULATIONONLY, "Hardware Emulation Layer", "", NULL);
    return TRUE;
}




游戏的初始化是指调入游戏中的图象、声音等资源和游戏中的角色、道具的属性、
初始位置、状态等并画出初始画面的图象以及游戏的系统、操作方法的定义、游戏的规则等。比如说在一个RPG游戏之中,在游戏开始时内存中就应装入主角的图象组(比如走时的几幅图,状态对话框中的图)、状态(级别、HP、MP、DP等等)、属性(性别、职业等)等,描述整个游戏世界的图,NPC的各种属性、游戏的规则(各种攻击方式的效果、升级所需的经验值等)等等,总之要装入您所设计的游戏世界的一切。在例程的InitGame()中调用的函数InitializeGame()就完成了这个任务。


InitializeGame()的代码:


/*
 * InitializeGame
 */
BOOL InitializeGame ( void )
{
    Splash();


    hBitmapList = LoadBitmaps();
    if( hBitmapList == NULL )
    {
        return FALSE;
    }


    InitTiles( &hTileList, hBitmapList, C_TILETOTAL );
        
    InitPlane( &hForeground, &hForePosList, "FORELIST", C_FORE_W, C_FORE_H, C_FORE_DENOM );
    TilePlane( hForeground, hTileList, hForePosList );


    InitPlane( &hMidground, &hMidPosList, "MIDLIST", C_MID_W, C_MID_H, C_MID_DENOM );
    TilePlane( hMidground, hTileList, hMidPosList );


    InitPlane( &hBackground, &hBackPosList, "BACKLIST", C_BACK_W, C_BACK_H, C_BACK_DENOM );
    TilePlane( hBackground, hTileList, hBackPosList );


    InitSurface( &hSurfaceList, "SURFLIST", C_FORE_W, C_FORE_H );
    SurfacePlane( hForeground, hSurfaceList );


    InitFox( &hFox, hBitmapList );
    InitBear( &hBear, hBitmapList );
    InitApple( &hApple, hBitmapList );


    DDClear();      // clear all the backbuffers.


    return TRUE;


} /* InitializeGame */




在现在的大部分游戏中游戏世界中的每个组成部分通常是用结构或类分别定义储存的。比如一个即时战略游戏中,各种建筑物放在一个类中,而每个建筑物的属性就放在该类的一个子类中;各种武器放在一个类中,每种武器放在该类的一个子类中。


class Weapon
{
WEAPON_TYPE Type;
char Name;
DWORD Id;
WORD Defend;
WORD Attack;
...
};


第四节 游戏内部循环


游戏内部循环包括刷新游戏单位、画游戏单位两部分。它的实现过程是这样的:检测状态,作出判断,绘出新图。看起来这并不是一个循环,对吗?是的,游戏内部循环并不是一个真正的循环,它实际上是由消息循环完成循环作用的。让我们从例程中看看这是如何实现的吧!


在消息循环中的第一个else if语句是这样的
else if (!bPaused && (bIsActive || !bFullscreen))
        {
            ProcessFox(lastInput);
            lastInput=0;
        }


if 后的表达式的含义是:当游戏没有被暂停时(bPause为FLASE)或以窗口模式显示(bFullscreen为FLASE)且窗口处于活动状态(bIsActive为TRUE)时执行
       {
            ProcessFox(lastInput);
            lastInput=0;
        }
语句段。而函数ProcessFox(lastInput)通过调用ProcessInput()和NewGameFrame( )达成刷新游戏单元和重画新图的功能。(这三个函数的原代码见例程foxbear.c和gameproc.c两文件)。


ProcessFox(lastInput):


/*
 * ProcessFox
 */
BOOL ProcessFox(SHORT sInput)
{
    if ((lpFrontBuffer && IDirectDrawSurface_IsLost(lpFrontBuffer) == DDERR_SURFACELOST) ||
        (lpBackBuffer && IDirectDrawSurface_IsLost(lpBackBuffer) == DDERR_SURFACELOST))
    {
        if (!RestoreGame())
        {
            PauseGame();
            return FALSE;
        }
    }




    ProcessInput(sInput);
    NewGameFrame();
    return TRUE;


} /* ProcessFox */


static HFONT hFont;


DWORD   dwFrameCount;
DWORD dwFrameTime;
DWORD dwFrames;
DWORD   dwFramesLast;
SIZE    sizeFPS;
SIZE sizeINFO;
int FrameRateX;
char szFPS[]   = "FPS %02d";
char    szINFO[]  = "%dx%dx%d%s     F6=mode F8=x2 ALT+ENTER=Window";
char    szINFOW[] = "%dx%dx%d%s     F6=mode F8=x2 ALT+ENTER=Fullscreen";


char szFrameRate[128];
char szInfo[128];


COLORREF InfoColor      = RGB(0,152,245);
COLORREF FrameRateColor = RGB(255,255,0);
COLORREF BackColor = RGB(255,255,255);


/*
 * initNumSurface
 */
void initNumSurface( void )
{
    HDC        hdc;
    RECT        rc;
    int         len;


    dwFramesLast = 0;


    len = wsprintf(szFrameRate, szFPS, 0, 0);


    if( lpFrameRate && IDirectDrawSurface_GetDC(lpFrameRate, &hdc ) == DD_OK )
    {
        SelectObject(hdc, hFont);
        SetTextColor(hdc, FrameRateColor);
        SetBkColor(hdc, BackColor);
        SetBkMode(hdc, OPAQUE);
        SetRect(&rc, 0, 0, 10000, 10000);
        ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, szFrameRate, len, NULL);
        GetTextExtentPoint(hdc, szFrameRate, 4, &sizeFPS);
        FrameRateX = sizeFPS.cx;
        GetTextExtentPoint(hdc, szFrameRate, len, &sizeFPS);


        IDirectDrawSurface_ReleaseDC(lpFrameRate, hdc);
    }


    if (bFullscreen)
        len = wsprintf(szInfo, szINFO,
                       GameSize.cx, GameSize.cy, GameBPP,bStretch ? " x2" : "");
    else
        len = wsprintf(szInfo, szINFOW,
                       GameSize.cx, GameSize.cy, GameBPP,bStretch ? " x2" : "");


    if( lpInfo && IDirectDrawSurface_GetDC(lpInfo, &hdc ) == DD_OK )
    {
        SelectObject(hdc, hFont);
        SetTextColor(hdc, InfoColor);
        SetBkColor(hdc, BackColor);
        SetBkMode(hdc, OPAQUE);
        SetRect(&rc, 0, 0, 10000, 10000);
        ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, szInfo, len, NULL);
        GetTextExtentPoint(hdc, szInfo, len, &sizeINFO);


        IDirectDrawSurface_ReleaseDC(lpInfo, hdc);
    }


} /* initNumSurface */


NewGameFrame( ):


/*
 * NewGameFrame
 */
int NewGameFrame( void )
{


    SetSpriteX( hFox, 0, P_AUTOMATIC );
    SetSpriteY( hFox, 0, P_AUTOMATIC );
                                    
    SetPlaneVelX( hBackground, GetSpriteVelX(hFox), P_ABSOLUTE );
    SetPlaneVelX( hMidground,  GetSpriteVelX(hFox), P_ABSOLUTE );
    SetPlaneVelX( hForeground, GetSpriteVelX(hFox), P_ABSOLUTE );


    SetPlaneX( hBackground, 0, P_AUTOMATIC );
    SetPlaneX( hMidground,  0, P_AUTOMATIC );
    SetPlaneX( hForeground, 0, P_AUTOMATIC );


    SetSpriteX( hBear, 0, P_AUTOMATIC );
    SetSpriteX( hApple, 0, P_AUTOMATIC );
    SetSpriteY( hApple, 0, P_AUTOMATIC );


    /*
     * once all sprites are processed, display them
     *
     * If we are using destination transparency instead of source
     * transparency, we need to paint the background with the color key
     * and then paint our sprites and planes in reverse order.
     *
     * Since destination transparency will allow you to only write pixels
     * on the destination if the transparent color is present, reversing
     * the order (so that the topmost bitmaps are drawn first instead of
     * list) causes everything to come out ok.
     */
    if( bTransDest )
    {
        gfxFillBack( dwColorKey );


        DisplayFrameRate();


        DisplaySprite( hBuffer, hApple, GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hBear,  GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hFox,   GetPlaneX(hForeground) );


        DisplayPlane( hBuffer, hForeground );
        DisplayPlane( hBuffer, hMidground );
        DisplayPlane( hBuffer, hBackground );
    }
    else
    {
        DisplayPlane( hBuffer, hBackground );
        DisplayPlane( hBuffer, hMidground );
        DisplayPlane( hBuffer, hForeground );
    
        DisplaySprite( hBuffer, hFox,   GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hBear,  GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hApple, GetPlaneX(hForeground) );


        DisplayFrameRate();
    }
                                                      
    gfxSwapBuffers();


    return 0;


} /* NewGameFrame */




第五节 刷新游戏单元
刷新游戏单位的作用是在每一桢刷新游戏单位的状态。请您先阅读一下下面的ProcessInput()函数的部分代码,然后再看看下面这两个例子。


ProcessInput()函数的部分代码:


/*
 * ProcessInput
 */
BOOL ProcessInput( SHORT input )
{
    static BOOL fBearPlaying = FALSE;
    LONG      foxSpeedX;
    LONG      foxSpeedY;
    LONG      foxX;
    LONG      foxY;
    LONG      bearX;
    LONG      bearY;
    LONG      appleX;
    LONG      appleY;
    ACTION    foxAction;
    DIRECTION foxDir;
    BOOL      cont = TRUE;


    foxSpeedX = GetSpriteVelX( hFox );
    foxAction = GetSpriteAction( hFox );
    foxDir    = GetSpriteDirection( hFox );


    if( (GetSpriteActive(hFox) == FALSE) && (input != 4209) )
    {
        input = 0;
    }
    switch( input )
    {
    case KEY_DOWN:
        if( foxAction == STOP )
        {
            break;
        }
        else if( foxAction == STILL )
        {
            SetSpriteAction( hFox, CROUCH, SAME );
        }
        else if( foxAction == WALK )
        {
            SetSpriteAction( hFox, CROUCHWALK, SAME );
        }
        break;


    case KEY_LEFT:
        if( foxAction == STOP )
        {
            break;
        }
        else if( foxSpeedX == 0 )
        {
            if( foxAction == STILL )
            {
              if( foxDir == RIGHT )
              {
                  ChangeSpriteDirection( hFox );
                  SetPlaneSlideX( hForeground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hMidground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hBackground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneIncremX( hForeground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hBackground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hMidground, C_BOUNDINCREM, P_ABSOLUTE );
              }
              else
              {
                  SetSpriteAction( hFox, WALK, LEFT );
                  SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
                  SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
              }
            }
            else if( foxAction == CROUCH )
            {
              if( foxDir == RIGHT )
              {
                  ChangeSpriteDirection( hFox );
                  SetPlaneSlideX( hForeground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hMidground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneSlideX( hBackground, -C_BOUNDDIF, P_RELATIVE );
                  SetPlaneIncremX( hForeground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hBackground, C_BOUNDINCREM, P_ABSOLUTE );
                  SetPlaneIncremX( hMidground, C_BOUNDINCREM, P_ABSOLUTE );
              }
              else
              {
                  SetSpriteAction( hFox, CROUCHWALK, LEFT );
                  SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
                  SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
              }
            }
            else
            {
              SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
            }
        } else {
            SetSpriteVelX( hFox, -C_FOX_XMOVE, P_RELATIVE );
        }
        break;


    case KEY_RIGHT:
        .
        .
        .
    case KEY_STOP:
        if( foxAction == STOP )
        {
            break;
        }
        else if( (foxAction == RUN) || (foxAction == BLURR) )
        {
            SetSpriteAction( hFox, STOP, SAME );
            SetSpriteAccX( hFox, -foxSpeedX / 25, P_ABSOLUTE );
            SoundPlayEffect( SOUND_STOP );
        } else {
            SetSpriteVelX( hFox, 0, P_ABSOLUTE );
        }
        break;


    case KEY_UP: 
        if( foxAction == STOP )
        {
            break;
        }
        else if( foxAction == CROUCH )
        {
            SetSpriteAction( hFox, STILL, SAME );
        }
        else if( foxAction == CROUCHWALK )
        {
            SetSpriteAction( hFox, WALK, SAME );
        }
        break;


    case KEY_JUMP:
        if( foxAction == STOP )
        {
            break;
        }
        else
        if( (foxAction == STILL) || (foxAction == WALK) ||
            (foxAction == RUN) || (foxAction == CROUCH) ||
            (foxAction == CROUCHWALK) )
        {
            SetSpriteAction( hFox, JUMP, SAME );
            SetSpriteSwitchType( hFox, TIME );
            SetSpriteSwitch( hFox, C_FOX_JUMPSWITCH, P_ABSOLUTE );
            SetSpriteVelY( hFox, -C_FOX_JUMPMOVE, P_ABSOLUTE );
            SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
            SoundPlayEffect( SOUND_JUMP );
        }
        break;


    case KEY_THROW:
        if( foxAction == STOP )
        {
            break;
        }
        else if( (foxAction == STILL) || (foxAction == WALK) ||
               (foxAction == RUN) || (foxAction == CROUCH) ||
               (foxAction == CROUCHWALK) )
        {
            SetSpriteAction( hFox, THROW, SAME );
            SetSpriteSwitch( hFox, C_FOX_THROWSWITCH, P_ABSOLUTE );
            SetSpriteVelX( hFox, 0, P_ABSOLUTE );
            SetSpriteSwitchType( hFox, TIME );
        }
        else if( foxAction == JUMP )
        {
            SetSpriteAccY( hFox, 0, P_ABSOLUTE );
            SetSpriteSwitch( hFox, C_FOX_THROWSWITCH, P_ABSOLUTE );
            SetSpriteAction( hFox, JUMPTHROW, SAME );
            SetSpriteVelY( hFox, 0, P_ABSOLUTE );
            SetSpriteSwitchDone( hFox, FALSE );
            SetSpriteSwitchForward( hFox, TRUE );
        }
        break;


    default: 
        break;
    }


    /*
     * Fox actions follow...
     */
    if( GetSpriteActive(hFox) == FALSE )
    {
        goto bearActions;
    }


    if( abs(GetSpriteVelX( hFox )) < C_FOX_XMOVE )
    {
        SetSpriteVelX( hFox, 0, P_ABSOLUTE );
    }


    foxAction = GetSpriteAction( hFox );


    if( GetSpriteVelY(hFox) == 0 )
    {
        if( GetSurface( hForeground, hFox ) == FALSE )
        {
            if( (foxAction == WALK) || (foxAction == RUN) ||
            (foxAction == CROUCHWALK) )
            {
              SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
            }
            else if( foxAction == STOP )
            {
              SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
              SetSpriteAccX( hFox, 0, P_ABSOLUTE );
            }
        }
    }
    else if( GetSpriteVelY(hFox) > 2 * C_UNIT )
    {
        if( (foxAction == WALK) || (foxAction == RUN) ||
            (foxAction == CROUCHWALK) )
        {
            SetSpriteSwitchForward( hFox, FALSE );
            SetSpriteAction( hFox, JUMP, SAME );
            SetSpriteSwitchType( hFox, TIME );
            SetSpriteSwitch( hFox, C_FOX_JUMPSWITCH, P_ABSOLUTE );
        }
        if( foxAction == STOP )
        {
            SetSpriteAction( hFox, STUNNED, SAME );
            SetSpriteAccX( hFox, -GetSpriteVelX(hFox) / 25, P_ABSOLUTE );
            SoundPlayEffect( SOUND_STUNNED );
        }
    }
    
    foxSpeedX = GetSpriteVelX( hFox );
    foxSpeedY = GetSpriteVelY( hFox );
    foxAction = GetSpriteAction( hFox );
    foxDir    = GetSpriteDirection( hFox );


    switch( foxAction ) {
    case STUNNED:
        if( (GetSpriteVelY(hFox) >= 0) &&
            (!GetSurface( hForeground, hFox ) == FALSE) )
        {
            SetSpriteAccY( hFox, 0, P_ABSOLUTE );
            SetSpriteAction( hFox, STOP, SAME );
            SetSpriteVelY( hFox, 0, P_ABSOLUTE );
            SetSpriteAccX( hFox, -foxSpeedX / 25, P_ABSOLUTE );
            // SetSurface( hForeground, hFox );
            SoundPlayEffect( SOUND_STOP );
        }
        break;


    case CROUCHWALK:
        if( foxSpeedX == 0 )
        {
            SetSpriteAction( hFox, CROUCH, SAME );
        }
        else if( foxSpeedX > C_FOX_WALKMOVE )
        {
            SetSpriteVelX( hFox, C_FOX_WALKMOVE, P_ABSOLUTE );
        }
        else if( foxSpeedX < -C_FOX_WALKMOVE )
        {
            SetSpriteVelX( hFox, -C_FOX_WALKMOVE, P_ABSOLUTE );
        }
        break;
        
    case STOP:  
        if( foxSpeedX == 0 )
        {
            SetSpriteAction( hFox, STILL, SAME );
            SetSpriteAccX( hFox, 0, P_ABSOLUTE );
        }
        break;
        
    case RUN:
        if( (foxSpeedX < C_FOX_WALKTORUN ) && (foxSpeedX > 0) )
        {
            SetSpriteAction( hFox, WALK, RIGHT );
            SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
        }
        else if( foxSpeedX > C_FOX_RUNTOBLURR )
        {
            SetSpriteAction( hFox, BLURR, RIGHT );
            SetSpriteSwitch( hFox, C_FOX_BLURRSWITCH, P_ABSOLUTE );
        }
        else if( (foxSpeedX > -C_FOX_WALKTORUN ) && (foxSpeedX < 0) )
        {
            SetSpriteAction( hFox, WALK, LEFT );
            SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
        }
        else if( foxSpeedX < -C_FOX_RUNTOBLURR )
        {
            SetSpriteAction( hFox, BLURR, LEFT );
            SetSpriteSwitch( hFox, C_FOX_BLURRSWITCH, P_ABSOLUTE );
        }
        break;


    case WALK:
        if( foxSpeedX == 0 )
        {
            SetSpriteAction( hFox, STILL, SAME );
        }
        else if( foxSpeedX > C_FOX_WALKTORUN )
        {
            SetSpriteAction( hFox, RUN, RIGHT );
            SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
        }
        else if( foxSpeedX < -C_FOX_WALKTORUN )
        {
            SetSpriteAction( hFox, RUN, LEFT );
            SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
        }
        break;


    case BLURR:
        if( (foxSpeedX < C_FOX_RUNTOBLURR ) && (foxSpeedX > C_FOX_WALKTORUN) )
        {
            SetSpriteAction( hFox, RUN, RIGHT );
            SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
        }
        else if( (foxSpeedX > -C_FOX_RUNTOBLURR ) && (foxSpeedX < -C_FOX_WALKTORUN) )
        {
            SetSpriteAction( hFox, RUN, LEFT );
            SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
        }
        break;


    case JUMPTHROW:
        if( !GetSpriteSwitchDone(hFox) == FALSE )
        {
            SetSpriteSwitchForward( hFox, FALSE );
            SetSpriteAction( hFox, JUMP, SAME );
            SetSpriteSwitch( hFox, C_FOX_JUMPSWITCH, P_ABSOLUTE );
            SetSpriteSwitchDone( hFox, FALSE );
            SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
            SoundPlayEffect( SOUND_THROW );
        }
        else
        if( (GetSpriteBitmap(hFox) == 1) &&
            (GetSpriteDirection(hFox) == RIGHT) )
        {
            SetSpriteActive( hApple, TRUE );
            SetSpriteX( hApple, GetSpriteX(hFox) + 60 * C_UNIT, P_ABSOLUTE );
            SetSpriteY( hApple, GetSpriteY(hFox) + 30 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelX( hApple, 8 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
            SetSpriteAccX( hApple, 0, P_ABSOLUTE );
            SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
        }
        else if( (GetSpriteBitmap(hFox) == 1) &&
                 (GetSpriteDirection(hFox) == LEFT) )
        {
            SetSpriteActive( hApple, TRUE );
            SetSpriteX( hApple, GetSpriteX(hFox) + 15 * C_UNIT, P_ABSOLUTE );
            SetSpriteY( hApple, GetSpriteY(hFox) + 30 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelX( hApple, -8 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
            SetSpriteAccX( hApple, 0, P_ABSOLUTE );
            SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
        }
        break;


    case THROW:
        if( !GetSpriteSwitchDone(hFox) == FALSE )
        {
            SetSpriteAction( hFox, STILL, SAME );
            SetSpriteSwitchType( hFox, HOR );
            SetSpriteSwitch( hFox, 0, P_ABSOLUTE );
            SetSpriteSwitchDone( hFox, FALSE );
            SoundPlayEffect( SOUND_THROW );
        }
        else if( (GetSpriteBitmap(hFox) == 1) &&
                 (GetSpriteDirection(hFox) == RIGHT) )
        {
            SetSpriteActive( hApple, TRUE );
            SetSpriteX( hApple, GetSpriteX(hFox) + 60 * C_UNIT, P_ABSOLUTE );
            SetSpriteY( hApple, GetSpriteY(hFox) + 50 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelX( hApple, 8 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
            SetSpriteAccX( hApple, 0, P_ABSOLUTE );
            SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
        }
        else if( (GetSpriteBitmap(hFox) == 1) &&
                 (GetSpriteDirection(hFox) == LEFT) )
        {
            SetSpriteActive( hApple, TRUE );
            SetSpriteX( hApple, GetSpriteX(hFox) + 20 * C_UNIT, P_ABSOLUTE );
            SetSpriteY( hApple, GetSpriteY(hFox) + 50 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelX( hApple, -8 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelY( hApple, -4 * C_UNIT, P_ABSOLUTE );
            SetSpriteAccX( hApple, 0, P_ABSOLUTE );
            SetSpriteAccY( hApple, C_UNIT / 4, P_ABSOLUTE );
        }
        break;


    case JUMP:
        if( (foxSpeedY >= 0) && (!GetSpriteSwitchForward( hFox ) == FALSE) )
        {
            SetSpriteSwitchForward( hFox, FALSE );
        }
        else if( GetSpriteSwitchForward( hFox ) == FALSE )
        {
            if( (!GetSurface( hForeground, hFox ) == FALSE) ||
            (!GetSurface( hForeground, hFox ) == FALSE) )
            {
              if( foxSpeedX >= C_FOX_RUNMOVE )
              {
                  SetSpriteAction( hFox, RUN, SAME );
                  SetSpriteSwitch( hFox, C_FOX_RUNSWITCH, P_ABSOLUTE );
              }
              else if( foxSpeedX == 0 )
              {
                  SetSpriteAction( hFox, STILL, SAME );
                  SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
              }
              else
              {
                  SetSpriteAction( hFox, WALK, SAME );
                  SetSpriteSwitch( hFox, C_FOX_WALKSWITCH, P_ABSOLUTE );
              }


              SetSpriteAccY( hFox, 0, P_ABSOLUTE );
              SetSpriteVelY( hFox, 0, P_ABSOLUTE );
              SetSpriteSwitchType( hFox, HOR );     
              SetSpriteSwitchForward( hFox, TRUE );
// SetSurface( hForeground, hFox );
              SetSpriteSwitchDone( hFox, FALSE );
            }
        }
        break;


    }


    /*
     * Bear Actions
     */
    bearActions:


    foxX   = GetSpriteX( hFox );
    foxY   = GetSpriteY( hFox );
    bearX  = GetSpriteX( hBear );
    bearY  = GetSpriteY( hBear );
    appleX = GetSpriteX( hApple );
    appleY = GetSpriteY( hApple );


    switch( GetSpriteAction( hBear ) ) {
    case STRIKE:
        if( GetSpriteBitmap( hBear ) == 2 )
        {
            if( (bearX > foxX - C_UNIT * 30) && (bearX < foxX + C_UNIT * 40) &&
                (bearY < foxY + C_UNIT * 60) )
            {
              SetSpriteActive( hFox, FALSE );
                if( !fBearPlaying )
                {
                    SoundPlayEffect( SOUND_BEARSTRIKE );
                    fBearPlaying = TRUE;
                }
            }
            else
            {
              SetSpriteAction( hBear, MISS, SAME );
              SetSpriteSwitch( hBear, C_BEAR_MISSSWITCH, P_ABSOLUTE );
              SetSpriteSwitchDone( hBear, FALSE );
            }
        }
        else if( !GetSpriteSwitchDone( hBear ) == FALSE )
        {
            SetSpriteAction( hBear, CHEW, SAME );
            SetSpriteSwitchDone( hBear, FALSE );
            chewCount = 0;
            fBearPlaying = FALSE;
        }
        break;


    case MISS:
        if( !fBearPlaying )
        {
            SoundPlayEffect( SOUND_BEARMISS );
            fBearPlaying = TRUE;
        }
        if( !GetSpriteSwitchDone( hBear ) == FALSE )
        {
            SetSpriteAction( hBear, WALK, SAME );
            SetSpriteVelX( hBear, -C_BEAR_WALKMOVE, P_ABSOLUTE );
            SetSpriteSwitch( hBear, C_BEAR_WALKSWITCH, P_ABSOLUTE );
            SetSpriteSwitchType( hBear, HOR );
            fBearPlaying = FALSE;
        }
        break;


    case WALK:
        if( (!GetSpriteActive(hApple) == FALSE) && (appleX > bearX) &&
            (appleX > bearX + 80 * C_UNIT) && (appleY > bearY + 30 * C_UNIT) )
        {
            SetSpriteAction( hBear, STRIKE, SAME );
            SetSpriteVelX( hBear, 0, P_ABSOLUTE );
            SetSpriteSwitchType( hBear, TIME );
            SetSpriteSwitch( hBear, C_BEAR_STRIKESWITCH, P_ABSOLUTE );           SetSpriteSwitchDone( hBear, FALSE );
        }
        else if( (bearX > foxX - C_UNIT * 30) &&
                 (bearX < foxX + C_UNIT * 30) &&
               (bearY < foxY + C_UNIT * 60) )
        {
            SetSpriteAction( hBear, STRIKE, SAME );
            SetSpriteVelX( hBear, 0, P_ABSOLUTE );
            SetSpriteSwitchType( hBear, TIME );
            SetSpriteSwitch( hBear, C_BEAR_STRIKESWITCH, P_ABSOLUTE );
            SetSpriteSwitchDone( hBear, FALSE );
        }
        break;


    case CHEW:
        ++chewCount;
        if( chewCount >= 200 )
        {
            SetSpriteAction( hBear, STRIKE, SAME );
            SetSpriteSwitch( hBear, C_BEAR_STRIKESWITCH, P_ABSOLUTE );
            SetSpriteVelX( hBear, 0, P_ABSOLUTE );
            SetSpriteSwitchDone( hBear, FALSE );


            if( GetSpriteDirection(hFox) == RIGHT )
            {
              SetPlaneSlideX( hForeground, -C_BOUNDDIF, P_RELATIVE );
              SetPlaneSlideX( hMidground,  -C_BOUNDDIF, P_RELATIVE );
              SetPlaneSlideX( hBackground, -C_BOUNDDIF, P_RELATIVE );
            }


            chewDif = GetSpriteX(hFox);


            SetSpriteActive( hFox, TRUE );
            SetSpriteAction( hFox, STUNNED, LEFT );
            SetSpriteX( hFox, GetSpriteX(hBear), P_ABSOLUTE );
            SetSpriteY( hFox, GetSpriteY(hBear), P_ABSOLUTE );
            SetSpriteAccX( hFox, 0, P_ABSOLUTE );
            SetSpriteAccY( hFox, C_UNIT / 2, P_ABSOLUTE );
            SetSpriteVelX( hFox, -8 * C_UNIT, P_ABSOLUTE );
            SetSpriteVelY( hFox, -10 * C_UNIT, P_ABSOLUTE );
            SetSpriteSwitch( hFox, 0, P_ABSOLUTE );
            SoundPlayEffect( SOUND_STUNNED );


            chewDif -= GetSpriteX(hFox);


            SetPlaneSlideX( hForeground, -chewDif, P_RELATIVE );
            SetPlaneSlideX( hMidground, -chewDif, P_RELATIVE );
            SetPlaneSlideX( hBackground, -chewDif, P_RELATIVE );
            SetPlaneIncremX( hForeground, C_BOUNDINCREM, P_ABSOLUTE );
            SetPlaneIncremX( hMidground,  C_BOUNDINCREM, P_ABSOLUTE );
            SetPlaneIncremX( hBackground, C_BOUNDINCREM, P_ABSOLUTE );
        }
        break;
    }


    /*
     * Apple actions...
     */
    if( (GetSpriteVelY(hApple) != 0) && (GetSpriteY(hApple) >= 420 * C_UNIT) )
    {
        SetSpriteX( hApple, 0, P_ABSOLUTE );
        SetSpriteY( hApple, 0, P_ABSOLUTE );
        SetSpriteAccX( hApple, 0, P_ABSOLUTE );
        SetSpriteAccY( hApple, 0, P_ABSOLUTE );
        SetSpriteVelX( hApple, 0, P_ABSOLUTE );
        SetSpriteVelY( hApple, 0, P_ABSOLUTE );
        SetSpriteActive( hApple, FALSE );
    }


    return cont;


} /* ProcessInput */






在射击游戏中的子弹的发射,每一帧都要检测上一帧时子弹的位置a然后确定当前帧子弹的位置b然后将该位置传给重画游戏单元的部分,在当前帧b的位置贴上子弹的图象。


在即使战略游戏中两军对战时,程序在每一帧都要根据上一帧每个战斗单位的位置和该战斗单位移动的目的、到该目的之间的障碍物的位置以及一定的路径算法确定在当前帧该战斗单位的新位置;还有要取得在上一帧时该战斗单位的生命值和所受的打击次数及强度,以确定该战斗单位的生命值。


通过阅读ProcessInput()函数的代码,我想您一定已理解了刷新游戏单元的概念。而从上面的两个例子中,您也一定发现用例程的方法很难实现这两类游戏的要求。我们不可能对每一颗子弹,每一个战斗单位进行操作,而且我们并不知道游戏者会一次发射多少颗子弹也不知道游戏者会造多少个战斗单位。我们应该怎么办呢?


考虑到每一个战斗单位(或每一颗子弹)都有相似(或相同)的属性,那么我们可以采用结构数组来储存每一个战斗单位的位置和状态。这个办法好象可行!但是仔细想想,我们又遇到了上面谈到的问题我们并不知道游戏者会一次发射多少颗子弹也不知道游戏者会造多少个战斗单位。当然我们可以采用Age of Empire的方式----限制单位的数量(我并不是说Age of Empire采用的是这种办法)。但是这意味什么呢!意味着,如果我们限定数量为50的话,在游戏者只有一个士兵时,计算机却需要为这个士兵分配50倍的内存!而且游戏者还不一定造出50个士兵。显然这并不是一个好办法!


我们应该怎么办呢?链表!链表能满足我们的要求。


class Node
{
//双向链表的指针。
Node* Next;
Node* Pre;

//节点数据。
NODE_DATA data;
...
};


链表是一种结构体的集合。在链表中的每一个结构体都包含了一个元素或指针,它指向链表中的另一个结构体。这个指针用作两个结构体之间的联系。这个概念与数组有些相似,但它允许链表的动态增长。现在的游戏中凡是遇到这种问题的一般都是采用链表的。关于链表的更多的信息请阅读有关的资料。




第六节 画游戏单元
画游戏单位的作用是在每一桢往屏幕上画游戏单位的图象。


这就是本例程中画游戏单元的主函数:
/*
 * NewGameFrame
 */
int NewGameFrame( void )
{
//这里是设置游戏单元的位置:
    SetSpriteX( hFox, 0, P_AUTOMATIC );
    SetSpriteY( hFox, 0, P_AUTOMATIC );
                                    
    SetPlaneVelX( hBackground, GetSpriteVelX(hFox), P_ABSOLUTE );
    SetPlaneVelX( hMidground,  GetSpriteVelX(hFox), P_ABSOLUTE );
    SetPlaneVelX( hForeground, GetSpriteVelX(hFox), P_ABSOLUTE );


    SetPlaneX( hBackground, 0, P_AUTOMATIC );
    SetPlaneX( hMidground,  0, P_AUTOMATIC );
    SetPlaneX( hForeground, 0, P_AUTOMATIC );


    SetSpriteX( hBear, 0, P_AUTOMATIC );
    SetSpriteX( hApple, 0, P_AUTOMATIC );
    SetSpriteY( hApple, 0, P_AUTOMATIC );


//将游戏单元的图形贴到BackBuffer上:
    if( bTransDest )
    {
        gfxFillBack( dwColorKey );


        DisplayFrameRate();


        DisplaySprite( hBuffer, hApple, GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hBear,  GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hFox,   GetPlaneX(hForeground) );


        DisplayPlane( hBuffer, hForeground );
        DisplayPlane( hBuffer, hMidground );
        DisplayPlane( hBuffer, hBackground );
    }
    else
    {
        DisplayPlane( hBuffer, hBackground );
        DisplayPlane( hBuffer, hMidground );
        DisplayPlane( hBuffer, hForeground );
    
        DisplaySprite( hBuffer, hFox,   GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hBear,  GetPlaneX(hForeground) );
        DisplaySprite( hBuffer, hApple, GetPlaneX(hForeground) );


        DisplayFrameRate();
    }


//更新前景:                                                      
    gfxSwapBuffers();


    return 0;


} /* NewGameFrame */


画游戏单元的顺序为:
1。清BackBuffer;


这是清BackBuffer的函数:
/*
 * gfxFillBack
 */
void gfxFillBack( DWORD dwColor )
{
    DDBLTFX ddbltfx;


    ddbltfx.dwSize = sizeof( ddbltfx );
    ddbltfx.dwFillColor = dwColor;


    IDirectDrawSurface_Blt(
lpBackBuffer, // dest surface
NULL, // dest rect
NULL, // src surface
NULL, // src rect
                        DDBLT_COLORFILL | DDBLT_WAIT,
                        &ddbltfx);


} /* gfxFillBack */


2。检查游戏单元图形的Surface是否丢失;


这是检查游戏单元图形的Surface是否丢失的函数:
/*
 * gfxRestoreAll
 *
 * restore the art when one or more surfaces are lost
 */
BOOL gfxRestoreAll()
{
    GFX_BITMAP  *curr;
    HWND hwndF = GetForegroundWindow();


    Splash();


    for( curr = lpVRAM; curr != NULL; curr = curr->link)
    {
if (curr->lpSurface &&
   (fForceRestore || IDirectDrawSurface_IsLost(curr->lpSurface) == DDERR_SURFACELOST))
        {
            if( !gfxRestore(curr) )
            {
                Msg( "gfxRestoreAll: ************ Restore FAILED!" );
                return FALSE;
            }
        }
    }


    DDClear();
    fForceRestore = FALSE;
    return TRUE;


} /* gfxRestoreAll */


3。将游戏单元的图形画到BackBuffer中;


这是画游戏单元图形的函数之一:
/*
 * DisplayPlane
 */
BOOL DisplayPlane ( GFX_HBM  hBuffer, HPLANE *hPlane )
{
    USHORT      n;
    USHORT      i;
    USHORT      j;
    USHORT      x1;
    USHORT      y1;
    USHORT      x2;
    USHORT      y2;
    USHORT      xmod;
    USHORT      ymod;
    POINT       src;
    RECT        dst;




    x1 = (hPlane->x >> 16) / C_TILE_W;          
    y1 = (hPlane->y >> 16) / C_TILE_H;
    x2 = x1 + C_SCREEN_W / C_TILE_W;
    y2 = y1 + C_SCREEN_H / C_TILE_H;
    xmod = (hPlane->x >> 16) % C_TILE_W;
    ymod = (hPlane->y >> 16) % C_TILE_H;


    for( j = y1; j < y2; ++j )
    {
for( i = x1; i <= x2; ++i )
{
   n = (i % hPlane->width) + j * hPlane->width;
   if( hPlane->hBM[n] != NULL )
   {
if( i == x1 )
{
   dst.left  = 0;
   dst.right = dst.left + C_TILE_W - xmod;
   src.x     = xmod;
}
else if( i == x2 )
{
   dst.left  = (i - x1) * C_TILE_W - xmod;
   dst.right = dst.left + xmod;
   src.x     = 0;
} else {
   dst.left  = (i - x1) * C_TILE_W - xmod;
   dst.right = dst.left + C_TILE_W;
   src.x     = 0;
}
    
if( j == y1 )
{
   dst.top    = 0;
   dst.bottom = dst.top + C_TILE_H - ymod;
   src.y      = ymod;
}
else if( j == y2 )
{
   dst.top    = (j - y1) * C_TILE_H - ymod;
   dst.bottom = dst.top + ymod;
   src.y      = 0;
} else {
   dst.top    = (j - y1) * C_TILE_H - ymod;
   dst.bottom = dst.top + C_TILE_H;
   src.y      = 0;
}
 
gfxBlt(&dst,hPlane->hBM[n],&src);
   }
}
    }


    return TRUE;


} /* DisplayPlane */


4。将BackBuffer和FrontBuffer进行翻转;


这是全屏幕模式下的页面翻转函数:
/*
 * gfxFlip
 */
BOOL gfxFlip( void )
{
    HRESULT     ddrval;


    ddrval = IDirectDrawSurface_Flip( lpFrontBuffer, NULL, DDFLIP_WAIT );
    if( ddrval != DD_OK )
    {
Msg( "Flip FAILED, rc=%08lx", ddrval );
return FALSE;
    }
    return TRUE;


} /* gfxFlip */


这是窗口模式下的页面翻转函数:
/*
 * gfxUpdateWindow
 */
BOOL gfxUpdateWindow()
{
    HRESULT     ddrval;


    ddrval = IDirectDrawSurface_Blt(
                    lpFrontBuffer,          // dest surface
                    &rcWindow,              // dest rect
                    lpBackBuffer,           // src surface
                    NULL,                   // src rect (all of it)
                    DDBLT_WAIT,
                    NULL);


    return ddrval == DD_OK;


} /* gfxUpdateWindow */




第七节 计算机人工智能


计算机人工智能


记得吗?在第五节刷新游戏单元中我们谈到在刷新游戏单元时,说到在取得游戏单位的的位置后要经过一些算法的判断再确定游戏单位的新位置。包含这些算法的部
分就是游戏中实现人工智能的部分。


对于游戏中的人工智能,我比较赞同下面这个定义:一个非游戏者控制的对象在基于各种复杂因素时的决策行为就象时由真正的人作出的,这是通过使用  一个决策算法来完成的,这个决策算法根据设计者确定的规则和提供给程序的信息进行处理。


现在在大部分的游戏中采用的的方式主要有以下几种:


检索
许多人工智能的算法中都涉及到对所有可能性的检索。这个算法的实现方式是这样的,首先您应让您的程序列一个选项表,例如一个士兵到目的之间所有可能的路径。然后再使用其他的人工智能技术如排除法等来找一个最优的选择。


排序
排序与检索都是基本的人工智能技术,您可以用排序来确定最佳的决策次序。比        如,在战略游戏中计算机对手不断地根据当前的环境修改行动的优先级。


专家系统
专家系统是指运用“if then”语句的逻辑表达式来表示所有的基本规则,然后计算机根据这些规则作出智能决策。比如,在制作一个足球游戏时,就可以请一个足球专家,记下他的的足球经验,他会说明在各种情况下,他采取的踢球方式。根据这些信息,建立一套规则库,在游戏中计算机就可以按照这些规则作出决策。


其他的方式还有:机器学习和和神经网络系统,这两种方式的效果相当不错。但是却很不容易掌握,这里我们就不再详述了。




第八节 游戏内存管理


游戏内存管理


这一部分对于优质高效的游戏软件是十分重要的,内存管理不当会导致游戏性能的降低,甚至引起死机。现在的很多游戏都使用了大量的图象和复杂的规则,需要大量的内存。这就需要我们对游戏者所用机器的内存进行精心的分配和组织了。首先,我们应当调查一下现在的主流机型的内存是多少,再与达到游戏的设计目标所需的内存量之间权衡一下,然后确定一个粗略的分配方案。


这个方案一般可以这样指定:


1.这个游戏从情节上可以分为几个部分,在开始时将每个部分所共有的资料调入,然后根据情节的发展将该部分不用的资料所占用的内存释放再调入该部分所特有的资料。比如说可以分为几关或者RPG游戏中的“世界”的地图可以分成几个场景。然后在游戏者到达某一关或进入某一场景时再调入相应的图象或相应的资料。


2.在每个部分中有很多并不常用而且调用时没有严格的速度限制同时调用时并不需要太多时间(通常1秒左右即可完成)的资料,也可以在使用时调用。比如角色从大地图上走入一个城市,这个城市的图象和游戏规则等资料就可以在走入这个城市时调入。


在完成这个方案后,我们就完成了内存和硬盘之间数据交换的规划了,接下来就应考虑运行时内存内部的管理了。


在这一步中主要应注意两个问题:


1.迅速释放存放无效资料的内存;

例如:


描述GAMEWORLD的指针temp在初始化时分配了内存空间。
GAMEWORLD *temp=new GAMEWORLD(Init value);
。。。


在程序结束时要释放内存空间。
delete temp;


2.严禁使用空指针(这样会导致系统错误,甚至死机)。这里没有什么技巧,只有靠您自己的认真和仔细了。


例如:
当在程序中已经释放了temp;
下面的调用就可能导致死机:
temp->Function();


这两个问题的解决方法为:


GAMEWORLD *temp=new GAMEWORLD(Init value);
...


if(temp)
delete temp;
temp=NULL;
...


if(temp)
{
temp->Function();
...
}
else
{
提示temp为空指针。
}




第九节 游戏交互设计


游戏交互设计


交互设计,实际上就是您想让游戏者怎么去操纵游戏的发展。说简单了交互设计就是游戏者怎样去控制游戏角色的行动,在例程中对键盘的设置——用小建盘上的“456237”控制狐狸的行为就是一种简单的交互设计。说复杂了呢!就是您提供了一种什么样的方式让游戏者进入游戏之中成为游戏中的一员——他就是游戏中英勇无敌、侠肝义胆的剑客,他就是游戏中足智多谋、威震天下的将军……这就是好游戏的一个重要的要素——好的交互性。


交互性就是设计者创造的一个诱使人去玩的游戏所拥有的提供故事线索、情绪感染、真实的声音和其他创造性的媒介所给予的特性。交互设计的目的就是让游戏者进入“幻觉状态”,幻觉状态是游戏界的一个术语,它的意思是指游戏者的意识融入到游戏世界中,这样,他或她就不是在玩游戏,而是在体验另一个世界。


怎样作到这一点呢?作为编程人员应考虑的是:


第一步考虑输入设备问题,设备即是游戏者控制游戏的手段,也就是输入设备的选择和设置的问题。在这一步中应该考虑是选择键盘、鼠标、游戏杆还是几种结合的方式,或是其他类型的输入设备。然后是设置各种操作所代表的含义(就象例程中
小键盘的“4”代表左行,“5”代表停止等等,或是鼠标单击、双击某个区域及拖动时代表的含义)这些设置主要是考虑一个操作的方便性的问题。


typedef enum enum_ACTION {
    NONE,
    STILL,
    WALK,
    RUN,
    JUMP,
    THROW,
    CROUCH,
    STOP,
    STUNNED,
    JUMPTHROW,
    CROUCHWALK,
    BLURR,
    STRIKE,
    MISS,
    CHEW,
} ACTION;




WinMainProc中:


    case WM_KEYDOWN:
        switch( wParam )
        {
        case VK_NUMPAD5:
            lastInput=KEY_STOP;
            break;
        case VK_DOWN:
        case VK_NUMPAD2:
            lastInput=KEY_DOWN;
            break;
        case VK_LEFT:
        case VK_NUMPAD4:
            lastInput=KEY_LEFT;
            break;
        case VK_RIGHT:
        case VK_NUMPAD6:
            lastInput=KEY_RIGHT;
            break;
        case VK_UP:
        case VK_NUMPAD8:
            lastInput=KEY_UP;
            break;
        case VK_HOME:
        case VK_NUMPAD7:
            lastInput=KEY_JUMP;
            break;
        case VK_NUMPAD3:
            lastInput=KEY_THROW;
            break;
        case VK_F5:
            bShowFrameCount = !bShowFrameCount;
            if( bShowFrameCount )
            {
              dwFrameCount = 0;
              dwFrameTime = timeGetTime();
            }
        break;


第二步考虑信息返回的问题,这里主要是一个界面的设计的问题。这个问题我们在
第一章第六节游戏的界面设计中已经讨论过了,这里就不详述了。




第十节 游戏图形底层设计


在游戏中,计算机主要花时间在处理图象和画图象上,所以我们应尽力使这些操作适合主流机型的硬件水平或尽量少占用系统资源,这就是游戏图形底层设计的目的。在前面讲的DirectDraw和DirectX5 SDK中的Direct3D都是图形底层,还有ID在 QUAKE发行后提供的QUAKE C也是一种不错的图形底层。建立一套游戏图形底层需要大量的关于图形编程的知识和很多的时间精力,而且效果不一定好,同时在市场上也有很多图形底层可供选择。所以对于一般的游戏开发者来说,只要作的游戏使用的图象并没有使计算机不负重荷或并没有使用现有的底层所不支持的特性,我建议还是使用现有的底层。


本例程的图形底层十分简单,采用DirectDraw提供的IDirectDrawSurface_BltFast和IDirectDrawSurface_Blt函数:


    if (pbm->lpSurface)
    {
        if( pbm->bTrans )
            bltflags = bTransDest ? DDBLTFAST_DESTCOLORKEY : DDBLTFAST_SRCCOLORKEY;
        else
            bltflags = bTransDest ? DDBLTFAST_DESTCOLORKEY : DDBLTFAST_NOCOLORKEY;
        ddrval = IDirectDrawSurface_BltFast(
                        lpBackBuffer, x, y,
                        pbm->lpSurface, &rc, bltflags | DDBLTFAST_WAIT);


        if (ddrval != DD_OK)
        {
            Msg("BltFast failed err=%d", ddrval);
        }
    }
    else
    {
        DDBLTFX     ddbltfx;


        rc.left   = x;
        rc.top    = y;
        rc.right  = rc.left + dx;
        rc.bottom = rc.top  + dy;


        ddbltfx.dwSize = sizeof( ddbltfx );
        ddbltfx.dwFillColor = pbm->dwColor;


        ddrval = IDirectDrawSurface_Blt(
                        lpBackBuffer,           // dest surface
                        &rc,                    // dest rect
                        NULL,                   // src surface
                        NULL,                   // src rect
                        DDBLT_COLORFILL | DDBLT_WAIT,
                        &ddbltfx);
    }


由于DirectDraw的通用性要求,所以虽然它提供的函数的速度很快,但是对特效的支持比较少。深入的图形底层应包括大量的高效的特效处理功能,所以我们应该能够直接对显存操作。


DirectDraw的DirectSurface提供了这个入口,它的DDSURFACEDESC结构中的变量
lpSurface就是显存映射的入口指针。


typedef struct _DDSURFACEDESC {
    DWORD          dwSize;
    DWORD          dwFlags;
    DWORD          dwHeight;
    DWORD          dwWidth;
    union
    {
        LONG       lPitch;
        DWORD      dwLinearSize;
    };
    DWORD          dwBackBufferCount;
    union
    {
        DWORD      dwMipMapCount;
        DWORD      dwZBufferBitDepth;
        DWORD      dwRefreshRate;
    };
    DWORD          dwAlphaBitDepth;
    DWORD          dwReserved;


    LPVOID         lpSurface;
    DDCOLORKEY     ddckCKDestOverlay;
    DDCOLORKEY     ddckCKDestBlt;
    DDCOLORKEY     ddckCKSrcOverlay;
    DDCOLORKEY     ddckCKSrcBlt;
    DDPIXELFORMAT  ddpfPixelFormat;
    DDSCAPS        ddsCaps;
} DDSURFACEDESC; 


但是使用它之前,必须调用DirectDrawSurface3::Lock函数将此图形内存锁住,
在处理后,调用DirectDrawSurface3::Unlock函数将此内存交还给系统。





你可能感兴趣的:(编程,函数,面向对象,人工智能,游戏编程)