网络游戏的移动同步(三)平滑算法 您所在的位置:网站首页 鼠标的移动同步 网络游戏的移动同步(三)平滑算法

网络游戏的移动同步(三)平滑算法

2024-07-04 12:15| 来源: 网络整理| 查看: 265

引文

本篇文章想解决的是引入航位预测后,预测位置与当前位置出现偏差的平滑处理算法,如第一篇所做的简单跳跃的话,会出现很不舒服的跳跃,这里用一些常用的插值方法解决那些生硬的跳跃。

一些问题

插值平滑就是处理p0插值到p1的问题,但是在游戏中,这难免会出现一些问题,比如我们在处理位置插值时,如何处理p0到p1与碰撞检测系统的冲突?如何选择更为合适的插值方法?

线性插值

我们先选择简单的线性插值,在代码中做如下更改,记录下目标位置,初始位置及时间。

1 2 3 4 5 6 7 8 var delta:Number = (getTimer() - rp.time) / 1000; netPlayer.targetPos = new Vector2().addVectors(rp.position, rp.velocity.clone().multiply(delta).add(rp.acceleration.clone().divide(2).multiply(delta * delta))); netPlayer.startPos = netPlayer.position.clone(); netPlayer.velocity = rp.velocity.clone(); netPlayer.acceleration = rp.acceleration.clone(); netPlayer.method = 2; netPlayer.smoothTick = netPlayer.smoothTime = delta;

这里我做了一下比较特别的处理,首先目标位置并不是发来的位置,而是发来的位置加上延迟时间内又移动的距离,所以目标位置稍微远一些,另外平滑时间我设置成于延迟时间相关,所以延迟越大平滑时间越长。

在玩家类中,需要通过平滑计时来判断当前在平滑阶段,还是普通的预测阶段,代码如下。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if (smoothTick > 0) { smoothTick -= Global.elapse / 1000; var dt:Number = 1 - smoothTick / smoothTime; position.x = startPos.x + (targetPos.x - startPos.x) * dt; position.y = startPos.y + (targetPos.y - startPos.y) * dt; } else { if (! acceleration.isZero()) { velocity.x += acceleration.x * Global.elapse / 1000; velocity.y += acceleration.y * Global.elapse / 1000; } if (! velocity.isZero()) { position.x += velocity.x * Global.elapse / 1000; position.y += velocity.y * Global.elapse / 1000; } }

从代码中可以看到,位置可以从两个阶段得到,当在插值阶段时,使用的是插值计算出的值,如果不在插值阶段,则为状态计算出的值。最终效果如下。

Network4.swf

可以看到有了平滑的算法后,网络场景的物体看起来不那么生硬了,不过平滑的时候,你会发现平滑的方向跟速度方向不一致,看起来很不自然。下面的平滑算法会做得更好一些。

立方样条插值

选择使用这种插值方式的原因是,这种插值使得插值路径更加真实,自然,可参加如下图

这里可以看到当前速度,期望最终速度都位移插值路径的最终切线上,与期望值一致。

实现这个插值需要4个坐标值。

坐标1:开始位置(即本地当前位置)

坐标2:坐标1经过一定时间后的位置(速度为当前速度)

坐标4:最终位置(即网络协议发送的最新位置加上一定的延迟时间后的位置)

坐标3:坐标4反向移动一定时间后的位置(速度为网络最新速度)

插值坐标公式为:

x = At3 + Bt2 + Ct + D

y = Et3 + Ft3 + Gt + H

其中

A = x3 – 3x2 +3x1 – x0

B = 3x2 – 6x1 + 3x0

C = 3x1 – 3x0 D = x0

E = y3 – 3y2 +3y1 – y0

F = 3y2 – 6y1 + 3y0

G = 3y1 – 3y0

H = y0

代码修改

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var delta:Number = (getTimer() - rp.time) / 1000; // 预测点,在延迟时间5倍以后 // 延迟越严重,预测越远 var scheduled:Number = delta * 5; scheduled = Math.min(scheduled, 0.8); var pos1:Vector2 = netPlayer.position.clone(); var pos2:Vector2 = new Vector2().addVectors(pos1, netPlayer.velocity.clone().multiply(0.1)); var pos4:Vector2 = new Vector2().addVectors(rp.position, rp.velocity.clone().multiply(scheduled).add(rp.acceleration.clone().divide(2).multiply(scheduled * scheduled))); var pos3:Vector2 = new Vector2().subVectors(pos4, rp.velocity.clone().add(rp.acceleration.clone().multiply(scheduled)).multiply(0.1)); netPlayer.smoothTick = netPlayer.smoothTime = scheduled; netPlayer.A = pos4.x - 3 * pos3.x + 3 * pos2.x - pos1.x; netPlayer.B = 3 * pos3.x - 6 * pos2.x + 3 * pos1.x; netPlayer.C = 3 * pos2.x - 3 * pos1.x; netPlayer.D = pos1.x; netPlayer.E = pos4.y - 3 * pos3.y + 3 * pos2.y - pos1.y; netPlayer.F = 3 * pos3.y - 6 * pos2.y + 3 * pos1.y; netPlayer.G = 3 * pos2.y - 3 * pos1.y; netPlayer.H = pos1.y; // 插值位置 position.x = A * dt * dt * dt + B * dt * dt + C * dt + D; position.y = E * dt * dt * dt + F * dt * dt + G * dt + H;

示例

Network4.swf

加权平均插值

这个在之前的插值介绍中提过,这种插值方式最简单,不需要记录dt,只需要记录期望位置即可。至于具体实现及方案,我将与下面的碰撞检测检测冲突一起给出。

碰撞检测冲突

目前插值的目标都是position,但是如果仅仅是对此值进行插值会存在一些问题,比如position.x = 1,期望位置为position.x = 3,而恰巧position.x = 2的位置是一个障碍点,那插值会导致与碰撞检测代码冲突,这种情况非常容易出现。

我处理这个问题的方案是,插值对象更改,更改为一个修正值modify。而在最终位置的选取上position要加上这个modify,这样插值可以跟碰撞检测规避开。

具体的实现如下

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 设置修正只 netPlayer.modify.x = netPlayer.x - rp.position.x; netPlayer.modify.y = netPlayer.y - rp.position.y; //如果位置偏差实在过大,直接跳跃 if (netPlayer.modify.lengthSQ > 50 * 50) { netPlayer.modify.set(0, 0); } // 注意这里直接设置到了期望位置 netPlayer.position.x = rp.position.x; netPlayer.position.y = rp.position.y; netPlayer.velocity = rp.velocity.clone(); netPlayer.acceleration = rp.acceleration.clone(); // 玩家更新代码 var smoothFactor:Number = 0.075; // 修正值平滑 modify.x *= (1 - smoothFactor); modify.y *= (1 - smoothFactor); // 显示位置 x = position.x + modify.x; y = position.y + modify.y;

这个很酷的方案最终效果如下

Network6.swf

这种方案的好处是更新过程不需要区分插值过程与预测计算过程,也不需要记录dt,代码显得比较间接,过渡相对比较平滑,不会与游戏其他系统相互冲突。

停止的不自然

之前的例子因为都有延迟波动的影响,所以停止过程经常出现不舒服的回拉,这个解决方案比较简单,如果停止时位置波动在某个阈值内,则不进行插值平滑即可。

最终效果全集

Network7.swf

参考 http://www.gamedev.net/page/resources/_/technical/multiplayer-and-network-programming/defeating-lag-with-cubic-splines-r914 http://gafferongames.com/2010/03/11/gdc-2010-networked-physics-slides-demo/


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有