第一节窗口的移动和改变大小时
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_SIZE:是在窗口的大小被改变时发送的。
case WM_MOVE:是在窗口被移动时发送的。
当收到这两个消息后,首先检测窗口是否最小化了,如果是则暂停游戏。否则再检测游戏是否转为全屏模式,若是则改变用户区的大小为全屏。如果只是改变了窗口的大小但并没有使显示模式变成全屏或窗口最小化,则先取得窗口的用户区坐标(左上角的x、y及右下角的x、y的值)将之存入结构rcWindows,再将rcWindows中的坐标转换为屏幕坐标。请注意rcWindows是RECT型的结构,但ClientToScreen()要求的是POINT型的结构,在结构RECT中存放的是一个矩形的左上角的x、y及右下角的x、y的值而结构POINT中存放的就只是x和y的值。故此在
ClientToScreen(hWnd, (LPPOINT)&rcWindow);
ClientToScreen(hWnd, (LPPOINT)&rcWindow+1);
中先对rcWindow进行强制类型转换,而且两次调用该函数,同时在第二次掉用时使rcWindow的地址自加1。
第二节窗口被激活时
case WM_ACTIVATEAPP:
bIsActive = (BOOL)wParam && GetForegroundWindow() == hWnd;
if (bIsActive)
Msg("FoxBear is active");
else
Msg("FoxBear is not active");
if (bPaused && bIsActive)
{
if (RestoreGame())
{
UnPauseGame();
}
else
{
if (GetForegroundWindow() == hWnd)
{
if (InitGame())
{
UnPauseGame();
}
}
}
}
break;
case WM_ACTIVATEAPP:当不同于当前窗口的应用程序的窗口被激活时发送本消息。
当接收到这个消息时,首先令bIsActive 等于 (BOOL)wParam && GetForegroundWindow() == hWnd,然后检测bIsActive是否为TURE。这里的意思就是检测游戏的窗口是否被激活且处于当前系统中最高优先级。接下来先检测游戏窗口是否是从暂停状态到激活的状态,若是则运行RestoreGame()从新开始,若RestoreGame()成功则运行UnPauseGame(),如果不是从暂停状态到激活的状态,则检测本程序的窗口是否拥有最高的优先级,若有则从新初始化游戏(运行InitGame()),初始化成功后则运行UnPauseGame()。
第三节 实现逻辑调色板时
case WM_QUERYNEWPALETTE:
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");
}
}
if( ddrval == DD_OK )
{
SetWindowText( hWnd, OUR_APP_NAME );
}
}
break;
WM_QUERYNEWPALETTE:在窗口收到输入焦点前发出,当窗口收到输入焦点后将返回显示它是否能实现逻辑调色板。
在这个消息获得后,程序先检测是否运行于窗口模式且调色板和前缓冲区已设定,然后设定一次调色板,若失败则恢复前缓冲区然后再试一次,如果仍然失败则输出错误信息。两次设定若成功一次则将标题改为“Win Fox Application”。
第四节 改变系统调色板时
case WM_PALETTECHANGED:
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;
WM_PALETTECHANGED:本消息在拥有输入焦点的当前窗口实现其逻辑调色板时送往所有的窗口。这时,系统调色板被改变,本消息允许不带输入焦点的窗口使用调色板去实现自己的逻辑调色板和更新其用户区域。
在得到这个消息后,程序首先是否是当前窗口改变了系统调色板,如果是则直接跳出窗口过程,若不是则再检测是否是全屏模式,若是则直接跳出窗口过程,若非则先检测bStress是否为FLASE,若为FLASE则暂停游戏,若为TURE则将标题条改为“ - palette changed COLORS PROBABLY WRONG”。
第五节当操作键按下时
WM_KEYDOWN消息是在一个非系统键按下时产生的,非系统键是指没有按下ALT键时按下的键,或是当某窗口已有输入焦点时按下的键。
在该消息的wParam参数中包含了识别所按下的键的虚键码,由不同的虚键码就可以完成键盘对游戏的操作。我们知道键盘对游戏的操作中所按的键可以分为:操作键和功能键两类。下面让我们先看看例程中是如何定义操作键的吧。
在上一章我们就介绍过在本例程中操作键是小建盘上的“2345678”,但一直没有谈到如何实现的,现在就让我们来看一看。
对操作键的功能的定义是在
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;
您可以看到在得到每个虚键码之后都对lastInput进行赋值,这时就完成了对操作键的定义了,至于操作键是如何其作用的,在本章的第八节中您可以看到。
第六节 当功能键按下时
在本游戏中的功能键是F3、F4、F5、F6、F7、F8、F9。这些键的作用时什么,是如何实现的呢?下面就让我们一个一个的看看吧!
1.F3的作用是暂停游戏和解除暂停。在程序中这个作用是这样实现的:
case VK_F3:
bPaused = !bPaused;
break;
2.F4的作用是实现ALT+ENTER的作用。在程序中是使用
case VK_F4:
PostMessage(hWnd, WM_SYSKEYUP, VK_RETURN, 0);
break;
4.F6的作用是逐个使用在显示模式列表中的显示模式。
case VK_F6:
{
static i;
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;
}
}
}
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;
在收到VK_F6的消息后,程序设立一个初值为0的变量,并以该变量为存放显示模式列表的数组的下标,然后令其自加直至找到一个与当前模式相同的。然后令其自加1,取以该值为下标的元素为显示模式,最后重新初始化游戏。
5.F7的作用是改变颜色数。
case VK_F7:
GameBPP = GameBPP == 8 ? 16 : 8;
InitGame();
break;
6.F8的作用是决定是否使用拉伸算法。
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;
这部分的运行过程为:若为全屏模式则对 bStretch取非,然后初始化游戏。若为窗口模式,则取得当时窗口的特征,以确定bStretch的值,然后从新显示窗口。
7.F9的作用是取消使用软件模拟并逐个使用已有的驱动程序
case VK_F9:
DevIndex ++;
bUseEmulation = FALSE;
if (DevIndex >= MaxDevIndex)
DevIndex = 0;
ExitGame();
DDDisable(TRUE); // destroy DirectDraw object
InitGame();
break;
第七节 其他消息
case WM_DISPLAYCHANGE:
break;
case WM_CREATE:
break;
这两个消息收到后,将不做任何反应。
case WM_SETCURSOR:
if (bFullscreen && bIsActive)
{
SetCursor(NULL);
return TRUE;
}
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;
该消息在请求重新绘制应用程序窗口是发出 。
程序在接收到这个消息后就调用BeginPaint()为hWnd所指的窗口作好绘画准备。然后判断游戏是否暂停,若是则向屏幕输出Game is paused, this is not a bug.最后调用EndPaint()
case WM_DESTROY:
hWndMain = NULL;
lastInput=0;
DestroyGame(); // end of game
DDDisable(TRUE); // destroy DirectDraw object
PostQuitMessage( 0 );
break;
该消息在要撤消某窗口时向该窗口发送。
在收到这个消息后程序令指向窗口的句柄为NULL,对游戏的刷新单元的下一次输入设为0,然后清除游戏及游戏工所占的内存。
第八节 刷新游戏单元
在讨论完窗口过程后,我们应开始介绍这个游戏的消息循环部分了。在这个游戏的消息循环部分中大部分在第二章 windows编程基础中已经谈到过了,所以在这里我们将只介绍刷新游戏单元和重画游戏单元部分。在消息循环中程序是调用函数ProcessFox(SHORT sInput)来进行这两部分的工作的。在ProcessFox(SHORT sInput)函数中,先对游戏是否正在运行或恢复游戏运行是否成功作了一个检测,然后就先调用ProcessInput(sInput)函数进行刷新游戏单元部分,再调用NewGameFrame()进行重画游戏单元的工作。下面就让我们先看看刷新游戏单元部分吧。
这一部分的运行过程为:
1.狐狸的行为刷新
首先取得当前狐狸的的速度、行为和方向。然后检测是否获得狐狸的位置或输入是否是4209,若检测的表达式为TURE则输入为0,即没有输入,若为FLASE则开始对各种输入进行响应。在响应的过程中程序先对狐狸的当前状态进行判断,然后根据狐狸的当前状态决定下一帧狐狸的基本状态。比如“↓”键或小键盘的“2”被按下时的响应过程为
case KEY_DOWN:
if( foxAction == STOP )
{
break;
}
else if( foxAction == STILL )
{
SetSpriteAction( hFox, CROUCH, SAME );
}
else if( foxAction == WALK )
{
SetSpriteAction( hFox, CROUCHWALK, SAME );
}
break;
在“↓”键被按下时,如果狐狸的动作是在急停的,则跳出;如果狐狸是静止的,则狐狸蹲下不动;若狐狸是在移动,则狐狸的行动改为爬行。
在对输入的初步处理后,程序便结合狐狸行为的其他属性,确定下一帧狐狸的状态。在这里同样是用switch——case语句和大量的if——else语句完成的。
2.熊和苹果的行为刷新
这两部分的运行过程实际上是一致的,都是首先进行碰撞检测,然后再根据检测结果决定角色新的行为属性。
第九节 重画游戏单元
这部分的工作是由NewGameFrame()完成的。这个函数首先调用SetSpriteX() 、SetSpriteY()两函数设定角色的新位置,再调用SetPlaneVelX()和SetPlaneX()函数设定三层背景的移动速度和下一帧的位置。然后通过检测bTransDest的值决定是先将角色的位图粘贴到Backbuffer还是先将背景的位图粘贴到BackiBuffer,
最后调用函数gfxSwapBuffers()进行页面翻转将后备缓冲区中的图显示到屏幕上。该函数是这样实现的:
BOOL gfxSwapBuffers( void )
{
if( bFullscreen )
{
if( bStretch )
{
gfxStretchBackbuffer();
}
if (nBufferCount > 1)
return gfxFlip();
else
return TRUE;
}
else
{
return gfxUpdateWindow();
}
它的运行过程是这样的:在全屏模式下且使用拉伸算法时用gfxStretchBackbuffer()对后备缓冲区和拉伸缓冲区之间进行blt操作,然后在有一个以上(不含一)的后备缓冲区时用gfxFlip()调用IDirectDrawSurface_Flip( lpFrontBuffer, NULL, DDFLIP_WAIT )进行一次页面翻转操作将后备缓冲区中的图显示到屏幕上。如果是窗口模式下,则通过gfxUpdateWindow()调用IDirectDrawSurface_Blt( lpFrontBuffer,&rcWindow,lpBackBuffer,NULL,DDBLT_WAIT,NULL)直接将后备缓冲区中的内容blt到主缓冲区中。这样就完成了重画游戏单元的任务了。
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( )显示这些信息。
SKEYUP, 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”控制狐狸的行为就是一种简单的交互设计。说复杂了呢!就是您提供了一种什么样的方式让游戏者进入游戏之中成为游戏中的一员——他就是游戏中英勇无敌、侠肝义胆的剑客,他就是游戏中足智多谋、威震天下的将军……这就是好游戏的一个重要的要素——好的交互性。
交互性就是设计者创造的一个诱使人去玩的游戏所拥有的提供故事线索、情绪感染、真实的声音和其他创造性的媒介所给予的特性。交互设计的目的就是让游戏者进入“幻觉状态”,幻觉状态是游戏界的一个术语,它的意思是指游戏者的意识融入到游戏世界中,这样,他或她就不是在玩游戏,而是在体验另一个世界。