对战坦克大战(vc++)

                              对战坦克大战

转载请注明出处

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

本节将介绍一个和FCFamilyComputer)上的经典游戏《坦克大战》类似的游戏——对战坦克

大战。这是一个4 人对战的坦克游戏,4个玩家两两一组,率先攻击到对方鹰巢的一组玩家获胜。

对战坦克大战是一个C/S 结构的网络游戏,它的网络部分是用重叠I/OSocket实现的。它分成

服务器端和客户端。服务器端用来接受客户端连接,并对游戏作出控制。先来看看服务器部分的实现。

4.10.1 对战坦克大战的服务器程序

服务器程序界面如图4.15 所示。

                          图4.15对战坦克大战的服务器程序

 

 

           服务器是一段Win32 程序。程序入口WinMain 和前面游戏中介绍过的入口函数并无二样。WndProc

WinMain 中定义的消息回调函数,代码如下:

LRESULT CALLBACK WndProc (HWND hwnd, UINTmessage, WPARAM wParam, LPARAM lParam)

{

int cxChar, cyChar ;

switch (message)

{

case WM_CREATE :

cxChar = LOWORD (GetDialogBaseUnits ()) ;

cyChar = HIWORD (GetDialogBaseUnits ()) ;

hwndList = CreateWindow (TEXT("listbox"), NULL,

WS_CHILDWINDOW|WS_VISIBLE | LBS_STANDARD ^LBS_SORT,

cxChar, cyChar,

cxChar * 44 + GetSystemMetrics (SM_CXVSCROLL),

cyChar * 16,

hwnd, (HMENU) ID_LIST,

(HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE),

NULL) ;

//初始化服务器

if ( !InitServer() )

第4 章网络游戏开发277

PostQuitMessage (0) ;

//创建socket 监听线程

CreateThread( NULL, 0, AcceptThread, NULL, 0,NULL );

//创建socket 工作线程

CreateThread( NULL, 0, WorkerThread, NULL, 0,NULL );

return 0 ;

case WM_SETFOCUS :

SetFocus (hwndList) ;

return 0 ;

case WM_DESTROY :

TerminateServer();

PostQuitMessage (0) ;

return 0 ;

}

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

}

WndProc 在创建消息中首先调用了InitServer,以初始化服务器。然后,它开启两个线程,一个是

socket 监听线程AcceptThread,另一个是socket工作线程WorkerThread

初始化服务器函数InitServer,定义如下:

bool InitServer()

{

WSADATA wsd;

sockaddr_in local;

// socket 初始化

if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)

return false;

// 创建监听socket

slisten = socket(AF_INET, SOCK_STREAM,IPPROTO_IP);

if (slisten == SOCKET_ERROR) {

WSACleanup();

return false;

}

// 绑定地址和端口

local.sin_addr.s_addr = htonl(INADDR_ANY);

local.sin_family = AF_INET;

local.sin_port = htons(sport);

if(bind(slisten,(struct sockaddr *)&local,

sizeof(local)) == SOCKET_ERROR) {

closesocket( slisten );

WSACleanup();

return false;

}

// 将socket 变成文件使用方式,并在上面监听socket

iocp =CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);

if ( !iocp ) {

278 Visual C++游戏开发技术与实例

closesocket( slisten );

WSACleanup();

return false;

}

// 初始化socket 池和玩家信息池

if ( !olexPool.InitPool(0) ||!playerPool.InitPool(16) ) {

closesocket( slisten );

WSACleanup();

return false;

}

ZeroMemory( &gTable, sizeof(GAMETABLE) );

// 开始监听

if ( listen(slisten,SOMAXCONN) != 0 ) {

closesocket( slisten );

WSACleanup();

return false;

}

Notice(1, "Server startsuccessfully!");

return true;

}

Socket 连接监听线程函数AcceptThread定义如下。它采用轮寻方式监听连接,并将创建的会话

Socket 与文件I/O进行关联。

DWORD WINAPI AcceptThread( LPVOID pParam ) {

sockaddr_in client;

int size;

SOCKET ret;

OVERLAPPEDEX *lpolex;

while(true) {

size = sizeof(sockaddr_in);

ret = accept(slisten,(sockaddr*)&client,&size);

if(ret != INVALID_SOCKET)

{

Notice(2, "Connect:",inet_ntoa(client.sin_addr));

lpolex = olexPool.GetUsable();

if ( lpolex )

{

//成功接受连接

//将会话Socket 和文件关联

CreateIoCompletionPort((HANDLE)ret, iocp, NULL,0);

lpolex->socket = ret;

RecvMsg( lpolex );

}

else {

closesocket( ret );

第4 章网络游戏开发279

}

}

else { // accept error

ret = WSAGetLastError();

WSAErrorTrigger(ret, TEXT("AcceptErr:"));

}

}

return 0;

}

