赛车游戏的2D 编程(VC++)

                          赛车游戏的2D 编程

转载请注明出处

本文章的下载地址,请单击此链接

 

         赛车是一个简单的游戏,可是麻雀虽小,五脏具全。它用DDraw实现了精灵的绘制,用DSound

实现游戏音效,用DInput 实现了键盘和鼠标接口,用DPlay实现了联网。

游戏运行的初始界面如图3.18 所示,游戏的竞赛场面如图3.19 所示。

这个游戏使用基础库cMain.libcMain.lib库是打包的DirectX库。在编译这个工程前,要确保自

己的计算机上安装了DirectX SDK 8.0或者9.0(注意是DirectX SDK,不是运行库)。如果已经安装了

SDK,编译仍有问题,那么请检查一下DXincludelibrary路径是否已经包括在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 个很重要的虚函数,它们是AppInitializedExitApp

DoIdle 函数。

当应用程序开始启动的时候需要调用AppInitialized函数。当退出游戏的时候,需要调用ExitApp

它负责销毁AppInitialized 创建时分配的内存和其他资源。当没有任何窗口消息需要处理的时候,

cApplication 类将调用DoIdle虚函数,从而允许用户处理自己的游戏。

_ cWindow

如果注意cApplication 类的声明,会看到它定义了一个cWindow 类成员变量。cWindow类负责在

游戏中创建主窗口。这个类只在库内部使用,所以没有必要去修改它的属性。

_ cInputDevicecKeyboardcMouse

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类的成员变量和相关精灵的信息。通过这个

类,可以自动的使精灵走动,而不用担心源页面的位置和大小。

_ cSoundInterfacecSoundcWavFile

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 vectorPLAYERINFO_VECTOR;

typedef listPLAYER_MESSAGES;

typedef list::iteratorPLAYERMESSAGE_ELEMENT;

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类继承而来,当

然可以应用AppInitializeExitAppDoIDle函数。

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类中其他

一些成员变量将会初始化:cCompetitioncRaceTrace类的对象。

_ 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存储其他信息:检

查站CheckPointLOBYTE)和角度信息(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类调用AccelerateBreakCarTurnCarRight或者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 的初始化函数InitApplicationDirectX初始化函数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()

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;iGetNumberOfCars();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);

}

}__

你可能感兴趣的:(Visual,C++游戏开发实例,Visual,C++,游戏开发,实例,C++编程相关,游戏引擎)