为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

对战坦克大战(vc++)

2017-09-19 15页 doc 134KB 23阅读

用户头像

is_601191

暂无简介

举报
对战坦克大战(vc++)    对战坦克大战 本节将介绍一个和FC(Family Computer)上的经典游戏《坦克大战》类似的游戏——对战坦克 大战。这是一个4 人对战的坦克游戏,4 个玩家两两一组,率先攻击到对方鹰巢的一组玩家获胜。 对战坦克大战是一个C/S 结构的网络游戏,它的网络部分是用重叠I/O 的Socket 实现的。它分成 服务器端和客户端。服务器端用来接受客户端连接,并对游戏作出控制。先来看看服务器部分的实现。 4.10.1 对战坦克大战的服务器程序 服务器程序界面如图4.15 所示。           图4.15 对战坦克大战...
对战坦克大战(vc++)
    对战坦克大战 本节将介绍一个和FC(Family Computer)上的经典游戏《坦克大战》类似的游戏——对战坦克 大战。这是一个4 人对战的坦克游戏,4 个玩家两两一组,率先攻击到对方鹰巢的一组玩家获胜。 对战坦克大战是一个C/S 结构的网络游戏,它的网络部分是用重叠I/O 的Socket 实现的。它分成 服务器端和客户端。服务器端用来接受客户端连接,并对游戏作出控制。先来看看服务器部分的实现。 4.10.1 对战坦克大战的服务器程序 服务器程序界面如图4.15 所示。           图4.15 对战坦克大战的服务器程序     服务器是一段Win32 程序。程序入口WinMain 和前面游戏中介绍过的入口函数并无二样。WndProc 是WinMain 中定义的消息回调函数,代码如下: LRESULT CALLBACK WndProc (HWND hwnd, UINT message, 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 start successfully!"); 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; ippla ) { if ( i > 0 ) gTable.players[i-1]->next = polex->ppla->next; break; } } for ( ; ippla ); 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, unsigned int 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 check your network connection."); else if(ret == WSAECONNREFUSED) ERRORMSG("The server does not work!"); else if(ret == WSAEPROCLIM) ERRORMSG("Too many users.\nPlease try later."); 第4 章网络游戏开发283 return false; } // 设置socket 为非阻塞 if( ioctlsocket( c_socket, FIONBIO, &ul ) == SOCKET_ERROR ) return false; // 初始化消息列表 NetList.CreatMsgList( 8, true ); // network message 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 的最后部分是绘制这些精灵,绘制的顺序是地图、子弹、坦克、鹰巢和草地(雪地)。 注意:这里实现坦克游戏能够完全模仿FC(Family 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; iGetDDrawSurface(),NULL,DDBLTFAST_SRCCOLORKEY); // 重绘精灵层 BlitBullets( m_ppBullets, DEFTANKNUM ); BlitTanks( m_anks, 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(); }__
/
本文档为【对战坦克大战(vc++)】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索