zoukankan      html  css  js  c++  java
  • D3D游戏编程系列(四):自己动手编写即时战略游戏之网络同步

           说到网络同步,这真是一个网络游戏的重中之重,一个好的网络同步机制,可以让玩家的用户体验感飙升,至少,我玩过的魔兽争霸在网络同步方面做得非常好,即便是网络状况很不稳定,依然可以保证用户数据不会出现意想不到的问题。

           在真正介绍同步这个大块头之前,我还要介绍一点,就是我们用什么样的网络协议,在我们面前,可用也是很广泛的协议无非是tcp和udp,这两个协议有什么区别我就不在此累述了,那么我就直接告诉大家,在游戏中数据包的传递工作尽可能的使用udp协议,为什么呢?因为快速简单,在一个需要高操作的游戏中,网络数据的传输速度是占主导地位的,就算udp协议没有tcp那么可靠,但是我们可以建立一个类似tcp的收发包回应排序机制来确保数据的正确传输,而且如果只是在局域网内的话,丢包率和乱序的情况是非常少见的,所以我们在像即时战略这样需要大量数据传递的游戏中首选udp(包括后面的第一人称射击游戏也是一样)。感兴趣的朋友可以试试使用tcp,那样的话,你的游戏极有可能出现人物瞬移,画面错乱的情况。

    什么是网络同步,就是让两个联机的游戏程序里面的数据,所表现出来的画面尽可能的一致(完全一致几乎不可能),因为网络传输的关系,还每个电脑的情况都不尽相同,我们需要做很多协调工作来确保这样的一致,网络同步的办法有很多种,我这里介绍一种,也是我游戏里面用的这一种:帧同步。

    什么是帧同步,这个网上的资料非常少,其实帧同步也可以叫做帧锁定,就是两个机子根据当前的帧数来决定是否更新逻辑,当然,两个机子的帧数更新以服务器端为准,举个例子:

    客户端:

    if(fpscount%5==0)

    {

    执行收集到的消息()

    }

    服务端:

    if(fpscount%5==0)

    {

    执行收集到的消息()

    }else if((fpscount+2)%5==0)

    {

    向客户端发送所收集到的消息()     //网络传输需要时间,客户端收到命令的时候差不多正是下一个需要执行消息的帧数

    }

            帧同步大概一个流程就像上面这样,客户端收到命令,并不会立即执行,而是发送给服务端,待到需要执行命令的帧数时,便把从服务端收集到的命令依次执行,而服务端则有一个命令主缓冲列表和一个命令备用缓冲列表,当有命令产生时,如果当前还没有向客户端发送消息,则加入主缓冲列表,否则加入备用缓冲列表,当达到需要执行命令的帧数时,便把备用缓冲列表里的命令添加到主缓冲列表,然后自身清空,就是这样一个一次循环的过程,当用户发出一个命令时,执行会有延迟,但是延迟很小,就是利用这个延迟来保证两个机子数据的同步的。当然,命令的接受我另开了一个线程,并加了锁,这样才能确保数据执行不会出现问题。下面给出实现代码:

    	if(m_bHost)
    	{
    		if((m_FpsCount+2)%5==0)
    		{
    			//cout<<"enter Send:"<<m_FpsCount<<endl;
    			
    			list<sMsg>::iterator it;
    			m_Lock.Lock();
    			if((m_FpsCount+2)%20==0)
    			{
    				for(int i=0;i<50;i++)
    				{
    					if(m_tank[i])
    					{
    						if(m_tank[i]->bMove==false)
    						{
    							_CreateTankPosMsg(m_tank[i]);
    						}
    					}
    				}
    			}
    			m_bBackMsgRecv=true;
    			sMsg msg;
    			msg.MsgID=sMsg::RECVMSGCOUNT;
    			msg.Time=m_MainMsgList.size();
    			m_Socket.sendto((char*)&msg,sizeof(sMsg),m_szSendIP,m_iSendPort);
    			//if(msg.Time>0)
    			//cout<<"Server Start Send Len: "<<msg.Time<<endl;
    			byte i=0;
    			//if(msg.Time>0)
    			//cout<<"Server Start Send:****************"<<endl;
    			for(it=m_MainMsgList.begin();it!=m_MainMsgList.end();it++)
    			{
    				it->SortedID=i++;
    			//	_PrintMsg(&(*it));
    				m_Socket.sendto((char*)&(*it),sizeof(sMsg),m_szSendIP,m_iSendPort);
    			}
    			//if(msg.Time>0)
    			//cout<<"Server End Send:****************"<<endl;
    			m_Lock.Unlock();
    			//cout<<"leave Send:"<<m_FpsCount<<endl;
    		}else if(m_FpsCount%5==0)
    		{
    			//cout<<"enter run:"<<m_FpsCount<<endl;
    			m_Lock.Lock();
    			list<sMsg>::iterator it;
    //			if(m_MainMsgList.size()>0)
    //			cout<<"Server Start Run:@@@@@@@@@@@@@@@"<<endl;
    			for(it=m_MainMsgList.begin();it!=m_MainMsgList.end();it++)
    			{
    				HandleMessage(&(*it));
    //				_PrintMsg(&(*it));
    			}
    //			if(m_MainMsgList.size()>0)
    //			cout<<"Server End Run:@@@@@@@@@@@@@@@"<<endl;
    			m_MainMsgList.clear();
    			//m_Lock.Unlock();
    			m_bBackMsgRecv=false;
    			//m_Lock.Lock();
    			m_MainMsgList=m_BackMsgList;
    			m_BackMsgList.clear();
    			m_Lock.Unlock();
    			//cout<<"leave run:"<<m_FpsCount<<endl;
    		}
    	}else
    	{
    		if(m_FpsCount%5==0)
    		{
    			if(!m_bSync)
    			{
    				return;
    			}
    			m_Lock.Lock();
    			int l=m_MainMsgList.size();
    			//if(l>0)
    		//	cout<<"Client Start Run:@@@@@@@@@@@@@@@"<<endl;
    			list<sMsg>::iterator it;
    			for(it=m_MainMsgList.begin();it!=m_MainMsgList.end();)
    			{
    			//	if(m_FpsCount>=it->Time)
    			//	{
    					HandleMessage(&(*it));
    			//		_PrintMsg(&(*it));
    			//		it=m_MainMsgList.erase(it);
    			//	}else
    			//	{
    					it++;
    			//	}
    				
    			}
    			//if(l>0)
    			//cout<<"Client end Run:@@@@@@@@@@@@@@@"<<endl;
    			m_MainMsgList.clear();
    			m_bSync=false;
    			m_Lock.Unlock();
    			
    		}else if((m_FpsCount+2)%20==0)
    		{
    			for(int i=50;i<100;i++)
    			{
    				if(m_tank[i])
    				{
    					if(m_tank[i]->bMove==false)
    					{
    						_CreateTankPosMsg(m_tank[i]);
    					}
    				}
    			}
    		}
    	}


           在两个机子运行速度相差比较大的情况下,依然会出现画面的明显不同步,所以,我每过20帧便会把玩家自身的数据更新到对方(客户端玩家的数据更新到服务端,服务端玩家的数据更新到客户端)。命令接受的线程代码如下:

    DWORD WINAPI MyWin::RecvMsgThread( LPVOID lp )
    {
    	MyWin* Win=(MyWin*)lp;
    	while(!Win->m_bStopRecvMsgThread)
    	{
    		sMsg msg;
    		if(Win->m_bHost)
    		{
    			Win->m_Socket.recvfrom((char*)&msg,sizeof(msg));
    			Win->m_Lock.Lock();
    			if(Win->m_bBackMsgRecv)
    			{			
    				Win->m_BackMsgList.push_back(msg);
    			}else
    			{
    				Win->m_MainMsgList.push_back(msg);
    			}
    			Win->m_Lock.Unlock();
    			
    		}else
    		{
    			sMsg msg;
    			Win->m_Socket.recvfrom((char*)&msg,sizeof(sMsg));
    			if(msg.MsgID!=sMsg::RECVMSGCOUNT)
    			{
    				continue;
    			}
    			int iMsgSize=msg.Time;
    //			if(msg.Time>0)
    //			cout<<"Client Start Recv Len: "<<msg.Time<<endl;
    			Win->m_Lock.Lock();
    //			if(msg.Time>0)
    //			cout<<"Client Start Recv: ********************"<<endl;
    			for(int i=0;i<iMsgSize;i++)
    			{
    				Win->m_Socket.recvfrom((char*)&msg,sizeof(sMsg));
    				Win->m_MainMsgList.push_back(msg);
    //				Win->_PrintMsg(&msg);
    			}
    //			if(msg.Time>0)
    //			cout<<"Client End Recv: ********************"<<endl;
    			Win->m_MainMsgList.sort();
    			Win->m_bSync=true;
    			Win->m_Lock.Unlock();
    			
    		}
    		
    
    	}
    	return 0;
    }


             当然,我写的这个同步机制并不算尽善尽美,还是有很多需要改善的地方,比如命令消息的确认,丢包处理等,这些我会在以后的时间里尽力完善。

            好了,一个即时战略游戏核心的几点差不多就介绍到这里了,当然,我有很多地方没有一一介绍到,有兴趣的朋友可以看下源码,这个demo是在命令行状态下利用广播来建立主机,等待玩家加入的。大家有什么建议批评请及时与我分享。

            本文有不足之处,还望大家多多指正。




  • 相关阅读:
    Linxu下段错误(segmentation fault)的调试
    n900破解无线路由密钥(wep)
    windows 7 下安装运行VC6
    【转载】W32Dasm反汇编工具使用详解
    用PreCode Snippet代码高亮
    转——别告诉我能懂PPT
    程序员必须掌握的基本正则表达式
    简单之美—软件开发实践者的思考—故事场景1
    xml解析
    Android Frameworktouch事件派发过程深入理解
  • 原文地址:https://www.cnblogs.com/fuhaots2009/p/3360885.html
Copyright © 2011-2022 走看看