另一个线程函数WorkerThread 用于和客户端进行通信,并对整个游戏进行控制。

DWORD WINAPI WorkerThread(LPVOID pParam) {

ULONG_PTR ckey;

OVERLAPPED *pol;

OVERLAPPEDEX *polex;

DWORD BytesTransferred;

int ret;

int *ibuf;

while(true) {

ret = GetQueuedCompletionStatus(iocp,&BytesTransferred,

&ckey,&pol,INFINITE);

// OVERLAPPEDEX 是自定义结构

polex = CONTAINING_RECORD(pol, OVERLAPPEDEX, ol);

// 远程主机断开连接

if ( ret == 0) {

int size = sizeof(sockaddr_in);

sockaddr_in client;

getpeername(polex->socket,(sockaddr*)&client,&size);

Notice(2, "Discont:",inet_ntoa(client.sin_addr));

// 删除所占的座位

for ( int i=0; i<gTable.current; i++ ) {

if ( gTable.players[i] == polex->ppla ) {

if ( i > 0 )

gTable.players[i-1]->next =polex->ppla->next;

break;

}

}

for ( ; i<gTable.current-1; i++ )

gTable.players[i] = gTable.players[i+1];

gTable.current--;

// 回收资源

playerPool.Recycle( polex->ppla );

olexPool.Recycle( polex );

continue;

}

280 Visual C++游戏开发技术与实例

// 成功收到消息

switch (polex->op) {

case OP_READ:

ibuf = (int *)(polex->wbuf.buf);

switch ( ibuf[0] ) {

// 分配玩家座位表

case NETMSGTK_ASKGROUPINFO:

Notice( "AskGroup: ", ibuf[2] );

polex->ppla = playerPool.GetUsable();

polex->ppla->seat = gTable.current;

gTable.players[gTable.current] = polex->ppla;

gTable.players[gTable.current]->socket =polex->socket;

SendMsg( NETMSGTK_ANSWERSEATINFO,polex->socket,

&gTable.current, sizeof(int) );

Notice( "AnswerSeat: ", gTable.current);

polex->ppla->next = NULL;

if ( gTable.current > 0 ) {

gTable.players[gTable.current-1]->next =polex->ppla;

SendMsgToOther( NETMSGTK_MOREPLAYER, gTable,gTable. current,

&gTable.current, sizeof(int) );

}

if ( ++gTable.current == MAXPLAYER )

SendMsgToTable( NETMSGTK_GAMEREADY, gTable, NULL,0 );

break;

case NETMSGTK_PLAYERREADY:

if ( ++gTable.counter == MAXPLAYER ) {

SendMsgToTable( NETMSGTK_GAMESTART, gTable, NULL,0 );

//初始化奖子

gTable.food.exsit = false;

gTable.food.existnum = DEFFOODEXFRAME;

gTable.food.notexistnum = DEFFOODNOTEXFRAME;

gTable.food.counter = DEFFOODNOTEXFRAME;

gTable.counter = 0;

}

break;

case NETMSGTK_CMDINFO:

if ( gTable.food.counter-- <= 0 ) {

if ( gTable.food.exsit ) { // 删除

gTable.food.counter = gTable.food.notexistnum;

SendMsgToTable( NETMSGTK_CMDFOODDELETE, gTable,NULL, 0 );

} else { // 创建

gTable.food.counter = gTable.food.existnum;

int foodparam[3];

foodparam[0] = rand() % FOOD_MAX;

foodparam[1] = rand() % 608;

foodparam[2] = rand() % 608;

SendMsgToTable(NETMSGTK_CMDFOODCREATE,gTable,foodparam,sizeof(int)*3 );

第4 章网络游戏开发281

}

gTable.food.exsit = !gTable.food.exsit;

}

SendMsgToOther( NETMSGTK_CMDINFO, gTable,polex->ppla->seat, ibuf+2,

ibuf[1] );

break;

case NETMSGTK_TEAMVICTORY:

SendMsgToTable( NETMSGTK_TEAMVICTORY, gTable,ibuf+2, ibuf[1] );

break;

}

RecvMsg( polex );

break;

case OP_WRITE:

olexPool.Recycle( polex );

break;

}

}

return 0;

}

4.10.2 对战坦克大战的客户端程序

对战坦克大战的客户端程序界面如图4.16 所示。

4.16 坦克大战客户端

注意:如果想测试这个游戏,需要同时运行4个客户端程序。

282 Visual C++游戏开发技术与实例

程序主框架首先调用InitNetwork 函数用于初始化网络通信。InitNetwork 函数定义如下:

bool InitNetwork( const char *serv_addr, unsignedint serv_port)

{

WSADATA wsd;

sockaddr_in local,server;

unsigned long ul = 1;

int ret;

// 初始化socket

if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)

