首页
社区
课程
招聘
[注意]多人游戏同步的问题
发表于: 2015-9-23 17:45 3741

[注意]多人游戏同步的问题

2015-9-23 17:45
3741
发现自己总喜欢问,不喜欢回答,可能是自己也没能力回答吧。

好吧,今天又有新问题了。求大牛答疑。

就是游戏一般都有一个列队类,也就是把接下来要显示的事件一次放入到列队,

然后一次从列队里取出来,对于单人游戏,这个好理解,但是多人游戏呢?

就拿传奇来说吧。

假设有两个人,相互对立站着相互砍,

A向服务器发送砍命令 服务器接受并处理(发送个客服端,客服端加入列队)

B向服务器发送砍命令 服务器接受并处理(发送个客服端,客服端加入列队)

那么客户端列队将列队将是这样的(B A)

显示的时候先显示A砍一刀  然后在显示B砍一刀。

玩过传奇的玩家应该知道传奇砍这个动作有8张图片组成,那么显示的时候就是先显示A 砍的八张图片吗?

可是我玩传奇的时候感觉不是这样的,几乎A刚显示2张 B就开始显示了。

那么该怎么设计,

假设循环函数是
void print()
{
       list.getmessage();//这里取出列队并执行。这样的话就不是同时砍了。
        //还有那么不砍的人,身子也在抖动,该怎么设计呢?
}

说的有点多,可能表述的还是不够明白,大神们就尽量理解下吧。。

[课程]FART 脱壳王!加量不加价!FART作者讲授!

