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是在命令行状态下利用广播来建立主机,等待玩家加入的。大家有什么建议批评请及时与我分享。

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




  • 相关阅读:
    读书笔记之理想设计的特征
    一些javascript 变量声明的 疑惑
    LINQ 使用方法
    Google MySQL tool releases
    读书笔记之设计的层次
    EF之数据库连接问题The specified named connection is either not found in the configuration, not intended to be used with the Ent
    转载 什么是闭包
    javascript面向对象起步
    Tips
    数据结构在游戏中的应用
  • 原文地址:https://www.cnblogs.com/fuhaots2009/p/3360885.html
Copyright © 2011-2022 走看看