对战坦克大战

转载请注明出处

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

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

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

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

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

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 的最后部分是绘制这些精灵,绘制的顺序是地图、子弹、坦克、鹰巢和草地(雪地)。

注意:这里实现坦克游戏能够完全模仿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; 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();

}__

对战坦克大战(vc++)相关推荐

  1. 【180622】VC++ 超经典坦克大战双人版

    超经典坦克大战双人版,VC++完整版,含声音.地图.资源文件,编译后请将Map.Sound.graphics拷贝至可执行文件同级目录内,否则出错.大家看看界面就知这游戏有多经典了吧?小时候没玩过的不多 ...

  2. JAVA大作业——网络在线对战游戏——坦克大战

    目录 大作业要求 实机演示 主机环回地址布置连接演示 多人联机对战演示 WASD控制坦克移动和按J键发射炮弹攻击 攻击到敌人后会爆炸并且消灭敌人 按下C键设置IP主机连接 大作业要求 简单的小游戏 要 ...

  3. 【20140809】VC++坦克大战带地图编辑器(游戏大赛一等奖)

    VC++坦克大战,曾是游戏大赛一等奖作品,带音乐.带地图编辑器:游戏可以单人玩和双人玩.想信你在小时候玩过这游戏,很过瘾!地图编辑器是用VB写的,有了它你可以自己制造地图导入坦克游戏中.本游戏源码在V ...

  4. c语言坦克大战源代码vc 6.0,坦克大战(VC6.0) - 源码下载|源代码 - 源码中国

    压缩包 : 坦克大战.rar 列表 坦克大战\BattleCity.aps 坦克大战\BattleCity.dsp 坦克大战\BattleCity.dsw 坦克大战\BattleCity.ncb 坦克 ...

  5. c语言课程设计坦克大战,funcodec++课程设计_坦克大战

    <funcodec++课程设计_坦克大战>由会员分享,可在线阅读,更多相关<funcodec++课程设计_坦克大战(24页珍藏版)>请在人人文库网上搜索. 1.课程设计一 坦克 ...

  6. 基于HTML5坦克大战游戏简化版

    之前我们有分享过不少经典的HTML5游戏,有些还是很有意思的,比如HTML5版切水果游戏和HTML5中国象棋游戏.今天要分享的是一款简化版的HTML5坦克大战游戏,方向键控制坦克的行进方向,空格键发射 ...

  7. C/C++游戏项目完整教程:《坦克大战》

    <坦克大战>以二战坦克为题材,既保留了射击类游戏的操作性,也改进了射击类游戏太过于复杂难玩的高门槛特点,集休闲与竞技于一身.经典再度袭来,流畅的画面,疯狂的战斗,让玩家再次进入疯狂坦克的世 ...

  8. Java实现经典版坦克大战(还原度很高)

    2022/4/8 13:30更(20年完结,游戏源码在文章底部,截至今天还可完美运行) 闲来无事写写帖子,想想上次更新已经是在一年前.源码里面附带了注解和思路.我把游戏效果展示给企业的老师看,给我的反 ...

  9. 坦克大战Java版(文末附下载地址)

    坦克大战Java版(文末附下载地址) 未用框架纯手打Java制作坦克大战游戏,实现基本的玩家对战NPC,积分记录保存,排行榜展示等功能 以下是当初我们组制作的简单坦克大战游戏项目展示(文章最后附上项目 ...

最新文章

  1. 【从零开始的ROS四轴机械臂控制】(三) - 为机械臂添加摄像头和夹爪、解决gazebo模型抖动、使用gazebo建立sdf模型
  2. SVN的搭建及使用(三)用TortoiseSVN修改文件,添加文件,删除文件,以及如何解决冲突,重新设置用户名和密码等...
  3. 入职一个多月了,谈谈感想
  4. Spring Cache-缓存概述及使用
  5. 使用rpm包升级ntpd服务_服务器准备升级,小程序将暂停使用
  6. mysql 数据库引擎介绍_MYSQL 数据库引擎介绍
  7. 铁粉看进来,surprise|湾区人工智能
  8. SWFUpload flash上传控件
  9. Javascript中的事件对象和事件源
  10. MySQL源码—线程篇
  11. 建行提示找不到服务器,中国建设银行E路护航网银安全组件常见问题解答
  12. E71(S60 3rd)通话录音软件 -终极录音- 的用法
  13. reshape() 函数与 kron()函数的区别
  14. Deepin20.4系统中wine优化设置
  15. 浅谈督查督办管理系统在企业管理中起到的作用
  16. MBTI各个字母的含义是什么
  17. Houdini 导出.ass文件
  18. 将Raspberry Pi用作台式PC的17个最佳Raspbian应用
  19. SnowField目标效果2-6:斜面移动2更真实的斜坡
  20. 【观察】掘金医疗大数据,如何四两拨千斤?

热门文章

  1. 小米 M365有致命漏洞!骇客能轻易远端控制
  2. yaml引用变量_合并当前数据
  3. 【天池智慧海洋建设】Topline源码——特征工程学习(大白)
  4. 何时对你说再见--深圳。
  5. PLC电路元器件基础
  6. 【cubeide】程控衰减器PE4302...
  7. 计算机数控机床的原理,数控机床5计算机数控装置原理.ppt
  8. ZigBee TI ZStack CC2530 3.10 IO口01-输入输出
  9. 【毕业设计】基于STM32的无线WIFI投影设计(解析BMP、TCP通讯)
  10. 软件开发平台实现目标归纳