收藏
免费 0
支持
分享
最新回复 (10)
雪    币: 101
活跃值: (139)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
我个人认为,在砍出时,客户端已执行了显示特效,扣血计算时才会到服务器计算,然后发送给客户端
2015-9-23 20:27
0
雪    币: 5
活跃值: (108)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
主要是怎么显示多人的问题,在绘制函数中去绘制每个人的状态?
2015-9-23 21:38
0
雪    币: 15
活跃值: (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
你没考虑电脑执行速度
可以自己搞3个程序来测试一下  一个服务器接受 2个客户端消息
然后 2个客户端处理 并且画图 不需要画2个人的 只需要2个窗口 接受到消息开始画就行了  应该能看出点东西
2015-9-24 18:23
0
雪    币: 101
活跃值: (139)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
如果客户端更新了其它用户的状态,哪么就很可能从服务器拿到了状态数据。(还有一种情况可能不需要,哪就是被攻击者的状态,可能就在本机计算直接得出结果),扣血时才会在服务器上校验
2015-9-24 20:51
0
雪    币: 5
活跃值: (108)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
我知道绘制很快,

但是假设砍这个事件有7个图片,而渲染函数只有1个,也就是说在接到砍得消息的时候就一次性把7刀渲染完?那么传奇怎么做到A显示砍的第一张图片,B却显示到了第三张?
2015-10-4 00:30
0
雪    币: 10
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
不知道我理解的是不是对的。

像是GDI(+)或者DirectDraw里,都是有后备页(双缓冲)的,绘制的时候在后备页里绘制,刷新的时候GDI好像需要编程将后备页绘制到前台页面,DirectDraw是交换指针(将N个后备页中的要显示页和前台页指针交换),这个动作都是很快的。

而绘制后备页的动作都是由程序控制了。比如AB互砍这个动作,在编程的时候就应该设计好绘图框架(或者使用游戏引擎),对绘制的每一帧都进行控制,即:绘制当前帧时,查询一下有多少个绘制对象(地图、场景效果、人物etc),检查各个对象当前状态(如地图,如果人物没有走动,那么绘制过程就不需要全部绘制,仅仅更新一下人物所占的那一小块面积即可),然后根据整个检查结果安排绘制。

就AB互砍来说,
第一帧绘制A砍杀动作的第一帧,更新显示。
第二帧绘制A砍杀动作的第二帧,更新显示。
第三帧绘制A砍杀动作的第三帧,并且查询到B的状态改变了,绘制B砍杀动作的第一帧,更新显示。
...

所有绘制对象的状态改变等等并不与绘制同一线程。

服务器处理的仅仅是接收AB发出砍人动作的命令,计算伤害,返回给客户端。至于客户端怎么绘图,服务器不去处理的。客户端接收到服务器的返回,改变自己绘图对象的状态就可以了,然后绘图动作查询对象状态,根据对象状态绘制游戏画面。
2015-10-8 10:01
0
雪    币: 5
活跃值: (108)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
感觉你说的是对的。

谢谢哥们了
2015-10-9 00:54
0
雪    币: 96
活跃值: (36)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
游戏中有一个帧的概念,每帧之间有时间间隔。
void print()
{
       list.getmessage();//这个是同步的
       //异步发送到游戏的绘制线程
}

//每帧都会执行下面这个函数,一般游戏每秒都有60多帧
//1. 第一帧, A打B
// 2. 第二帧, A打B绘制到第二张图片
// 3. 第三帧, B打了A, 开始绘制B打A的第一张图标,并且绘制A打B的第三张图片
// 4. 第四帧
游戏的绘制线程
{
      
}
==
总结下:
1. 接受消息和界面绘制不是一个线程,是异步的
2. 界面绘制有一个帧的概念,每帧执行时候有时间间隔,所以第一帧时候需要绘制A打B,在第8帧时候要同时绘制A打B和B打A
2015-10-10 00:26
0
雪    币: 5
活跃值: (108)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
非常感谢。
2015-10-15 22:53
0
雪    币: 10
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
程序上,并没有侦这个概念的。
拿D3D的Win32游戏程序来说,绘图函数Vender()的位置一般在消息处理结束的地方,也就是下面这样:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{
        int wmId, wmEvent;
        PAINTSTRUCT ps;
        HDC hdc;

        switch (message)
        {
        case WM_COMMAND:
                wmId    = LOWORD(wParam);
                wmEvent = HIWORD(wParam);
                // 分析菜单选择:
                switch (wmId)
                {
                case IDM_ABOUT:
                        DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                        break;
                case IDM_EXIT:
                        DestroyWindow(hWnd);
                        break;
                default:
                        return DefWindowProc(hWnd, message, wParam, lParam);
                }
                break;
        case WM_PAINT:
                hdc = BeginPaint(hWnd, &ps);
                // TODO: 在此添加任意绘图代码...
                EndPaint(hWnd, &ps);
                break;
        case WM_DESTROY:
                PostQuitMessage(0);
                break;
        default:
                return DefWindowProc(hWnd, message, wParam, lParam);
        }
///////////////////////////////////////////////////////////////////////////////////////////////
               Vender();//没什么消息需要处理的话,那我就绘图吧
///////////////////////////////////////////////////////////////////////////////////////////////
        return 0;
}

上面是Windows程序的消息处理过程,在处理过程的末尾,有一个绘图函数Vender(),这个绘图函数是要自己实现的。

也就是说,没有消息需要处理的话,我就绘图。这也就是为什么性能高的机器,FPS就高的原因。因为Vender()完成的速度在不同性能的机器上完成的时间是不一样的。配置低的机器一秒钟仅能完成几次(FPS只有几)。

程序里,可能会在Vender()保存一个自身被调用次数计数的变量,但是Vender()被调用多少次实际上程序多半是不怎么关心的,可能也是不可控的(如果可控,那么配置低的机器也能实现很高的FPS。当然,程序里人为的降低FPS还是没问题的,提高的话就很难)。

所以,你说的绘图是根据第几侦这个说法,其实是不正确的。

简单的说,
比如,第一侦绘制,

性能高的电脑:
仅需要0.001s就完成了Vender(),,然后没有新的消息到来,那么就绘制第二侦,这时所有的参数都还没有变化,所以Vender()绘制的第二侦跟第一侦完全一样。
性能低的电脑:
需要3秒钟才完成Vender(),然后前面已经有很多消息到来,并且已经处理了。非常多的参数都变化了,然后又调用Vender(),绘制第二侦,这个第二侦跟第一侦区别非常多了(游戏里面的卡屏,哈哈,第一侦刚扬起刀,画面停滞3秒钟之后,你被砍死了。实际上如果是你被砍死了,而不是一直在逻辑上砍杀的话,说明这个程序写的有问题,因为绘图阻塞了逻辑处理。更好的处理方法是分别用不同的后台线程去处理,或者逻辑运算交给服务器,绘图、表现交给客户机去实现,这样画面更流畅,对防作弊也有一定好处,当然,对网络环境要求也高了很多。像是剑网三,不只这些,剑网三的逻辑和绘图除了把逻辑运算放到服务器,本地绘图还有一个“呼吸”的概念,这样可以运行lua脚本,思想非常先进)。

可以看到,同样的程序,同样的第二侦,运行的内容却是不一样的。

上面的例子就说明了你的关于游戏绘图的理解是不正确的。

至于FPS统计,如果是游戏本身的统计,那么在绘图里搞个计数器就好了。如果是外挂式的,统计的地点是不在这里的。多半会统计某个显卡驱动或者系统的API什么的吧
2016-2-17 19:32
0
游客
登录 | 注册 方可回帖
返回
//