游戏开发基础(7)

类别:VC语言 点击:0 评论:0 推荐:

游戏开发基础(7)


第八章 例程之初始化部分
第一节DDInit():

DDInit()的作用是枚举驱动程序,它的具体运行过程如下:
首先调用DirectDrawEnumerate(),这个函数的作用在第五章 DirectDraw深入篇第三节选择DirectDraw驱动程序中已经谈到了。在本例程中这个函数的参数是&DDEnumCallback和NULL,&DDEnumCallback是指回调函数DDEnumCallback()的地址,NULL是指没有这个指向应用程序数据的指针。

回调函数DDEnumCallback()的作用是将枚举过的的驱动程序的GUDI、描述和名字存入一个结构数组aDDDevs[]中。待以后选择。

第二节定义命令行参数
1.根据命令行参数确定运行方式
首先,使用while( lpCmdLine[0] == '-' || lpCmdLine[0] == '/')检测命令行参数的标识符,然后再使用switch (*lpCmdLine++)对部分参数的含义进行定义:
-e Use emulator(使用软件模拟)
-S No Sound(无声)
-1 No backbuffer(不使用后备缓冲区)
-2 One backbuffer(一个后备缓冲区)
-4 Three backbuffers(三个后备缓冲区)
-s Use stretch(使用拉伸算法,即是在窗口模式下改变窗口的形状时对图形使用拉伸算法使图象比较匀称。)
-x Demo or stress mode(使用重音模式)

2.根据命令行参数确定显示模式
对显示模式的横轴方向的像素数GameMode.cx、纵轴方向的像素数GameMode.cy及颜色数GameMode.BPP调用getint(char**p, int def)取得命令行参数对这些项目的设定。
getint(char**p, int def)函数的运行过程为:
先检测命令行参数第一个字符是否是“”、“\r”、“\t”、“\n”或“x”。如果是就使指针p自加1并继续检测,否则检测该字符是否是小于9大于0的数。如果该字符不是小于9大于0的数则返回默认值,反之则通过
while (IS_NUM(**p) 
i = i*10 + *(*p)++ - '0'
将输入的字符的ASCII值转变为数值。然后通过
while (IS_SPACE(**p))
(*p)++;
检测后面的字符是否是“”、“\r”、“\t”、“\n”或“x”,如果是就使指针p自加1并继续检测,直到出现其他字符或字符串结束。

第三节初始化Windows
程序在这一部分调用了initApplication( HINSTANCE hInstance, int nCmdShow )函数来初始化Windows。

initApplication( HINSTANCE hInstance, int nCmdShow )首先定义窗口类为:
style:指明了类风格为向窗口发送一个鼠标双击的消息。
  1pfnWndProc:指明了指向窗口函数的指针,该指针指向MainWndProc。
  cbClsExtra:指定在窗口类结构后面分配的字节数为0。
  cbWndExtra:指定在窗口实例后面分配的字节数为0。
  hInstance:注册窗口类的应用程序实例句柄是hInstance。
  hIconhIcon:划定利用窗口最小化时显示的图标通过调用LoadIcon( hInstance, MAKEINTATOM(FOX_ICON))获得。
  hCursorhCursor:定义应用程序使用的光标通过调用LoadCursor( NULL, IDC_ARROW )获得。
hbrBackground:背景刷子的标识符通过调用GetStockObject(BLACK_BRUSH)获得。
1pszMenuName:菜单的资源名的指针为NULL。
1pszClassName:窗口类的名字为WinFoxClass。
然后用
if( !rc )
{
    return FALSE;
}

注册这个窗口类,并在注册失败时结束程序。
接着用hWndMain = CreateWindowEx(……)创建窗口并将窗口的句柄赋给hWndMain 。该窗口被创建为:
窗口的扩展格式为 WS_EX_APPWINDOW
窗口类为 "WinFoxClass"
窗口名为 OUR_APP_NAME
窗口格式为 WS_VISIBLE |WS_SYSMENU |WS_POPUP(创建一个初始态可见的标题条上有系统菜单的重叠窗口或弹出式窗口
窗口左上角的X坐标 0
窗口左上角的Y坐标 0
窗口宽度 GetSystemMetrics(SM_CXSCREEN)(屏幕宽度)
窗口高度 GetSystemMetrics(SM_CYSCREEN)(屏幕高度)
父窗口的句柄 NULL,
窗口菜单的句柄 NULL,
窗口类的应用程序的实例句柄是 hInstance 
32位附加信息为 NULL 
然后,用
if( !hWndMain )
{
    return FALSE;
}
检测窗口创建是否成功,并在创建失败时结束程序。
最后调用UpdateWindow( hWndMain )和SetFocus( hWndMain )显示窗口和将键盘输入限定在hWndMain所指的窗口(即游戏窗口)中。

第四节帮助信息的显示
在这里调用了MessageBoxA(HWND hWnd ,LPCSTR lpText,LPCSTR lpCaption,UINT uType)函数。它的调用方式如下:
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 );
}

