赛车游戏的2D 编程
转载请注明出处
本文章的下载地址,请单击此链接
赛车是一个简单的游戏,可是麻雀虽小,五脏具全。它用DDraw实现了精灵的绘制,用DSound
实现游戏音效,用DInput 实现了键盘和鼠标接口,用DPlay实现了联网。
游戏运行的初始界面如图3.18 所示,游戏的竞赛场面如图3.19 所示。
这个游戏使用基础库cMain.lib。cMain.lib库是打包的DirectX库。在编译这个工程前,要确保自
己的计算机上安装了DirectX SDK 8.0或者9.0(注意是DirectX SDK,不是运行库)。如果已经安装了
SDK,编译仍有问题,那么请检查一下DX的include和library路径是否已经包括在VC++的Option
列表中。
图3.18赛车游戏初始运行图
图3.19赛车游戏运行图
3.8.1 cMain 游戏库
打开工程文件(.dsw)的时候会发现该工程中包括了两个项目。一个项目是运行程序,它调用了
cMain 库。而另一个项目是就DirectX的打包库cMain,这个打包库几乎包括了游戏可能需要的所有功
能。这个库包含了14 个类。下面就来介绍这些类。
_ cApplication 类
classcApplication
{
protected:
LPSTRm_lpszAppName;
LPSTRm_lpszwndClassName;
DWORDm_ColorDepth;
DWORDm_ScreenHeight;
DWORDm_ScreenWidth;
public:
BOOLm_bActive;
voidPreventFlip();
boolm_bDontFlip;
HWNDGetMainWnd();
LPDIRECTDRAW7GetDirectDraw();
LPDIRECTDRAW7m_pDD;
LPDIRECTDRAWSURFACE7m_pFrontBuffer;
LPDIRECTDRAWSURFACE7m_pBackBuffer;
cApplication();
~cApplication();
staticHINSTANCE m_hInst;
staticHINSTANCE GetInstHandle() { return m_hInst; };
BOOLInitApplication();
BOOLRunApplication();
BOOLInitDirectX();
virtualvoid ExitApp();
virtualvoid DoIdle();
virtualvoid AppInitialized();
private:
cWindowm_pWindow;
};
cApplication 类是一个简单的Win 32程序框架的封装。既然要用DirectX,那么这个类就负责为
DirectDraw 创建基本的框架。在cMain库中,会发现一个全局函数CreateApplication(),它就负责这项工
作。CreateApplication()是一个虚函数,它需要在游戏自身的项目中创建而且需要返回应用程序的实例。
在游戏创建过程中,cApplication 类中有3 个很重要的虚函数,它们是AppInitialized、ExitApp和
DoIdle 函数。
当应用程序开始启动的时候需要调用AppInitialized函数。当退出游戏的时候,需要调用ExitApp,
它负责销毁AppInitialized 创建时分配的内存和其他资源。当没有任何窗口消息需要处理的时候,
cApplication 类将调用DoIdle虚函数,从而允许用户处理自己的游戏。
_ cWindow 类
如果注意cApplication 类的声明,会看到它定义了一个cWindow 类成员变量。cWindow类负责在
游戏中创建主窗口。这个类只在库内部使用,所以没有必要去修改它的属性。
_ cInputDevice,cKeyboard和cMouse类
cInputDevice 类代码如下所示:
classcInputDevice
{
private:
staticint m_iRefCount;
protected:
staticLPDIRECTINPUT8 m_lpDI;
public:
cInputDevice();
virtual~cInputDevice();
BOOLCreate()
{
HRESULThRet;
if(!m_lpDI)
{
hRet=DirectInput8Create(GetMainApp()->GetInstHandle(),DIRECTINPUT_VERSION,
IID_IDirectInput8,(void**)&m_lpDI, NULL);
ifFAILED(hRet)
returnFALSE;
}
//引用计数递增
m_iRefCount++;
returnTRUE;
}
voidDestroy()
{
142 Visual C++游戏开发技术与实例
m_iRefCount--;
if(m_iRefCount== 0)
{
if(m_lpDI!= NULL){
m_lpDI->Release();
m_lpDI= NULL;
}
}
}
};
cKeyboard 类代码如下所示:
classcKeyboard : cInputDevice
{
private:
staticLPDIRECTINPUTDEVICE8 m_lpDIKeyboard;
staticchar* m_KbdBuffer;
public:
BOOLCheckKey(const int cKey);
voidProcess();
voidDestroy();
BOOLCreate();
cKeyboard();
virtual~cKeyboard();
};
cMouse 类代码如下所示:
classcMouse : cInputDevice
{
public:
cMouse();
virtual~cMouse();
private:
longm_lXPos;
longm_lYPos;
DWORDm_bButton0;
DWORDm_bButton1;
staticLPDIRECTINPUTDEVICE8 m_lpDIMouse;
HANDLEm_hMouseEvent;
public:
DWORDGetX() { return m_lXPos; };
DWORDGetY() { return m_lYPos; };
BOOLGetRightButton() { return m_bButton1; };
BOOLGetLeftButton() { return m_bButton0; };
voidProcess();
voidDestroy();
BOOLCreate();
};
这3 个类主要处理用户在游戏中的输入信息。既然鼠标和键盘的处理依赖于DirectInput框架,所
第3 章2D 游戏开发143
以需要一个类用来初始化DirectInput 主对象,这就是cInputDevice 类。
在cInputDevice 类中有一个指向DirectInput接口的指针和引用计数。引用计数用来确定当前使用
DirectInput 主接口的类有多少个。注意引用计数和接口指针都是静态变量。cMouse类和cKeyboard类
都是从cInputDevice 类继承而来,使用同样的DirectInput 主对象。
cKeyboard 类关心键盘的输入。它有一个静态的变量,用来缓冲每个键值的状态。由于这个缓冲
是静态的,它允许在代码中任意一个地方创建cKeyboard对象,而使用同一块buffer。
cMouse 类关心鼠标的输入,它的工作原理和cKeyboard类类似。每次调用Process()函数的时候,
它改变内部变量从而反映鼠标在当前屏幕的位置和状态。
_ cSurface 类和cSprite类
cSurface 类声明如下:
struct SURFACE_SOURCE_INFO
{
HINSTANCE m_hInstance;
UINT m_nResource;
int m_iX;
int m_iY;
int m_iWidth;
int m_iHeight;
};
class cSurface
{
public:
SURFACE_SOURCE_INFO m_srcInfo;
void Restore();
LPDIRECTDRAWSURFACE7 GetSurface();
UINT Width();
UINT Height();
void Destroy();
COLORREF m_ColorKey;
BOOL Draw(LPDIRECTDRAWSURFACE7 lpDest, int iDestX= 0, int iDestY = 0, int iSrcX
= 0, int iSrcY = 0, int nWidth = 0, int nHeight =0);
BOOL Create(int nWidth, int nHeight, COLORREFdwColorKey = -1);
BOOL LoadBitmap(HINSTANCE hInst, UINT nRes, intnX = 0, int nY = 0, int nWidth =
0, int nHeight = 0);
cSurface(HINSTANCE hInst, UINT nResource, intnWidth, int nHeight, COLORREF
dwColorKey = -1);
cSurface();
virtual ~cSurface();
protected:
UINT m_Height;
UINT m_Width;
LPDIRECTDRAWSURFACE7 m_pSurface;
};
cSprite 类声明如下:
class cSprite
144 Visual C++游戏开发技术与实例
{
public:
void Rewind();
BOOL IsBegin();
BOOL IsEnd();
int m_iSpriteHeight;
int m_iSpriteWidth;
void Previous();
void Next();
int m_iRows;
int m_iCols;
int m_iAbsolutePosition;
BOOL Create(HINSTANCE hInst, UINT nResource, intiTileWidth, int iTileHeight,
COLORREF dwColorKey, int iSpriteWidth,intiSpriteHeight);
BOOL Draw(LPDIRECTDRAWSURFACE7 lpDest, intiDestX, int iDestY, BOOL bAdvance = TRUE,
int iSrcX=0, int iSrcY=0, int iWidth = -1, intiHeight = -1);
void Destroy();
cSurface m_surfTile;
cSprite();
virtual ~cSprite();
};
页面类cSurface 是DirectX页面对象的打包。cSurface是一个结构,在显存中它拥有游戏中所用到
的图形,这样在游戏的每次循环中都可以轻松地重新绘制屏幕。
cSprite 类是处理精灵的打包类。它拥有一个cSurface类的成员变量和相关精灵的信息。通过这个
类,可以自动的使精灵走动,而不用担心源页面的位置和大小。
_ cSoundInterface,cSound和cWavFile类
cSoundInterface 类声明如下:
class cSoundInterface
{
protected:
static LPDIRECTSOUND8 m_pDS;
static LPDIRECTSOUNDBUFFER m_pDSBPrimary;
public:
void SetListernerPosition(float fX, float fY,float fZ);
void Destroy();
LPDIRECTSOUND8 GetDirectSound();
cSoundInterface();
HRESULT Initialize( HWND hWnd, DWORD dwCoopLevel,DWORD dwPrimaryChannels = 2,
DWORD dwPrimaryFreq = 22050, DWORDdwPrimaryBitRate = 16);
HRESULT SetPrimaryBufferFormat(DWORDdwPrimaryChannels, DWORD dwPrimaryFreq, DWORD
dwPrimaryBitRate );
virtual ~cSoundInterface();
};
cSound 类声明如下:
class cSound
{
private:
第3 章2D 游戏开发145
DWORD m_dwDSBufferSize;
cWavFile* m_pWaveFile;
public:
void Destroy();
void SetVelocity(float fX, float fY, float fZ);
void SetPosition(float fX, float fY, float fZ);
LPTSTR m_sFileName;
HRESULT Stop(BOOL bOverride = FALSE);
BOOL m_bIsPlaying;
LPDIRECTSOUND3DBUFFER Get3DInterface();
HRESULT RestoreBuffer(BOOL *bRestored);
HRESULT Play(DWORD dwPriority = 0, DWORD dwFlags= 0);
LPDIRECTSOUNDBUFFER m_pSoundBuffer;
LPDIRECTSOUND3DBUFFER m_p3DInterface;
HRESULT Create(LPTSTR lpszFileName,DWORDdwCreationFlags,GUID guid3DAlgorithm);
cSound();
virtual ~cSound();
protected:
HRESULT FillBuffer();
};
cWavFile 类声明如下:
class cWavFile
{
public:
WAVEFORMATEX* m_pwfx; // Pointer to WAVEFORMATEXstructure
HMMIO m_hmmio; // MM I/O handle for the WAVE
MMCKINFO m_ck; // Multimedia RIFF chunk
MMCKINFO m_ckRiff; // Use in opening a WAVE file
DWORD m_dwSize; // The size of the wave file
MMIOINFO m_mmioinfoOut;
DWORD m_dwFlags;
BOOL m_bIsReadingFromMemory;
BYTE* m_pbData;
BYTE* m_pbDataCur;
ULONG m_ulDataSize;
CHAR* m_pResourceBuffer;
protected:
HRESULT ReadMMIO();
HRESULT WriteMMIO( WAVEFORMATEX *pwfxDest );
public:
cWavFile();
~cWavFile();
HRESULT Open( LPTSTR strFileName, WAVEFORMATEX*pwfx, DWORD dwFlags );
HRESULT OpenFromMemory( BYTE* pbData, ULONGulDataSize, WAVEFORMATEX* pwfx, DWORD
dwFlags );
HRESULT Close();
146 Visual C++游戏开发技术与实例
HRESULT Read( BYTE* pBuffer, DWORD dwSizeToRead,DWORD* pdwSizeRead );
HRESULT Write( UINT nSizeToWrite, BYTE* pbData,UINT* pnSizeWrote );
DWORD GetSize();
HRESULT ResetFile();
WAVEFORMATEX* GetFormat() { return m_pwfx; };
};
这3 个类主要负责游戏中声音的处理。cSoundInterface创建DirectSound主对象,从而创建了游戏
中的声音缓冲。推荐在cApplication 类中的AppInitializad 虚函数中初始化这个类,这样就可以在游戏
开始之前初始化声音接口。
cSound 类拥有游戏中声音缓冲。它兼容了DirectSound缓冲的所有普通属性,例如频率、3D声音
和重复。cSound 类使用cWavFile对象从资源或从文件中导入声音。这是一个波形文件的导入类。
_ cMultiplayer 和cMessageHandler类
cMultiplayer 类声明如下:
struct PLAYER_MESSAGE
{
int m_iType;
BYTE* pReceiveData;
};
struct PLAYER_INFO
{
DPNID dpnidPlayer;
};
struct HOST_NODE
{
DPN_APPLICATION_DESC* pAppDesc;
IDirectPlay8Address* pHostAddress;
WCHAR* pwszSessionName;
HOST_NODE* pNext;
};
typedef vector
typedef list
typedef list
class cMultiplayer : public cMessageHandler
{
private:
cMessageHandler* m_pHandler;
DWORD m_dwTcpPort;
int m_iModemDevice;
IDirectPlay8Peer *m_pDP;
IDirectPlay8Address *m_pDeviceAddress;
DPN_SERVICE_PROVIDER_INFO* m_pdnSPInfo;
DPN_SERVICE_PROVIDER_INFO* m_pdnDeviceInfo;
第3 章2D 游戏开发147
char m_sFlowControl[10];
char m_sParity[10];
char m_sStopBits[10];
DWORD m_dwNumSP;
DWORD m_dwNumDevices;
int m_iSPIndex;
int m_iSerialDevice;
HOST_NODE* m_pHostList;
int m_iNumHosts;
BOOL m_bCanConnect;
int m_iPlayerType;
DPNID m_dpnidHost;
DPNID m_dpnidSelf;
PLAYER_MESSAGES m_lstMessages;
CRITICAL_SECTION m_csMessages;
PLAYERINFO_VECTOR m_lstPlayers;
public:
void SetHandler(cMessageHandler *pHandler);
void ReleaseMessage();
int GetMyId();
void FindHost();
int GetHost();
void RemoveMessage(PLAYER_MESSAGE* pMessage);
PLAYER_MESSAGE* GetMessage(int iIndex);
int GetNumMessages();
void SendTo(DPNID dpnidTarget, int iType, LPBYTElpBuffer, DWORD dwSize, DWORD
dwFlags = DPNSEND_SYNC | DPNSEND_NOLOOPBACK);
PLAYER_INFO* GetPlayerInfo(int iIndex);
int GetNumConnected();
BOOL IsHosting();
void Connect(int iHost);
BOOL GetCanConnect();
void SetCanConnect(BOOL bValue);
void GetSessionName(int iIndex, char* sValue, intiSize);
int GetNumSessions();
BOOL EnumSessions();
BOOL Host(char* sSessionName);
void AdvanceSPOption(int iOption);
void GetSPOption(int iOption, char* szBuffer,DWORD dwBufferSize);
char* GetSPOptionName(int iOption);
int GetSPOptionType(int iOption);
int GetSPIndex();
void NextSP();
int GetNumSPOptions();
GUID* GetSPGuid(int iIndex);
char* GetSPName(int iIndex);
148 Visual C++游戏开发技术与实例
long GetNumSPs();
void Destroy();
void EnumServiceProviders();
static HRESULT WINAPIcMultiplayer::StaticDirectPlayMessageHandler(
PVOID pvUserContext,
DWORD dwMessageId,
PVOID pMsgBuffer );
HRESULT WINAPI cMultiplayer::DirectPlayMessageHandler(
PVOID pvUserContext,
DWORD dwMessageId,
PVOID pMsgBuffer );
BOOL Initialize();
cMultiplayer();
virtual ~cMultiplayer();
DWORD GetRegDWValue(char* lpszValue);
char* GetRegStringValue(char* lpszValue, char*szBuffer, DWORD dwBufLen);
void SetRegDWValue(char* lpszValueName, DWORDdwValue);
void SetRegStringValue(char* lpszValueName, char*lpszValue);
protected:
DWORD m_dwBaudRate;
void EnumDevices();
};
cMessageHandler 类声明如下:
class cMessageHandler
{
public:
cMessageHandler();
virtual ~cMessageHandler();
virtual void IncomingMessage(DWORD dwType, intidSender, BYTE* pBuffer, DWORD
dwBufferSize);
};
这两个类封装了DirectPlay 接口,被用来创建多人游戏。cMultiplayer 类处理所有DirectPlay功能,
例如装置枚举,会话枚举和玩家连接等。它也处理所有关于游戏会话中玩家相关的信息。需要注意的
是,每个玩家都拥有一个独一无二的ID,这些ID被存放在cMultiplayer中的一个列表中。在多人游戏
会话中,这些ID 被用来控制游戏相关的行为。
为了处理多人网络消息, cMultiplayer类拥有一个cMessageHandler指针的成员变量。
cMessageHandler 类是拥有一个虚函数的简单类。每次计算机从对方收到一个DirectPlay 消息的时候都
会调用这个虚函数。推荐从cMessageHandler类中继承一个类,这样可以自定义处理这种网络消息。
_ cMatrix 类
class cMatrix
{
private:
int m_iRows;
第3 章2D 游戏开发149
int m_iCols;
HANDLE hHeap;
int* pBuffer;
public:
void SetBuffer(void* pBuffer);
void SetValue(int iCol, int iRow, int iValue);
int GetValue(int iCol, int iRow);
void Destroy();
void Create(int iCols, int iRows);
cMatrix();
virtual ~cMatrix();
};
这个简单的类用来创建动态的矩阵。它的Create函数用来分配必要的内存,Destroy函数释放内存。
GetValue 和SetValue函数用来处理矩阵值。
_ cHitChecker 类
这个类用来检测游戏中的碰撞事件。这里的算法和入侵者游戏中碰撞检测算法相同,所以不再赘述。
3.8.2 游戏类和元素
上面对游戏库cMain 中的每个类都做了简要的介绍,下面来看一下游戏项目。这个项目和上面介
绍的游戏库项目是关联的。项目中每个类描述了游戏中不同的单元(游戏本身、赛道、赛车和比赛),
下面逐一介绍。
_ cRaceXApp 类
cRaceXApp 类是程序的主应用类:
class cRaceXApp : public cApplication,cMessageHandler
{
private:
// 游戏状态的启动标志
int iStart;
// 赛道名称
STRVECTOR pTrackNames;
// RaceTrack 对象
cRaceTrack pRaceTrack;
// 鼠标和键盘对象
cMouse m_Mouse;
cKeyboard m_Keyboard;
// 文字类对象,用来在屏幕中写文字信息,它们分别拥有各自的位图信息
cTextWriter m_txDigital;
cTextWriter m_txDigitalSmall;
cTextWriter m_txVerdana;
// cCompetition 类对象
cCompetition m_pCompetition;
// DXSound 打包的接口
cSoundInterface m_pSoundInterface;
// 多人管理对象
cMultiplayer m_pMultiPlayer;
150 Visual C++游戏开发技术与实例
public:
int GetDifficultyLevel();
int m_iDifficultyLevel;
cSurface m_surfCarPannel;
BOOL m_bSendKeyboard;
cMultiplayer* GetMultiplayer();
BOOL m_bIsMultiplayer;
BOOL CheckInput(string*, int iMax);
string m_sPlayerName;
string m_sGameName;
cSprite m_sptrCar;
int m_iIncrY;
int m_iIncrX;
// 声音对象
cSound m_sndChangeOption;
cSound m_sndSelect;
cSound m_sndType;
int m_iOption;
int m_iTrack;
// 页面对象
cSurface m_surfBigCars[4];
cSurface m_surfCursor;
cSurface m_surfCaret;
cSurface m_surfTitle;
cSurface m_surfHelmet;
cSurface m_surfPanel;
cSurface m_surfTophy;
cSurface m_surfPositions;
int m_iState;
cSurface m_hcHit;
cRaceCar* pCar;
// 显示选择赛道
int m_iX, m_iY;
//虚函数,结束游戏
void ExitApp();
cRaceXApp() : cApplication()
{
m_bSendKeyboard = TRUE;
m_lpszAppName = "RaceX";
m_lpszwndClassName = "RaceXWnd";
m_iState = GS_MAINSCREEN;
m_bIsMultiplayer = FALSE;
iStart = 0;
第3 章2D 游戏开发151
WIN32_FIND_DATA lpFindData;
HANDLE hFileFinder = FindFirstFile("*.rxt",&lpFindData);
if(hFileFinder)
{
char* lpTmpString;
do
{
lpTmpString = (char*)malloc(strlen(lpFindData.cFileName)+1);
memcpy(lpTmpString, lpFindData.cFileName,strlen(lpFindData.
cFileName)+1);
pTrackNames.push_back(lpTmpString);
lpTmpString = NULL;
}while(FindNextFile(hFileFinder, &lpFindData)!= 0);
}
}
~cRaceXApp()
{}
//3 个虚函数
void AppInitialized();
void DoIdle();
void IncomingMessage(DWORD dwType, int idSender,BYTE* pBuffer, DWORD
dwBufferSize);
};
这个类是从cApplication 和cMessageHandler 类继承而来。既然是从cApplication类继承而来,当
然可以应用AppInitialize、ExitApp和DoIDle函数。
在AppInitialize 中,做一些额外的初始化工作。需要注意的是,在cRaceXApp类中,已经有一些
变量用来处理声音接口、多人参与、鼠标和键盘输入了。所有这些成员变量都在这个虚函数中初始化。
在cMultiplayer 成员变量的初始化中调用SetHandler()函数,参数是this指针。传递this的原因是应用
类是从cMessageHandler 类继承而来的。使用这个类可以处理DirectPlay 网络消息。其中IncomingMessage
函数用来处理从DirectPlay 对口端接受网络消息。
在cRaceXApp 类中,实现了另一个函数DoIdle()。在DoIdle中建立了整个游戏的逻辑。这个函数
的第一件事就是检测m_iStata 成员变量,从而判断当前游戏状态。当开始游戏的时候,游戏的状态是
GS_MAINSCREEN,这代表了菜单屏幕模式。读者可以在DoIdle中检测这个GS_MAINSCREEN情况
的菜单结构。
DoIdle 函数中另一个重要的变量是iStart。可以使用这个变量来检测游戏是否从一个状态变换到了
另一个状态。当游戏状态改变的时候,这个变量将会被设置成0,从而在下一次游戏循环基类重新调
用DoIdle 函数的时候,新游戏状态下的页面和额外的初始化工作都会被载入。在新状态下的资源被成
功载入后,iStart 被重新设置成非0值。游戏状态的变化就是通过这个变量判断的。
当用户在游戏菜单屏幕中选择了某种游戏类型(单人、多人、单赛道、竞赛模式),游戏将会进入
赛道选择屏幕(或者直接进入竞赛屏幕)。当用户进入到赛道选择屏幕的时候,cRaceXApp类中其他
一些成员变量将会初始化:cCompetition和cRaceTrace类的对象。
_ cCompetition 类
struct PLAYER_DATA
{
152 Visual C++游戏开发技术与实例
string m_sPlayerName;
int m_iPoints;
UINT m_iCarColor;
int m_iControlType;
BYTE m_bId;
int m_dpnid;
BOOL m_bPlayerIsReady;
};
// 这个类用来控制游戏中的竞争,它拥有所有赛车的信息:名称,位置和ID。
class cCompetition
{
private:
PLAYER_DATA m_pPlayerData[4];
public:
BOOL AllPlayersReady();
void ResetReadyState();
void SetReadyState(BYTE bPlayerId, BOOL bReady);
BYTE GetPlayerID(int iIndex);
BYTE GetPlayerIDbyDPNID(int iDPNID);
int GetPlayerDPNID(BYTE idPlayer);
string GetPlayerName(BYTE idPlayer);
DWORD GetPlayerColor(BYTE idPlayer);
void SetPlayerName(BYTE idPlayer, char* sName);
void SetPlayerColor(BYTE idPlayer, UINT iColor);
BOOL DPNIDExists(int iID);
int GetControlType(int iPosition);
void AddPointToPlayer(BYTE bId,int iPoints);
int GetNumberOfCars();
int GetPlayerPointsByPosition(int iPosition);
void NextRace();
int m_iNextRace;
string GetNextRace();
UINT GetColor(UINT iPosition);
void AddPlayer(string sPlayerName, UINTiCarColor, int iControlType, BYTE bID, int
iDPNID);
void Reset();
int m_iNumPlayers;
BOOL GetCompetitionMode();
void SetCompetitionMode(BOOL bActivate);
BOOL m_bCompetitionActive;
cCompetition();
virtual ~cCompetition();
};
从名字上看,这个类主要处理游戏中的竞争模式(Competion Mode),事实上它还处理单赛道模式。
这个类存储了游戏中每个参赛选手的基本信息。当游戏开始的时候,需要调用这个类的AddPlayer函
数用来向游戏中添加选手。当赛道中出现了所有的选手后,游戏状态变成GS_RACE。注意GS_RACE
状态,它使用了cCompetition 对象的选手列表信息,从而创建每一辆赛车。
第3 章2D 游戏开发153
cCompetion 类中还存储了竞赛模式下(Competition Mode)需要用到的每位赛车手的分数和位置
信息,还负责赛道顺序的安排。
_ cRaceTrack 类
cRaceTrack 类主要用来控制游戏中的赛道,负责赛道的载入、绘制、碰撞检测和控制当前在跑的
赛车
class cRaceTrack
{
private:
// 当前赛道上的赛车数目
int m_iNumCars;
// 赛道的行数和列数,每一单元都是40×40 贴图
int m_iCols;
int m_iRows;
// 可视的赛道数
int m_iViewY;
int m_iViewX;
// 需要完成的圈数
int m_iLaps;
// 赛道名称
char *m_sTrackName;
// 状态变量,用来表示比赛是否开始,用来控制比赛开始时的信号灯显示
// 如果值是0,则显示第1 个信号;如果是2,则显示第2 个信号⋯⋯当值为2 之后的m_lStartTime 时
间时,就不再绘制信号灯
int m_iState;
long m_lStartTime;
// 使用哪种贴片(沙漠或者草地)
UINT m_Tile;
//赛道中用到的贴片页面
cSurface m_surfTile;
//比赛结束时旗帜的页面
cSurface m_surfRaceCompleted;
//下面所有定义的页面都代表了一种赛道贴片
cSurface m_surfDiagonalQ0;
cSurface m_surfDiagonalQ1;
cSurface m_surfDiagonalQ2;
cSurface m_surfDiagonalQ3;
cSurface m_surfMidDiagonalQ0;
cSurface m_surfMidDiagonalQ1;
cSurface m_surfMidDiagonalQ2;
cSurface m_surfMidDiagonalQ3;
cSurface m_surfBlackPointQ0;
cSurface m_surfBlackPointQ1;
cSurface m_surfBlackPointQ2;
cSurface m_surfBlackPointQ3;
154 Visual C++游戏开发技术与实例
cSurface m_surf_HZ_StartDiagonalQ0;
cSurface m_surf_HZ_StartDiagonalQ1;
cSurface m_surf_HZ_StartDiagonalQ2;
cSurface m_surf_HZ_StartDiagonalQ3;
cSurface m_surf_HZ_EndDiagonalQ0;
cSurface m_surf_HZ_EndDiagonalQ1;
cSurface m_surf_HZ_EndDiagonalQ2;
cSurface m_surf_HZ_EndDiagonalQ3;
cSurface m_surf_VR_StartDiagonalQ0;
cSurface m_surf_VR_StartDiagonalQ1;
cSurface m_surf_VR_StartDiagonalQ2;
cSurface m_surf_VR_StartDiagonalQ3;
cSurface m_surf_VR_EndDiagonalQ0;
cSurface m_surf_VR_EndDiagonalQ1;
cSurface m_surf_VR_EndDiagonalQ2;
cSurface m_surf_VR_EndDiagonalQ3;
cSurface m_surfSRaceRoadQ0;
cSurface m_surfSRaceRoadQ1;
cSurface m_surfSRaceRoadQ2;
cSurface m_surfSRaceRoadQ3;
cSurface m_surfHalfRoadQ0;
cSurface m_surfHalfRoadQ1;
cSurface m_surfHalfRoadQ2;
cSurface m_surfHalfRoadQ3;
cSurface m_surfFullRoadQ0;
cSurface m_surfFullRoadQ1;
cSurface m_surfFullRoadQ2;
cSurface m_surfFullRoadQ3;
cSurface m_surfFullRoadQ4;
cSurface m_surfFullRoadQ5;
cSurface m_surfFullRoadQ6;
cSurface m_surfFullRoadQ7;
cSurface m_surfFullRoadQ8;
cSurface m_surfFullRoadQ9;
cSurface m_surfFullRoadQ10;
cSurface m_surfFullRoadQ11;
cSurface m_surfFullRoadQ12;
cSurface m_surfFullRoadQ13;
cSurface m_surfFullRoadQ14;
cSurface m_surfFullRoadQ15;
cSurface m_surfFullRoadQ16;
cSurface m_surfFullRoadQ17;
cSurface m_surfCurveQ0;
cSurface m_surfCurveQ1;
第3 章2D 游戏开发155
cSurface m_surfCurveQ2;
cSurface m_surfCurveQ3;
cSurface m_surfEndCurveQ0;
cSurface m_surfEndCurveQ1;
cSurface m_surfEndCurveQ2;
cSurface m_surfEndCurveQ3;
cSurface m_surfMediumCurveQ0P1;
cSurface m_surfMediumCurveQ0P2;
cSurface m_surfMediumCurveQ0P3;
cSurface m_surfMediumCurveQ1P1;
cSurface m_surfMediumCurveQ1P2;
cSurface m_surfMediumCurveQ1P3;
cSurface m_surfMediumCurveQ2P1;
cSurface m_surfMediumCurveQ2P2;
cSurface m_surfMediumCurveQ2P3;
cSurface m_surfMediumCurveQ3P1;
cSurface m_surfMediumCurveQ3P2;
cSurface m_surfMediumCurveQ3P3;
cSurface m_surfEndMediumCurveQ0P1;
cSurface m_surfEndMediumCurveQ0P2;
cSurface m_surfEndMediumCurveQ0P3;
cSurface m_surfEndMediumCurveQ1P1;
cSurface m_surfEndMediumCurveQ1P2;
cSurface m_surfEndMediumCurveQ1P3;
cSurface m_surfEndMediumCurveQ2P1;
cSurface m_surfEndMediumCurveQ2P2;
cSurface m_surfEndMediumCurveQ2P3;
cSurface m_surfEndMediumCurveQ3P1;
cSurface m_surfEndMediumCurveQ3P2;
cSurface m_surfEndMediumCurveQ3P3;
//这个函数用来删除在赛道的主碰撞检测区域的每个贴片多边形区域
void RemoveHitRegion();
public:
//精灵控制信号灯的动画
cSprite m_sptrWait;
//赛车数组,它代表了当前赛道中比赛的所有赛车,对于每一次的比赛,都需要将所有赛车加入到这个数组中
cRaceCar* m_pRaceCars[4];
// 这个矩阵拥有赛道中所有贴片信息
cMatrix m_RoadMap;
156 Visual C++游戏开发技术与实例
// 这个碰撞类拥有赛道中所有有效区域的信息。如果有车碰到了无效区域,则它要爆炸
cHitChecker m_hcRoadMap;
// 信号灯的声音缓冲
cSound m_sndSemaphore;
BYTE GetLocalCarID();
void SetRemoteKeyboardStatus(BYTE bIdCar, BYTEbStatus);
int GetAngle(int iCol, int iRow);
void SetCarInfo(BYTE bID, BYTE bMask,
DWORD dwElapseTime,
BYTE bLaps,
int iSpeed,
BYTE bCarPosition,
DWORD dwLastLapTime,
unsigned short nCarX,
unsigned short nCarY,
BYTE bAngle,
BYTE bCarState);
char* GetFileName();
string m_sFileName;
int GetHeight();
int GetWidth();
cRaceCar* GetCar(int iPos);
int GetNumberOfCars();
int GetNumberOfLaps();
char* GetTrackName();
void GetCheckPointPosition(int *iX, int* iY,intiCheckPoint);
int GetRoadType(int iX, int iY);
void AddCar(cRaceCar* pCar);
void Process();
int m_iMaxCheckPoint;
int GetMaxCheckPoint();
int GetCheckPoint(int iX, int iY);
BOOL CarHittedRoad(cRaceCar* pCar, int iX = -1,int iY = -1);
int GetViewY();
int GetViewX();
void AdjustView(cRaceCar* pPar);
void GetStartPosition(int* iX, int* iY);
BOOL ReadFromFile(char* lpszFile);
void SetRoad(int iCol, int iRow, int iType);
void Draw(int iDestX = 0, int iDestY = 0, int nX=-1 , int nY =-1 , int nWidth =
640, int nHeight = 480);
void Destroy(BOOL bLastDestroy = FALSE);
void SetTile(UINT iTile);
void Create(int nWidth, int nHeight);
cRaceTrack();
virtual ~cRaceTrack();
};
cRaceTrack 类负责游戏中赛道的创建和处理。大多数游戏逻辑都在这个类中处理。调用这个类的
第3 章2D 游戏开发157
第一件事就是从赛道文件中载入赛道。这个类中的ReadFromFile函数负责从.rxt文件中读入赛道信息。
当从文件中载入赛道信息的时候,cRaceTrack类中的内部成员变量将会记住这些赛道信息。赛道
是结构化的二维矩阵,而矩阵中每个元素都代表了一个道路类型。程序将会按照这个矩阵中元素的值
用各种40×40分辨率的赛道贴片绘制赛道。在赛道文件中,DWORD类型的数组描述了赛道中的每个
贴片。DWORD 是4字节类型,所以可以使用LOWORD存储道路类型。HIWORD存储其他信息:检
查站CheckPoint(LOBYTE)和角度信息(HYBYTE)。
赛道文件中的CheckPoint 被用来控制赛道顺序。如果一个赛车通过了CheckPoint1,那么它就需
要通过CheckPoint2,直到最终到达最后一个CheckPoint。使用CheckPoint信息,可以阻止赛车手往回
跑造成的信息混乱。既然有了CheckPoint信息,就可以强迫赛车按序完成每个CheckPoint。
当赛车再次到达CheckPoint1 的时候,圈计数器增1。当圈计数器是最后一圈而且CheckPoint到
达了最后一个CheckPoint 的时候,比赛结束。
角度信息可以让电脑操纵赛车,因为它可以用来标志赛车前进的方向。cRaceCar类将会使用这个
信息。
_ cRaceCar 类
cRaceCar 定义了参加游戏的每辆赛车,这些赛车包括了颜色,id和选手名字属性
class cRaceCar
{
private:
int iTurnCar;
//定义赛车爆炸时间
int iCrashTime;
//到一个检查站的距离(用来定位)
int m_iDistanceToNextCheckPoint;
//赛车位置
int m_iPosition;
//声音缓冲
cSound m_pSound;
cSound m_pCrashSound;
//存储每一圈用时的Vector
LONGVECTOR m_vcLapTimes;
long m_lCurrentTime;
//这些是赛车发生碰撞时用到的备用变量
//如果赛车在发生碰撞后要重置状态,则使用这些变量
int m_fBackupIncrementX;
int m_fBackupIncrementY;
int m_fBackupDirectionY;
int m_fBackupDirectionX;
int m_iBackupAngle;
float m_fBackupPosY;
float m_fBackupPosX;
int iBackupTurnCar;
158 Visual C++游戏开发技术与实例
int m_iBackupDistanceToNextCheckPoint;
//赛车当前状态
int m_iCarState;
void TurnCarRight();
void TurnCarLeft();
//定义赛车如何控制(电脑、人、还是网络)
int m_iControlType;
//定义如何增加X 和Y 的位置
int m_fIncrementX;
int m_fIncrementY;
//赛车当前速度
int m_iSpeed;
//赛车最高速度
int m_iMaxSpeed;
//赛车在赛道地图上的当前位置
float m_fPosY;
float m_fPosX;
//上次赛车转向的时间
long m_lLastTurn;
//赛车上次加速时间
long m_lLastSpeedIncr;
// 标志X 和Y 的方向
int m_fDirectionY;
int m_fDirectionX;
//多边形边界
POINT *m_pBound;
public:
void MoveForward(int iSpeed);
void Backup();
DWORD m_dwElapseTime;
void AddLapTime(DWORD dwTime);
void SetSpeed(int iValue);
BYTE m_bLastSent_Speed;
BYTE m_bLastSent_Position;
BYTE m_bLastSent_CarState;
BYTE m_bLastSent_Laps;
BYTE m_bLastSent_Angle;
DWORD m_dwLastSent_LastLapTime;
unsigned short m_nLastSent_PosX;
第3 章2D 游戏开发159
unsigned short m_nLastSent_PosY;
BYTE m_bRemoteKeyboardStatus;
int m_iAngle;
BYTE GetID();
void SetID(BYTE iValue);
BYTE m_bID;
int GetCarColor();
long GetLastLapTime();
long GetLapElapseTime(int iLap = -1);
int GetDistanceToNextCheckPoint();
void SetPosition(int iPosition);
int GetPosition();
int m_iLaps;
int m_iBackupSprite;
void Crashed();
int GetCarState();
void SetCarState(int iState);
void Idle();
void HitCar();
void BreakCar();
void Accelerate();
int GetControlType();
void SetControlType(int iType);
int GetSpeed();
int m_iCheckPoint;
int GetLastCheckPoint();
void SetLastCheckPoint(int iLastCheckPoint);
cHitChecker m_hcRaceCar;
void GetPos(int* iX, int* iY);
void Process(void* pTrack);
void Destroy();
void Create(int iColor);
void Draw(int nViewX, int nViewY);
void SetPos(int iX, int iY);
cSprite m_sprCar;
cSprite m_sprCarExplode_0;
cSprite m_sprCarExplode_45;
cSprite m_sprCarExplode_90;
cSprite m_sprCarExplode_135;
cSprite m_sprCarExplode_180;
cSprite m_sprCarExplode_225;
cSprite m_sprCarExplode_270;
cSprite m_sprCarExplode_315;
cRaceCar();
virtual ~cRaceCar();
160 Visual C++游戏开发技术与实例
protected:
int m_iColor;
void RotateBound(int iAngle);
};
cRaceCar 类处理游戏中所有赛车的行为。这个赛车类中最重要的函数是Process,它处理游戏循环
中赛车的所有动作。
Process 函数中将会检测赛车是如何控制的。赛车可以被电脑控制,被玩家控制,也可以被网络用
户控制。
如果赛车是被玩家控制的,赛车将会检测用户的键盘输入,判断用户什么时候在加速、刹车或者
在转弯。依靠从键盘中获取的信息,cRaceCar类调用Accelerate、BreakCar、TurnCarRight或者TurnCarLeft
函数。
如果这个赛车被电脑控制,赛车类将会检测当前赛车的位置和与该位置关联的赛道的角度。如果
这个赛道角度和当前赛车行进角度不同,电脑将会让赛车按顺时针或逆时针转弯。电脑控制的赛车一
般都处在加速状态,除非它检测到以这个速度行进下去将会和赛道旁边的墙发生碰撞。
如果赛车是被网络用户控制的,那么就要检测这个赛车是否是游戏服务器主机(hoster)。如果玩
家是游戏的hoster,那么就要基于键盘输入从远端计算机处理赛车信息。否则,发送键盘信息到多人
游戏的主机。
3.8.3 游戏的流程
在游戏中,开始比赛的时候,首先将游戏状态改变成GS_RACE,然后根据cCompetition类信息
创建一个cRaceCar 对象。接着调用cRaceTrack类的AddCar()函数添加赛道中的赛车。
在游戏的每次循环里,都要调用cRaceTrack类的Process()函数。在这个函数里,会遍历cRaceTrack
中的赛车数组,然后改变赛车状态。另外,还要使用cHitChecker类检测每辆赛车是否碰到了墙或者
其他赛车。如果赛车碰到了墙,则改变赛车的状态为CARSTATE_CRASHEDWALL。
接下来,看看程序中使用到的核心函数和程序流程。
首先是Win32 程序的入口函数WinMain,这个函数是在cMain库中的cApplication.cpp文件中定
义的。
int WINAPI WinMain(HINSTANCE hInst, HINSTANCEhPrevInst, LPSTR lpCmdLine, int iCmdShow)
{
cApplication::m_hInst = hInst;
pApp = CreateApplication();
CoInitialize(NULL);
if(!pApp->InitApplication())
{
MessageBox(NULL, "Error Starting upapplication.\n Close all open programs and
try again.", "Error",MB_ICONERROR);
return 0;
}
if(!pApp->InitDirectX())
{
MessageBox(NULL, "Error Starting upDirectX.\n Check if you have Microsoft
DirectX correctly installed on your machine.\nYou can download the latest version from
http://www.microsoft.com/directx.","Error", MB_ICONERROR);
return 0;
第3 章2D 游戏开发161
}
//调用子类的虚函数设置游戏配置
pApp->AppInitialized();
//开始消息循环
pApp->RunApplication();
delete pApp;
return 0;
}
WinMain 中使用的CreateApplication是在游戏类RaceX.cpp中定义的全局函数。
cApplication* CreateApplication()
{
cRaceXApp* theApp;
theApp = new cRaceXApp();
return (cApplication*)theApp;
}
由此可见cRaceXApp 类是程序的主控制流。而在WinMain中还调用了cRaceXApp类的父类
cApplication 的初始化函数InitApplication,DirectX初始化函数InitDirectX和消息循环函数
RunApplication 和cRaceXApp类的虚函数AppInitialized。
BOOL cApplication::InitApplication()
{
m_bActive = TRUE;
//首先是注册窗口
if(FAILED(m_pWindow.RegisterWindow(m_lpszwndClassName)))
return FALSE;
// 创建窗口
if(!m_pWindow.Create(m_lpszAppName))
return FALSE;
return TRUE;
}
InitDirectX 函数如下所示:
BOOL cApplication::InitDirectX()
{
HRESULT hRet;
LPDIRECTDRAW pDD;
DDSURFACEDESC2 ddsd;
DDSCAPS2 ddscaps;
// 首先创建DirectX 对象
hRet = DirectDrawCreate( NULL, &pDD, NULL );
if(hRet != DD_OK)
return FALSE;
// 获取DirectDraw7接口
hRet = pDD->QueryInterface(IID_IDirectDraw7,(LPVOID*)&m_pDD);
162 Visual C++游戏开发技术与实例
if(hRet != DD_OK)
return FALSE;
// 既然不再需要DirectDraw接口,那么就释放它
pDD->Release();
// 设置成满屏模式
hRet =m_pDD->SetCooperativeLevel(m_pWindow.GetHWnd(), DDSCL_EXCLUSIVE |
DDSCL_FULLSCREEN);
if(hRet != DD_OK)
return FALSE;
// 设置显示模式
hRet=m_pDD->SetDisplayMode(m_ScreenWidth,m_ScreenHeight,m_ColorDepth,0,0);
if(hRet != DD_OK)
return FALSE;
// 创建主页面,这里没有使用cSurface
ZeroMemory( &ddsd, sizeof( ddsd ) );
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
DDSCAPS_FLIP |DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
hRet = m_pDD->CreateSurface(&ddsd,&m_pFrontBuffer, NULL );
if(hRet != DD_OK)
return FALSE;
// 获得后台缓冲区指针
ZeroMemory(&ddscaps, sizeof(ddscaps));
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
hRet = m_pFrontBuffer->GetAttachedSurface(
&ddscaps,&m_pBackBuffer);
if(hRet != DD_OK)
return FALSE;
return TRUE;
}
RunApplication 函数如下所示:
BOOL cApplication::RunApplication()
{
// 消息循环系统,DoIdle 是个虚函数,要在继承类中实现
MSG msg;
BOOL bContinue = TRUE;
HRESULT hRet;
while (bContinue)
{
//检测消息池
if( PeekMessage( &msg, NULL, 0, 0,PM_NOREMOVE ) )
{
第3 章2D 游戏开发163
//如果是结束消息,则调用继承类的ExitApp 函数
if(msg.message == WM_QUIT)
ExitApp();
if( !GetMessage( &msg, NULL, 0, 0 ) )
{
return msg.wParam;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else //没有消息,表示CPU 处于Idle 状态
{
// 调用继承类的DoIdle 函数
DoIdle();
if(pApp->m_bActive)
{
if(m_bDontFlip == false)
{
hRet = m_pFrontBuffer->Flip(NULL, 0 );
if(hRet != DD_OK)
{
DXTRACE_MSG("Error Flipping Front Buffer!\n");
}
}
else
m_bDontFlip = false;
}
else
{
WaitMessage();
}
}
}
return TRUE;
}
cRaceXApp 类的虚函数AppInitialized定义如下:
void cRaceXApp::AppInitialized()
{
//显示鼠标光标
ShowCursor(FALSE);
//初始化声音接口
m_pSoundInterface.Initialize(GetMainWnd(),DSSCL_PRIORITY );
//创建DirectInput键盘对象
if(m_Keyboard.Create() == FALSE)
{
DXTRACE_MSG("Failed To Initialize KeyboardInput");
PostQuitMessage(0);
}
164 Visual C++游戏开发技术与实例
//创建DirectInput鼠标对象
if(m_Mouse.Create() == FALSE)
{
DXTRACE_MSG("Failed To Initialize KeyboardInput");
PostQuitMessage(0);
}
//创建DirectPlay对象
if(m_pMultiPlayer.Initialize() == FALSE)
{
DXTRACE_MSG("Failed To InitializeMultiplayer Support.");
PostQuitMessage(0);
}
else
{
m_pMultiPlayer.SetHandler(this);
m_pMultiPlayer.EnumServiceProviders();
}
m_iDifficultyLevel =m_pMultiPlayer.GetRegDWValue("DifficultyLevel");
if(m_iDifficultyLevel == NULL)
{
m_iDifficultyLevel = 20;
m_pMultiPlayer.SetRegDWValue("DifficultyLevel",(DWORD) m_iDifficultyLevel);
}
m_txDigital.Create();
}
而cRaceXApp 类的虚函数OnIdle定义如下。因为该函数代码很多,所以这里只将它的伪码做一
个介绍。OnIdle 是整个游戏中最核心的函数,它负责处理整个游戏逻辑。每次系统空闲的时候,
cApplication 基类对象都会调用这个DoIdle虚函数。
void cRaceXApp::DoIdle()
{
定义一些静态变量用来控制游戏时间信息
图像帧是以30ms 的频率刷屏,如果达不到30ms 的刷新率,就不交换主页面
更新LastIdle 变量
用黑色清除后台缓冲
根据游戏状态,显示相应的游戏屏幕
switch(m_iState)
{
case GS_HELP: //帮助屏幕
显示帮助信息
如果用户敲击了回车、空格或ESC 键,则回到主屏幕
break;
case GS_WAITCOMPETITION: //等待屏幕
这个状态是在多人游戏中才会出现的。在竞赛模式下,也许该用户看到比赛状态屏幕时,其他网络用户
还没有看到,则绘制这个等待屏幕。
检测是否所有用户都准备就绪。
break;
case GS_WAIT: //等待状态
第3 章2D 游戏开发165
这个状态是在网络游戏中才会出现的。如果已经准备开始比赛,而还有一些玩家没有完成它们的配置信
息,那么就需要等待。
检测是否所有玩家都准备就绪。
如果是在竞赛模式,那么下一步将会进入竞赛状态屏幕。如果是单赛道模式,那么开始比赛。
break;
case GS_CREDITS: //显示游戏分数的状态
显示游戏的分数
如果用户键入回车、空格或者ESC 键,则回到主屏幕
break;
case GS_NETWORKSTATUS: //网络状态
这个状态是多人游戏中使用的,它显示了当前游戏连接的玩家数目、玩家名称和赛车颜色等。
如果本次循环中玩家的数目和上次循环的玩家数目不一样,那么新连接玩家将会加入到当前的游戏中。
如果用户是游戏创建者,那么需要按下回车键开始和其他玩家比赛;否则开始命令是由其他主机控
制的。
处理键盘输入。
break;
case GS_CREATEGAME: //创建游戏状态
这是多人游戏中使用的状态,当用户创建一个新的游戏会话时,将显示新游戏面板,用户可以进入
游戏。
处理用户键盘输入。
处理用户鼠标输入。
break;
case GS_JOINGAME: //加入游戏状态
用户尝试加入一个已有的多人游戏
程序会自动搜索网络上存在的多人游戏。
处理用户键盘输入。
处理用户鼠标输入。
break;
case GS_MAINSCREEN: //主屏幕状态
创建主屏幕,在屏幕中间绘制贴片。
绘制必要的主菜单和从菜单。
处理键盘输入
break;
case GS_SELECTTRACK_AND_CAR: //选择赛道和赛车状态
载入赛道信息
绘制可选的赛车精灵
处理键盘输入。
处理鼠标输入。
break;
case GS_RACE: //绘制赛道状态
处理键盘输入。
跟踪比赛信息。
在屏幕中绘制比赛状态统计
break;
case GS_RACECOMPLETED: //比赛完成状态
当用户完成比赛的时候会显示这个屏幕。它显示每个赛车的牌位和每圈消耗的时间。
处理键盘输入。
break;
case GS_COMPETITIONSTATUS: //竞赛状态
显示比赛状态,显示每位车手在比赛中的牌位和下一圈的信息,如果比赛结束则显示成绩。
处理键盘输入。
break;
166 Visual C++游戏开发技术与实例
}
}
下面看一下cRaceTrack 类的核心成员函数Process()。
在游戏的每次循环里,都会调用cRaceTrack类的Process函数处理赛车数据。Process所做的事情
是检测所有的赛车两两之间是否发生了碰撞,如果有碰撞发生,则让发生碰撞的车减速,如果有车越
过了赛道,则要发生爆炸。
void cRaceTrack::Process()
{
int iOffset = 0, i=0;
int iSrcCarX, iSrcCarY, iCarX, iCarY;
if(((GetRaceXApp()->m_bIsMultiplayer == TRUE
&&GetRaceXApp()->GetMultiplayer()->IsHosting())
|| GetRaceXApp()->m_bIsMultiplayer == FALSE)
&& m_iState > 2)
{
for(i=m_iNumCars-1;i>=0;i--)
{
for(int j=0;j
{
if(i!=j)
{
m_pRaceCars[j]->GetPos(&iCarX,&iCarY);
m_pRaceCars[i]->GetPos(&iSrcCarX,&iSrcCarY);
if(m_pRaceCars[j]->m_hcRaceCar.HaveHitted
(&m_pRaceCars[i]->m_hcRaceCar, iCarX,
iCarY, iSrcCarX, iSrcCarY) == TRUE)
{
if(m_pRaceCars[j]->GetSpeed() != 0)
{
if(m_pRaceCars[i]->GetLastCheckPoint()<
m_pRaceCars[j]->GetLastCheckPoint())
{
m_pRaceCars[i]->HitCar();
m_pRaceCars[j]->MoveForward(
m_pRaceCars[j]->GetSpeed());
m_pRaceCars[j]->Accelerate();
}
else if (m_pRaceCars[i]->GetLastCheckPoint()
{
m_pRaceCars[j]->HitCar();
m_pRaceCars[i]->MoveForward(
m_pRaceCars[j]->GetSpeed());
m_pRaceCars[i]->Accelerate();
}
else
{
if(m_pRaceCars[i]->GetDistanceToNextCheckPoint()
> m_pRaceCars[j]->GetDistanceToNextCheckPoint())
第3 章2D 游戏开发167
{
m_pRaceCars[i]->HitCar();
m_pRaceCars[j]->MoveForward(m_pRaceCars[j]->
GetSpeed());
m_pRaceCars[j]->Accelerate();
}
else
{
m_pRaceCars[j]->HitCar();
m_pRaceCars[i]->MoveForward(m_pRaceCars[j]->
GetSpeed());
m_pRaceCars[i]->Accelerate();
}
}
}
}
}
}
}
}
// 在这个循环里,处理每辆赛车并设置它们的位置
for(i=0;i { // 设置当前赛车位置 if( ( (GetRaceXApp()->m_bIsMultiplayer == TRUE &&GetRaceXApp()->GetMultiplayer()->IsHosting()) || GetRaceXApp()->m_bIsMultiplayer == FALSE) ) m_pRaceCars[i]->SetPosition(i+1); // 只有信号灯变绿的时候,才调用赛车类的Process 函数 if(m_iState > 2) m_pRaceCars[i]->Process(this); // 这里检测赛车是否碰到了赛车道的边缘,如果是则设置赛车状态为CARSTATE_CRASHED_WALL,即发生 爆炸 if( ( (GetRaceXApp()->m_bIsMultiplayer == TRUE&& GetRaceXApp()->GetMultiplayer()->IsHosting()) || GetRaceXApp()->m_bIsMultiplayer == FALSE)) { if(m_pRaceCars[i]->GetCarState() ==CARSTATE_OK) { if(CarHittedRoad(m_pRaceCars[i]) == TRUE) { m_pRaceCars[i]->SetCarState(CARSTATE_CRASHED_WALL); } } } // 处理完赛车位置后,处理赛车视角,即运动方向 if(m_pRaceCars[i]->GetControlType() == CTRL_USER|| 168 Visual C++游戏开发技术与实例 m_pRaceCars[i]->GetControlType() ==CTRL_NETWORK_LOCAL) { AdjustView(m_pRaceCars[i]); } } // 处理完所有赛车的数据后,要绘制赛车 for(i=0;i { if(!(m_pRaceCars[i]->GetCarState() ==CARSTATE_RACECOMPLETED &&m_pRaceCars[i]->GetLapElapseTime()> 5000)) m_pRaceCars[i]->Draw(GetViewX(), GetViewY()); if(m_pRaceCars[i]->GetCarState() ==CARSTATE_RACECOMPLETED &&(m_pRaceCars[i]->GetControlType()==CTRL_USER ||m_pRaceCars[i]->GetControlType() ==CTRL_NETWORK_LOCAL)) { m_surfRaceCompleted.Draw(GetMainApp()->m_pBackBuffer,245, 190); } } // 绘制信号灯的部分 switch(m_iState) { case 0: m_sptrWait.Create(GetMainApp()->GetInstHandle(), IDB_TRAFFICLIGHT, 120, 60, RGB(0,0,0), 40, 60); m_sptrWait.m_iAbsolutePosition = 0; m_sptrWait.Draw(GetMainApp()->m_pBackBuffer,10, 10, FALSE); m_lStartTime = GetTickCount(); m_iState++; m_sndSemaphore.Play(); break; case 1: if(GetTickCount() - m_lStartTime > 1200) { m_sptrWait.Draw(GetMainApp()->m_pBackBuffer,10, 10, TRUE); m_lStartTime = GetTickCount(); m_iState++; m_sndSemaphore.Play(); } else { m_sptrWait.Draw(GetMainApp()->m_pBackBuffer,10, 10, FALSE); } break; case 2: if(GetTickCount() - m_lStartTime > 1200) { m_sptrWait.Draw(GetMainApp()->m_pBackBuffer,10, 10, TRUE); m_lStartTime = GetTickCount(); m_iState++; m_sndSemaphore.Play(); 第3 章2D 游戏开发169 } else { m_sptrWait.Draw(GetMainApp()->m_pBackBuffer,10, 10, FALSE); } break; default: if(GetTickCount() - m_lStartTime < 1200) { m_sptrWait.Draw(GetMainApp()->m_pBackBuffer,10, 10, FALSE); } break; } if(m_iState > 2) qsort(m_pRaceCars, m_iNumCars, sizeof(cRaceCar*),compare); } 在上面介绍的Process()函数中,还调用了赛车类cRaceCar的核心成员函数Process。这也是要介 绍的最后一个函数,定义如下: void cRaceCar::Process(void* pTrack) { #define SPEEDXTURN 70 #define TURN_TIME 45 #define RAISESPEED_TIME 85 #define MAX_SPEED_FOR_CURVE 180 #define MAX_SPEED_FOR_IDLE 200 //升高速度量从而降低游戏速度 //降低速度量从而使游戏更快速 #define _SPEED_FACTOR_ 17 int iX, iY; static long lLastMessage; cRaceTrack* theTrack = (cRaceTrack*)pTrack; cKeyboard pKeyboard; //如果赛车不在正常状态 if(m_iCarState != CARSTATE_OK) { //赛车发生碰撞,需要调入爆炸的声音 if(iCrashTime == 0) { iCrashTime = GetTickCount(); if(m_iCarState != CARSTATE_RACECOMPLETED) m_pCrashSound.Play(); } //爆炸 Crashed(); if(m_iCarState == CARSTATE_RACECOMPLETED) { 170 Visual C++游戏开发技术与实例 } else { if(GetTickCount() - iCrashTime > 2500) { m_sprCarExplode_0.Destroy(); m_sprCarExplode_135.Destroy(); m_sprCarExplode_180.Destroy(); m_sprCarExplode_225.Destroy(); m_sprCarExplode_270.Destroy(); m_sprCarExplode_315.Destroy(); m_sprCarExplode_45.Destroy(); m_sprCarExplode_90.Destroy(); if( (GetRaceXApp()->m_bIsMultiplayer == TRUE&& GetRaceXApp() ->GetMultiplayer()->IsHosting()) || GetRaceXApp()->m_bIsMultiplayer == FALSE) { m_fDirectionX = m_fBackupDirectionX; m_fDirectionY = m_fBackupDirectionY; m_fIncrementX = m_fBackupIncrementX; m_fIncrementY = m_fBackupIncrementY; m_iDistanceToNextCheckPoint =m_iBackupDistanceToNextCheckPoint; iTurnCar = iBackupTurnCar; m_iAngle = m_iBackupAngle; m_fPosX = m_fBackupPosX; m_fPosY = m_fBackupPosY; m_sprCar.m_iAbsolutePosition = m_iBackupSprite; if(theTrack->GetRoadType((int)m_fPosX+10, (int)m_fPosY+10) == ID_ROADTYPE_ROADQ2 || theTrack->GetRoadType((int)m_fPosX+10, (int)m_fPosY+10) == ID_ROADTYPE_MEDIUMCURVEQ0P3|| theTrack->GetRoadType((int)m_fPosX+10, (int)m_fPosY+10) == ID_ROADTYPE_MEDIUMCURVEQ0P2) { m_fPosY+=10; } if(theTrack->GetRoadType((int)m_fPosX+10, (int)m_fPosY+10) == ID_ROADTYPE_ROADQ3|| theTrack->GetRoadType((int)m_fPosX+10, (int)m_fPosY+10) == ID_ROADTYPE_MEDIUMCURVEQ2P3|| theTrack->GetRoadType((int)m_fPosX+10, (int)m_fPosY+10) == ID_ROADTYPE_MEDIUMCURVEQ2P2) { m_fPosY-=10; } if(theTrack->GetRoadType((int)m_fPosX+10, (int)m_fPosY+10) == ID_ROADTYPE_ROADQ0 || theTrack->GetRoadType((int)m_fPosX+10, (int)m_fPosY+10) == ID_ROADTYPE_MEDIUMCURVEQ2P1|| theTrack->GetRoadType((int)m_fPosX+10, (int)m_fPosY+10) == ID_ROADTYPE_MEDIUMCURVEQ2P2) { 第3 章2D 游戏开发171 m_fPosX+=10; } if(theTrack->GetRoadType((int)m_fPosX+10,(int) m_fPosY+10) == ID_ROADTYPE_ROADQ1 || theTrack->GetRoadType((int)m_fPosX+10, (int)m_fPosY+10) == ID_ROADTYPE_MEDIUMCURVEQ3P1|| theTrack->GetRoadType((int)m_fPosX+10, (int)m_fPosY+10) == ID_ROADTYPE_MEDIUMCURVEQ3P2) { m_fPosX-=10; } RotateBound(m_iAngle); iX = 0; iY = 0; m_iDistanceToNextCheckPoint = 999999999; do { iX = iX / 40; iY = iY / 40; theTrack->GetCheckPointPosition(&iX,&iY, m_iCheckPoint+1); if(m_iDistanceToNextCheckPoint > (int) sqrt( ((m_fPosX - iX)*(m_fPosX - iX)) + ((m_fPosY -iY)*(m_fPosY - iY)) )) { m_iDistanceToNextCheckPoint = (int) sqrt (((m_fPosX - iX)*(m_fPosX - iX)) + ((m_fPosY -iY)*(m_fPosY - iY))); } if(iX != -1) iX += 40; }while(iX>0); for(inti=0;i { if(m_bID !=theTrack->m_pRaceCars[i]->GetID()) { int iSrcX, iSrcY; theTrack->m_pRaceCars[i]->GetPos(&iSrcX,&iSrcY); if(m_hcRaceCar.HaveHitted(&theTrack->m_pRaceCars[i] ->m_hcRaceCar, m_fPosX, m_fPosY, iSrcX, iSrcY)== FALSE) { m_iCarState = CARSTATE_OK; iCrashTime = 0; } } } } } } } else { int i; iCrashTime = 0; 172 Visual C++游戏开发技术与实例 //如果赛车由人控制,则读取键盘值,判断用户意图 if(m_iControlType == CTRL_USER) { if(pKeyboard.CheckKey(DIK_RIGHT)) TurnCarRight(); if(pKeyboard.CheckKey(DIK_LEFT)) TurnCarLeft(); if(pKeyboard.CheckKey(DIK_DOWN)) BreakCar(); if(pKeyboard.CheckKey(DIK_UP)) Accelerate(); else { Idle(); } } //远端用户,发送键盘消息到主机 if(m_iControlType == CTRL_NETWORK_REMOTE&& GetRaceXApp()->m_bIsMultiplayer == TRUE&& GetRaceXApp()->GetMultiplayer()->IsHosting()) { // 键盘状态是一个字节变量,设置某些位从而判断哪个键被按下 if(m_bRemoteKeyboardStatus & 0x01) TurnCarRight(); if(m_bRemoteKeyboardStatus & 0x02) TurnCarLeft(); if(m_bRemoteKeyboardStatus & 0x04) BreakCar(); if(m_bRemoteKeyboardStatus & 0x08) Accelerate(); else Idle(); } //电脑控制的赛车 if(m_iControlType == CTRL_COMPUTER) { BOOL bCanBrake = FALSE; //如果当前地图中贴片的角度和当前赛车的行进角度不一样,则需调整赛车的行进角度 if(m_iAngle != theTrack->GetAngle(m_fPosX+20,m_fPosY+20) && theTrack->GetAngle(m_fPosX+20, m_fPosY+20) !=370) { int iValue1 =(360-m_iAngle)+theTrack->GetAngle(m_fPosX+20, m_fPosY+20); if(iValue1 > 360) { iValue1 = iValue1 % 360; } int iValue2 =(m_iAngle-theTrack->GetAngle(m_fPosX+20, m_fPosY+20)); 第3 章2D 游戏开发173 if(iValue2 < 0) { iValue2+=360; } if(iValue1 < iValue2) { TurnCarRight(); bCanBrake = TRUE; } else { TurnCarLeft(); bCanBrake = TRUE; } } //如果速度过快,则刹车 if(m_iSpeed > 270) { if(theTrack->CarHittedRoad(this, (int)m_fPosX+20+(m_iSpeed * (m_fDirectionX/9.0f)),(int)m_fPosY+20+(m_iSpeed *(m_fDirectionY/9.0f))) == TRUE) BreakCar(); } if(bCanBrake == TRUE) { for(i=m_iSpeed-30;i>=0;i-=40) { if(theTrack->CarHittedRoad(this,(int)m_fPosX+20+(i*(m_fDirectionX/9.0f)),(int) m_fPosY+20+ (i * (m_fDirectionY/9.0f))) == TRUE) { BreakCar(); } } } Accelerate(); }/*电脑控制*/ } //如果赛车被一个远端用户控制 if(m_iControlType == CTRL_NETWORK_LOCAL&& GetRaceXApp()->m_bIsMultiplayer == TRUE&& !GetRaceXApp()->GetMultiplayer()->IsHosting()) { m_bRemoteKeyboardStatus = 0; //从远端读取键盘状态值 if(pKeyboard.CheckKey(DIK_RIGHT)) { m_bRemoteKeyboardStatus |= 0x01; } if(pKeyboard.CheckKey(DIK_LEFT)) { 174 Visual C++游戏开发技术与实例 m_bRemoteKeyboardStatus |= 0x02; } if(pKeyboard.CheckKey(DIK_DOWN)) { m_bRemoteKeyboardStatus |= 0x04; } if(pKeyboard.CheckKey(DIK_UP)) { m_bRemoteKeyboardStatus |= 0x08; } if(GetRaceXApp()->m_bSendKeyboard == TRUE || GetTickCount()-lLastMessage > 100) { GetRaceXApp()->GetMultiplayer()->SendTo(GetRaceXApp() ->GetMultiplayer()->GetHost(),MSG_KEYBOARD_STATUS, &m_bRemoteKeyboardStatus,1,0/*DPNSEND_SYNC*/); GetRaceXApp()->m_bSendKeyboard = FALSE; lLastMessage = GetTickCount(); } } if((GetRaceXApp()->m_bIsMultiplayer==TRUE&&GetRaceXApp() ->GetMultiplayer()->IsHosting())|| GetRaceXApp()->m_bIsMultiplayer == FALSE) { //处理完用户/电脑/网络输入后,重新定位赛车位置 m_fPosY = m_fPosY + ((float)m_fDirectionY / 9.0f)* ((float)m_iSpeed / _SPEED_FACTOR_); m_fPosX = m_fPosX + ((float)m_fDirectionX / 9.0f)* ((float)m_iSpeed / _SPEED_FACTOR_); if(m_iCarState == CARSTATE_OK) { if(m_iCheckPoint == -1) { if(theTrack->GetCheckPoint((int)m_fPosX+10,(int)m_fPosY+18)==1) { m_iCheckPoint = 1; m_lCurrentTime = GetTickCount(); iX = 0; iY = 0; m_iDistanceToNextCheckPoint = 999999999; do { iX = iX / 40; iY = iY / 40; theTrack->GetCheckPointPosition(&iX,&iY, m_iCheckPoint+1); if(m_iDistanceToNextCheckPoint > (int) sqrt( ((m_fPosX - iX)*(m_fPosX - iX)) + ((m_fPosY - iY)*(m_fPosY - iY)) )) { m_iDistanceToNextCheckPoint = (int) sqrt(((m_fPosX - iX)*(m_fPosX - iX)) + ((m_fPosY - iY)*(m_fPosY - iY))); 第3 章2D 游戏开发175 } if(iX != -1) iX += 40; }while(iX>0); } } else { if(theTrack->GetCheckPoint((int)m_fPosX+10,(int)m_fPosY+18) <= m_iCheckPoint) { if(theTrack->GetCheckPoint((int)m_fPosX+10,(int)m_fPosY+18) == 1 && m_iCheckPoint == theTrack->GetMaxCheckPoint()) { m_vcLapTimes.push_back(GetTickCount()-m_lCurrentTime); m_lCurrentTime = GetTickCount(); m_iCheckPoint = 1; iX = 0; iY = 0; m_iDistanceToNextCheckPoint = 999999999; do { iX = iX / 40; iY = iY / 40; theTrack->GetCheckPointPosition(&iX,&iY, m_iCheckPoint+1); if(m_iDistanceToNextCheckPoint>(int) sqrt( ((m_fPosX - iX)*(m_fPosX - iX))+ ((m_fPosY - iY)*(m_fPosY - iY)))) { m_iDistanceToNextCheckPoint = (int) sqrt( ((m_fPosX - iX)*(m_fPosX - iX))+ ((m_fPosY - iY)*(m_fPosY - iY))); } if(iX != -1) iX += 40; }while(iX>0); m_iLaps++; if(m_iLaps == theTrack->GetNumberOfLaps()) { m_iCarState = CARSTATE_RACECOMPLETED; } else { if(m_iControlType == CTRL_USER) theTrack->m_sndSemaphore.Play(); } } else { iX = 0; iY = 0; 176 Visual C++游戏开发技术与实例 m_iDistanceToNextCheckPoint = 999999999; do { iX = iX / 40; iY = iY / 40; theTrack->GetCheckPointPosition(&iX,&iY, m_iCheckPoint+1); if(m_iDistanceToNextCheckPoint > (int) sqrt( ((m_fPosX - iX)*(m_fPosX - iX))+ ((m_fPosY - iY)*(m_fPosY - iY)))) { m_iDistanceToNextCheckPoint = (int) sqrt( ((m_fPosX - iX)*(m_fPosX - iX))+ ((m_fPosY - iY)*(m_fPosY - iY))); } if(iX != -1) iX += 40; }while(iX>0); } } else { if(theTrack->GetCheckPoint((int)m_fPosX+10,(int) m_fPosY+18)==m_iCheckPoint+1) { m_iCheckPoint++; iX = 0; iY = 0; m_iDistanceToNextCheckPoint = 999999999; do { iX = iX / 40; iY = iY / 40; theTrack->GetCheckPointPosition(&iX,&iY, m_iCheckPoint+1); if(m_iDistanceToNextCheckPoint>(int) sqrt( ((m_fPosX - iX)*(m_fPosX - iX))+ ((m_fPosY - iY)*(m_fPosY - iY)))) { m_iDistanceToNextCheckPoint = (int) sqrt( ((m_fPosX - iX)*(m_fPosX - iX)) + ((m_fPosY - iY)*(m_fPosY - iY))); } if(iX != -1) iX += 40; }while(iX>0); m_fBackupDirectionX = m_fDirectionX; m_iBackupDistanceToNextCheckPoint = m_iDistanceToNextCheckPoint; m_fBackupDirectionY = m_fDirectionY; m_fBackupIncrementX = m_fIncrementX; m_fBackupIncrementY = m_fIncrementY; 第3 章2D 游戏开发177 m_iBackupAngle = m_iAngle; m_fBackupPosX = m_fPosX; m_fBackupPosY = m_fPosY; m_iBackupSprite = m_sprCar.m_iAbsolutePosition; iBackupTurnCar = iTurnCar; } else { iX = 0; iY = 0; m_iDistanceToNextCheckPoint = 999999999; do { iX = iX / 40; iY = iY / 40; theTrack->GetCheckPointPosition(&iX,&iY, m_iCheckPoint+1); if(m_iDistanceToNextCheckPoint>(int) sqrt (((m_fPosX - iX)*(m_fPosX - iX)) + ((m_fPosY - iY)*(m_fPosY - iY)) )) { m_iDistanceToNextCheckPoint = (int) sqrt (((m_fPosX - iX)*(m_fPosX - iX))+ ((m_fPosY - iY)*(m_fPosY - iY))); } if(iX != -1) iX += 40; }while(iX>0); } } } } } if(m_iControlType != CTRL_USER &&m_iControlType != CTRL_NETWORK_LOCAL) { m_pSound.SetPosition(m_fPosX/200.0f,m_fPosY/200.0f, 0.0f); m_pCrashSound.SetPosition(m_fPosX/200.0f,m_fPosY/200.0f, 0.0f); } DWORD dwFreq = 0; if(GetSpeed() != 0) { m_pSound.Play(0, DSBPLAY_LOOPING); dwFreq = 22050 + (GetSpeed() * 50); m_pSound.m_pSoundBuffer->SetFrequency(dwFreq); } else { m_pSound.Stop(); } if(m_iControlType == CTRL_USER || m_iControlType== CTRL_NETWORK_LOCAL) { cSoundInterface pSInterface; 178 Visual C++游戏开发技术与实例 pSInterface.SetListernerPosition( m_fPosX/200.0f, (m_fPosY/200.0f), 0.0f); } }__