return false;

// 创建客户端socket 并绑定

c_socket = socket(AF_INET, SOCK_STREAM,IPPROTO_IP);

if (c_socket == SOCKET_ERROR)

return false;

local.sin_addr.s_addr = htonl(INADDR_ANY);

local.sin_family = AF_INET;

c_port = NET_CLIENT_PORT_MIN;

while(c_port < NET_CLIENT_PORT_MAX)

{

local.sin_port = htons(c_port);

if(bind(c_socket,(struct sockaddr *)&local,

sizeof(local)) == SOCKET_ERROR) {

ret = WSAGetLastError();

if(ret == WSAEADDRINUSE)

c_port++;

else break;

}

else break;

}

if(c_port >= NET_CLIENT_PORT_MAX)

return false;

server.sin_addr.s_addr = inet_addr(serv_addr);

server.sin_family = AF_INET;

server.sin_port = htons(serv_port);

// 连接服务器

if( connect( c_socket, (const sockaddr*)&server,sizeof(server) ) == SOCKET_ERROR )

{

ret = WSAGetLastError();

if(ret == WSAENETDOWN || ret == WSAENETUNREACH)

ERRORMSG("Can’t reach server.\nPlease checkyour network connection.");

else if(ret == WSAECONNREFUSED)

ERRORMSG("The server does not work!");

else if(ret == WSAEPROCLIM)

ERRORMSG("Too many users.\nPlease trylater.");

第4 章网络游戏开发283

return false;

}

// 设置socket 为非阻塞

if( ioctlsocket( c_socket, FIONBIO, &ul ) ==SOCKET_ERROR )

return false;

// 初始化消息列表

NetList.CreatMsgList( 8, true ); // networkmessage list

// 创建消息接受线程

HANDLE hThread =CreateThread(NULL,0,MsgReceiver,NULL,0,NULL);

if(!hThread)

return false;

return true;

}

InitNetwork 函数中开启了一个新线程用于接受网络消息,线程函数是MsgReceiver。在MsgReceiver

中,程序采用轮寻方式检测网络数据,函数定义如下:

DWORD WINAPI MsgReceiver( LPVOID param )

{

fd_set fdread;

timeval tval;

int ret, msgsize;

char buf_char[BUFFERSIZE]; // 接受缓冲

char *mark;

CMsgElem elem;

// 向服务器查询组信息

ret = 0;

SendMsg( NETMSGTK_ASKGROUPINFO, &ret,sizeof(int) );

tval.tv_usec = 0;

tval.tv_sec = 1;

//轮寻方式检测是否有网络消息

while(true)

{

FD_ZERO(&fdread);

FD_SET(c_socket,&fdread);

ret = select(0,&fdread,NULL,NULL,&tval);

if ( ret == 0 || ret == SOCKET_ERROR ) {

ret = WSAGetLastError();

continue;

}

// 可能还未初始化

if ( !NetList.GetSize() )

continue;

//接受数据

284 Visual C++游戏开发技术与实例

ret = recv(c_socket,buf_char,BUFFERSIZE,0);

if(ret == SOCKET_ERROR) {

ret = WSAGetLastError();

NetList.Lock();

char *temp = "Connection shutdown!";

elem.CreateMsgElem(MSGNET_RECEIVEERROR, temp,strlen(temp)+1, MSG_NET );

NetList.Push(&elem);

NetList.UnLock();

break;

}

// 把消息弹入列表中

NetList.Lock();

mark = buf_char;

while ( ret > 0 &&

elem.CreateMsgElemFromBuf( mark, msgsize, MSG_NET) ) {

NetList.Push(&elem);

mark += msgsize;

ret -= msgsize;

}

NetList.UnLock();

}

return 0;

}

MsgReceiver 中还调用了SendMsg函数,这是向服务器发送消息的函数。

bool SendMsg(int msg, LPVOID param, int size)

{

int ret = size+sizeof(int)*2;

char *buffer = new char[ret];

if(!buffer)

return false;

*(int *)buffer = msg;

*(int *)(buffer+sizeof(int)) = size;

if(param && size>0)

memcpy( buffer+sizeof(int)*2, param, size );

ret = send(c_socket,buffer,ret,0);

delete[] buffer;

if( ret == SOCKET_ERROR)

return false;

else

return true;

}

InitNetwork 函数完成后,系统调用GameMain进入游戏控制循环。在GameMain函数中,程序

首先调用MsgProcessor 处理网络消息,接着根据当前的游戏状态作出不同动作。而当游戏处于运行状

态时,程序首先对子弹进行碰撞检测相关计算,接着对坦克运动做计算,然后再对奖子做碰撞检测计

算,最后是向电脑控制的坦克做AI 命令。当这些控制完成后,程序将上面的动作统一发送到服务器

