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是在命令行状态下利用广播来建立主机,等待玩家加入的。大家有什么建议批评请及时与我分享。
本文有不足之处,还望大家多多指正。
D3D游戏编程系列(四):自己动手编写即时战略游戏之网络同步的更多相关文章
- D3D游戏编程系列(三):自己动手编写即时战略游戏之寻路
说起即时战略游戏,不得不提的一个问题是如何把一个物体从一个位置移动到另一个位置,当然,我说的不是瞬移,而是一个移动的过程,那么在这个移动的过程中我们如何来规划路线呢,这就不得不提到寻路了. 我所了解到 ...
- WCF编程系列(四)配置文件
WCF编程系列(四)配置文件 .NET应用程序的配置文件 前述示例中Host项目中的App.config以及Client项目中的App.config称为应用程序配置文件,通过该文件配置可控制程序的 ...
- C#中的函数式编程:递归与纯函数(二) 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面
C#中的函数式编程:递归与纯函数(二) 在序言中,我们提到函数式编程的两大特征:无副作用.函数是第一公民.现在,我们先来深入第一个特征:无副作用. 无副作用是通过引用透明(Referential ...
- OWIN系列之自己动手编写中间件
一.前言 1.基于OWIN的项目摆脱System.Web束缚脱颖而出,轻量级+跨平台,使得ASP.NET应用程序只需依赖这个抽象接口,不用关心所运行的Web服务器. 2.OWIN.dll介绍 使用反编 ...
- 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面
学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...
- 异步编程系列第04章 编写Async方法
p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...
- 学习ASP.NET Core Blazor编程系列四——迁移
学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...
- 【Visual C++】游戏编程学习笔记之九:回合制游戏demo(剑侠客VS巡游天神)
本系列文章由@二货梦想家张程 所写,转载请注明出处. 作者:ZeeCoder 微博链接:http://weibo.com/zc463717263 我的邮箱:michealfloyd@126.com ...
- D3D游戏编程系列(六):自己动手编写第一人称射击游戏之第一人称视角的构建
说起第一人称射击游戏,不得不提第一人称视角啊,没有这个,那么这个第一就无从谈起啊,我作为一个观察者究竟如何在这个地图上顺利的移动和观察呢,那么,我们一起来研究下. 我们首先来看下CDXCamera类: ...
随机推荐
- win10无法访问局域网共享文件?(因微软账户和本地账户登陆问题导致)
1 笔记本系统win10 X64企业版,其中一文件夹已设置为“共享”.本地帐号登录系统. 2 平板电脑系统win8.1 X64专业版,可以顺畅的访问笔记本的共享文件.微软帐号登录系统. 3 平板电脑系 ...
- oozie中调度mapreduce
mapreduce可以直接对hdfs进行清洗和计算,这里介绍oozie中如何调度使用. 操作步骤如下: 1. 写一个mapper和reduce类,并且打包成jar包 2. 在workflow中引用ma ...
- poj3208 Apocalypse Someday 数位dp+二分 求第K(K <= 5*107)个有连续3个6的数。
/** 题目:poj3208 Apocalypse Someday 链接:http://poj.org/problem?id=3208 题意:求第K(K <= 5*107)个有连续3个6的数. ...
- iOS边练边学--UIScrollView的属性简单使用,代理的简单介绍以及内容缩放
一.什么是UIScrollView *移动设备的屏幕大小是极其有限的,因此直接展示在用户眼前的内容也是相当有限 *当展示的内容较多,超出一个屏幕时,用户可通过滚动收拾来查看屏幕以外的内容 *普通的UI ...
- DataGridView使用技巧三:不显示最下面的新行、判断新增行
一.DataGridView不显示下面的新行 通常DataGridView的最下面一行是用户新追加的行(行头显示*).如果不想让用户新追加行即不想显示该新行,可以将DataGridView对象的All ...
- ruby send respond_to
http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_2.html http://galeki.is-progr ...
- QTableView修改数据后弹出是否保存的提示框。
自定义CustomDelegate继承自QStyledItemDelegate,重写setModelData(self, editor, model, index)方法 def setModelDat ...
- myeclipse 配置
1.配置java 新建系统变量JAVA_HOME 和CLASSPATH 变量名:JAVA_HOME 变量值:C:\Program Files\Java\jdk1.7.0变量名:CLASSPATH 变量 ...
- [转]线上GC故障解决过程记录
排查了三四个小时,终于解决了这个GC问题,记录解决过程于此,希望对大家有所帮助.本文假定读者已具备基本的GC常识和JVM调优知识,关于JVM调优工具使用可以查看我在同一分类下的另一篇文章: http: ...
- 怎样用MathType创建竖式算法
在使用MathType编辑公式时,有时将最简单的表达式变成Word文档也会出现一些问题.比如MathType竖式.下面介绍MathType竖式的一些编辑方法. 步骤如下: 步骤一:在MathType底 ...