它的含义是:如果bHelp为TURE,则在hWndMain所指向的窗口创建一个消息框。这个消息框的内容是“”所包含的部分,标题是OUR_APP_NAME(在foxbear.c中有#define OUR_APP_NAME "Win Fox Application"语句),并且这个消息框显示一个OK按钮。

第五节初始化游戏
这个部分只是调用了InitGame( void )函数,它的调用方式为:
if( !InitGame() )
{
    return FALSE;
}
即如果调用失败,则结束程序。
InitGame( void )是整个初始化部分最庞大的部分,它完成了剩下的初始化的工作。在本章的后面几节中我们将分几部分讲解这个函数

第六节内存刷新
由于在窗口过程中会接受被定义为改变显示模式的消息这时需要对游戏进行重新初始化,于是就会对InitGame()函数进行调用(比如在消息VK_F8时就调用了该函数)。这时的调用会是在游戏运行时发生的,这时在游戏运行中较重要的指针都已被使用(指向了一定的内存区域),同时内存已被分配。如果这时不释放内存和将指针指向NULL(即不指向任何一个内存区域),以后的初始化将会出现错误 。故在该函数的运行过程中调用了ExitGame()函数已进行刷新指针的工作。
ExitGame()的运行过程为:

首先,检测lpFrameRate、lpInfo、lpPalette三个指针,若非0,则释放其所指的内存,并令该指针为NULL使其恢复为初始状态(通过利用IUuknown接口提供的函数Release()递减指针的内部引用值,直至引用值为0释放所分配的内存)。
然后,调用函数DestroyGame()继续释放内存。DestroyGame()的运行过程为:首先,检测hBuffer是否为非0,如果非0则调用下列函数:
DestroyTiles( hTileList )——释放句柄hTileList所指的局部内存块。
DestroyPlane( hForeground )——释放句柄hForeground和hForeground->hBM所指向的内存块。
DestroyPlane( hMidground )——释放句柄hMidground和hMidground->hBM所指向的内存块。
DestroyPlane( hBackground )——释放句柄hBackground和hBackground->hBM所指向的内存块。
DestroyBuffer( hBuffer )——释放所有位图所占用的内存及lpClipper、 lpBackBuffer、 lpFrontBuffer所指向过的内存区域。
DestroySound()——释放所有音效所占用的内存及lpDS所指向过的内存区域。

最后令hTileList、 hForeground、hMidground、hBackground和hBuffer指向空。


第七节初始化声音
这个部分主要调用函数IinitSound()进行对DirectSound进行初始化和声音的播放的工作。它的工作过程为:
调用DSEnable(hwndOwner)进行DirectSound的初始化:先用bUseDSound = GetProfileInt("FoxBear", "use_dsound", bWantSound)选择播放设备;再通过
if (!bUseDSound)
{
    lpDS = NULL;
    return TRUE;
}

if (lpDS != NULL)
{
    Msg( "DSEnable, already enabled" );
    return TRUE;
}
检测选择是否成功和声音是否已被初始化。然后是
dsrval = DirectSoundCreate(NULL, &lpDS, NULL);

if (dsrval != DS_OK)
{
    Msg("DirectSoundCreate FAILED");
    return FALSE;
}


dsrval = IDirectSound_SetCooperativeLevel(lpDS, hwnd, DSSCL_NORMAL);

if (dsrval != DS_OK)
{
    DSDisable();
    Msg("SetCooperativeLevel FAILED");
    return FALSE;
}
用于创建DirectSound的对象并检测创建是否成功和设置合作层并检测设置是否成功。在这里为默认设备创建了一个对象,将合作层设置为普通(DSSCL_NORMAL)即使用声卡的应用程序可以顺序地进行切换。
调用DSEnable()之后用语句:
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]);
    }
}
将音效调入(利用函数SoundLoadEffect((EFFECT)idx))和检测设备的特性并将之存放在结构caps中的成员dwsize里(利用函数IDirectSoundBuffer_GetCaps(lpSoundEffects[idx], &caps)。
最后是一部分是播放,这部分主要是设定dsBD.dwFlags为DSBCAPS_PRIMARYBUFFER确定可对主缓冲区进行操作,使用语句if (SUCCEEDED(IDirectSound_CreateSoundBuffer(lpDS, &dsBD, &lpPrimary, NULL)))来创建副缓冲区并检测成功与否,使用语句 if (!SUCCEEDED(IDirectSoundBuffer_Play(lpPrimary, 0, 0, DSBPLAY_LOOPING)))播放并检测成功与否。

第八节DirectDraw的设置
这个部分完成了对DirectDraw的设置工作和游戏开始时初始化画面的显示。它的运行过程如下:
if( !PreInitializeGame() )
{
    return FALSE;
}
在PreInitializeGame()函数中只有语句return InitBuffer( &hBuffer)。该语句调用了InitBuffer( &hBuffer)函数又只调用了gfxBegin()函数,并在调用失败后结束程序。函数gfxBegin()完成了对显示模式的设置工作(通过对DDEnable( void )函数的调用)、创建表面的工作(通过调用函数DDCreateFlippingSurface( void )、初始化画面的显示(通过调用函数Splash())。
函数DDEnable( void )的运行过程如下:
1.获取系统信息(运用GetProfileInt()函数)和决定是否使用软件模拟(应用DirectDrawCreate() 为该驱动方式建立一个对象再用DirectDraw_QueryInterface()查询该对象是否被支持)。
2.检测显示模式。首先判断是否使用全屏模式,若是则使用IDirectDraw_SetCooperativeLevel(( lpdd, hWndMain,DDSCL_ALLOWMODEX | DDSCL_EXCLUSIVE |DDSCL_FULLSCREEN )创建一个对显示设备能够进行最大化控制的合作层,然后用IDirectDraw_EnumDisplayModes(lpdd, DDEDM_STANDARDVGAMODES, NULL, 0, EnumDisplayModesCallback)枚举VGA模式下的显示模式。如果枚举失败,则调用IDirectDraw_EnumDisplayModes(lpdd, 0, NULL, 0, EnumDisplayModesCallback)(枚举所有的显示模式)。若不使用全屏模式,则调用 ddrval = IDirectDraw_SetCooperativeLevel( lpdd, hWndMain,DDSCL_NORMAL )(创建一个DDSCL_NORMAL 模式下的合作层即使用一般Windows应用程序的方式)。随后将设定的显示模式放入结构数组ModeList[]中。若合作层创建失败则返回错误信息并结束游戏。
接下来是选择显示模式部分。首先判断是否使用全屏模式,若是则判断使用哪种显示大小和颜色数。若没选用全屏模式则获取系统当前的设备及所采用的颜色数,设定窗口模式下的窗口的属性及判断是否使用拉伸算法并根据判断结果设定用户区的大小(利用SetRect(&rc, 0, 0, GameMode.cx*2, GameMode.cy*2)及SetRect(&rc, 0, 0, GameMode.cx, GameMode.cy)),然后根据用户区的大小和窗口风格的窗口属性设定窗口的大小,并由SetWindowPos()确定窗口在多窗口的情况下的位置。
利用IDirectDraw_GetCaps( lpdd, &ddcaps, NULL )函数获取设备所支持的特性,并确定那些特征由软件模拟,那些特征由硬件支持。由nBufferCount的值确定缓冲区的数量。最后由设备所支持的特性确定最大显示模式。
函数DDCreateFlippingSurface( void )的运行过程:
首先取得硬件支持的特性(应用IDirectDraw_GetCaps( lpDD, &ddcaps, NULL ) )。再将缓冲区中全部填上0。在全屏模式下和缓冲区数大于1时,创建一个以DD_CAPS为标志的表面功能区及一个以DDSD_BACKBUFFERCOUNT为标志的后备缓冲记数区,并给表面功能区分配DDSCAPS_PRIMARYSURFACE、DDSCAPS_FLIP和DDSCAPS_COMPLEX三个标志(关于这三个标志,请详细阅读DirectDraw深入篇)。接下来用 ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval = IDirectDrawSurface_GetAttachedSurface(
lpFrontBuffer,
&ddscaps,
&lpBackBuffer );
取得后备缓冲区的指针。如果使用了拉伸算法或ddcaps.dwCaps的标志为DDCAPS_BANKSWITCHED时则创建再一个用于拉伸算法的缓冲区(在您详细阅读函数DDCreateSurface(DWORD width,DWORD height,BOOL sysmem,BOOL trans )后会发现该缓冲区的创建方法与上面谈到的缓冲区的创建是差不多的,只不过少了后备缓冲记数区而多了两个关于高度和宽度的缓冲区,并使用了系统内存,请注意这个技巧)。下面是在全屏模式和有一个缓冲区的情况下,如何进行创建表面的工作。在这一步中可以看到程序只创建了一个表面功能区而无后备缓冲记数区,同时取得后备缓冲区的指针的部分被 
IDirectDrawSurface_AddRef(lpFrontBuffer);
lpBackBuffer = lpFrontBuffer;
替代。然后是在窗口模式下的缓冲区的设置,首先创建一个表面功能区(在VRAM中)
然后用lpBackBuffer = DDCreateSurface( GameSize.cx, GameSize.cy, FALSE, FALSE )创建一个后备缓冲区(在系统内存中)。接下来是对剪切板的设置,先创建一个剪切板(ddrval = IDirectDraw_CreateClipper(lpDD, 0, &lpClipper, NULL)),再取得剪切板的指针( ddrval = IDirectDrawClipper_SetHWnd(lpClipper, 0, hWndMain)),最后将剪切板连接到表面上( ddrval = IDirectDrawSurface_SetClipper(lpFrontBuffer, lpClipper))。下面是对颜色的操作,首先使用IDirectDrawSurface_GetPixelFormat(lpFrontBuffer, &ddpf)取得表面的像素格式,然后设置ColorKey,若是8位色则ColorKey为256反之则取16位色。

第九节用户区尺寸及调色板设置
1.用户区尺寸设置
在这部分中首先是对全屏模式或使用拉伸算法的情况下的用户区的大小设置,然后是在窗口模式下的设置。在这些设置工作中可能需要解释的是 GameRect.left为什么等于GameMode.cx - GameSize.cx,这是由于GameRect.left(或GameRect.right)是指用户区的左上角(右下角)的横坐标长度,而GameMode.cx是指整个用户区的宽度(全屏)而且GameSize.cx= GameMode.cx / 2 。所以GameRect.left=GameMode.cx - GameSize.cx。其他的比如GameRect.top、GameRect.right和GameRect.bottom的计算式的含义都和GameRect.left的计算方法差不多。
2调色板设置
这一部分程序的功能主要是由函数ReadPalFile()完成的,这个函数的功能是引入一个调色板文件,如果找不到指定的文件则使用默认的调色板,而这个默认的调色板的建立是使用了下面的语句:
for( i=0; i<256; i++ )
{
    pal.ape[i].peRed = (BYTE)(((i >> 5) & 0x07) * 255 / 7);
    pal.ape[i].peGreen = (BYTE)(((i >> 2) & 0x07) * 255 / 7);
    pal.ape[i].peBlue = (BYTE)(((i >> 0) & 0x03) * 255 / 3);
    pal.ape[i].peFlags = (BYTE)0;
}/*在结构数组pal.ape[i]中存放256组由四个数值组成的颜色值*/
和函数 ddrval = IDirectDraw_CreatePalette(lpDD,DDPCAPS_8BIT,pal.ape,&ppal,NULL )。从这些语句中我们可以看到建立了一个256色的调色板(8位色)。
在调用调色板之后用IDirectDrawSurface_SetPalette()将调色板连接到表面上。

第十节调入图象
这个部分是通过调用InitializeGame()函数实现的。InitializeGame()函数的运行过程为:首先调用 Splash()函数,该函数的作用是将后备缓冲区全部写为蓝色并测试全部缓冲区(用DDClear()函数)和向屏幕输出FoxBear is loading...... Device。然后用LoadBitmaps( void )函数从foxbear.art文件中将所有的位图读出并存入结构数组hBitmapList[]中。接着是:
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 );
这些语句将 hBitmapList[]中的位图分类存入相应的列表中并建立各自的内存区。如:InitPlane( &hForeground, &hForePosList, "FORELIST", C_FORE_W, C_FORE _H, C_FORE_DENOM )在主缓冲区建立了一个存放前景的区域和一个存放前景的位图的区域(第一帧)。
TilePlane( hForeground, hTileList, hForePosList )在主缓冲区建立一个存放前景中其他位图的区域。
最后用DDClear()将所有后备缓冲区清空。

第十一节输出设备信息
在游戏的过程中会显示出当前的FPS值及显示模式和“ALT+ENTER=WINDOWS”的信息。这部分的功能就是输出这些信息。
这一功能的是通过函数makeFontStuff()的调用而实现的。makeFontStuff()的实现过程为:删除已有的字体句柄,创建一个新的字体,调用initNumSurface( )为FPS及FPS的值和显示模式及字符串“ALT+ENTER=WINDOWS”设定输出字体的大小、颜色、在用户区的位置等等属性。然后各为FPS 和显示模式等的信息创建一个内存区域以存放它们。然后用
if( bTransDest )
    BackColor = RGB(255,255,255);
else
    BackColor = RGB(128,64,255);

ddck.dwColorSpaceLowValue = DDColorMatch(lpInfo, BackColor);
ddck.dwColorSpaceHighValue = ddck.dwColorSpaceLowValue;

IDirectDrawSurface_SetColorKey( lpInfo, DDCKEY_SRCBLT, &ddck);
IDirectDrawSurface_SetColorKey( lpFrameRate, DDCKEY_SRCBLT, &ddck);
来设定FPS信息和显示模式信息显示的色彩键码。
最后,再次调用initNumSurface( )显示这些信息。

本文地址:http://com.8s8s.com/it/it1161.htm