第4 章网络游戏开发285

端。GameMain 的最后部分是绘制这些精灵,绘制的顺序是地图、子弹、坦克、鹰巢和草地(雪地)。

注意:这里实现坦克游戏能够完全模仿FCFamily Computer)上的坦克大战,所以坦克是可以

在草地中隐藏的,这也是为什么将草地最后绘制的原因。

void ConsoleNet::GameMain() {

static int counter = 0;

static DWORD start_time = 0, last_get;

static DWORD frame_start = 0;

DWORD end_time;

if ( m_dwStatus == CONSTAT_ENDGAME )

return ;

// 如果网络消息队列非空,则调用MsgProcessor 函数处理消息列表。

if ( !NetList.IsEmpty() )

MsgProcessor( &NetList );

// 判断当前游戏状态

if ( m_dwStatus < CONSTAT_WAITMORE ) {

return ;

} else if ( m_dwStatus == CONSTAT_WAITBEGIN ) {

last_get = timeGetTime();

return ;

} else if ( m_dwStatus == CONSTAT_WAITPLAYER ) {

if ( bFresh ) {

cmdbuf = uiCurrentCmd;

firebuf = bFired;

bFresh = false;

}

if ( bRecvCmd ) {

last_get = timeGetTime();

m_dwStatus = CONSTAT_RUNNING;

} else {

end_time = timeGetTime();

if( end_time - last_get > WAITTIMEOUT ) {

DebugOutput( 1, "Wait error!" );

m_dwStatus = CONSTAT_WAITERROR;

}

return ;

}

}

// FPS 控制, 理论上1000/x

// x = 20, 30 fps

while ( timeGetTime() - frame_start < 30 );

frame_start = timeGetTime();

// 如果程序处于运行状态

if ( m_dwStatus == CONSTAT_RUNNING ) {

BulletsProc(); // 1——先执行子弹运动和碰撞检测

286 Visual C++游戏开发技术与实例

#ifdef _DEBUG_CMDOUTPUT

char temp[4];

DebugOutput( 1, "*****Last Get:*****");

for ( int i=0; i<DEFTANKNUM; i++ ) {

itoa(m_pCmd[i].cmd,temp,10);

DebugOutput( 2, temp, "\t" );

}

DebugOutput( 1, "\r\n" );

#endif

TanksProc(); // 2——执行坦克运动

FoodProc(); // 3——奖子碰撞检测和信息更新

// 交换命令信息

m_pCmd[m_nLocal].cmd= cmdbuf;

m_pCmd[m_nLocal].fire= firebuf;

bFresh = true;

CreateAICmd(); // 向电脑控制的坦克发出AI 命令

#ifdef_DEBUG_CMDOUTPUT

DebugOutput( 1,"*****Generate:*****" );

for ( intk=m_nLocal; k<m_nLocal+DEFAINUM+1; k++ ) {

itoa(m_pCmd[k].cmd,temp,10);

DebugOutput( 2,temp, "\t" );

}

#endif

//向服务器发送消息

SendCmdMsg(&m_pCmd[m_nLocal],DEFAINUM+1,m_nLocal);//home-0,enemy-5

// 等待下一条消息

m_dwStatus =CONSTAT_WAITPLAYER;

m_nMsgCounter = 0;

ZeroMemory(m_pMsgFlag,sizeof(bool)*m_nPlayers);

bRecvCmd = false;

}

// 如果应用窗口没有被激活,则跳过刷新屏幕

if ( !g_bActive )

return ;

// 动画,帧卷屏

RollBlockObjects(m_pRiver, m_nNumRiver );

// 重绘地图层

Display.Clear(0); //level ground

Display.Blt(0,0,pMapSolid->GetDDrawSurface(),NULL,DDBLTFAST_SRCCOLORKEY);

// 重绘精灵层

BlitBullets(m_ppBullets, DEFTANKNUM );

BlitTanks(m_ppTanks, DEFTANKNUM );

BlitBases(m_ppBases, 2 );

第4 章网络游戏开发287

// 重绘草地层,树层

Display.Blt(0,0,pMapGrass->GetDDrawSurface(),NULL,DDBLTFAST_SRCCOLORKEY);

BlitFood();

// 显示当前FPS

counter++;

end_time =timeGetTime();

if ( end_time -start_time >= 1000 ) {

start_time =end_time;

itoa( counter,fps+5, 10 );

counter = 0;

}

pText->DrawText(NULL, " ", 5, 5, RGB(0,0,0), RGB(0,0,0) );

pText->DrawText(NULL, fps, 5, 5, RGB(0,0,0), RGB(255,0,0) );

Display.Blt(0,0,pText->GetDDrawSurface(),NULL, 0);

// 交换前后缓冲区

Display.Present();

}__

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值