网游预测插值

2008/09/20 21:57 ArthurXF
做过一个3D网游项目的插值预测是我负责的.和大家分享一下经验.
具体的项目我不方便透露,但是,其要求实时性是比较高的.
插值和预测是两样的东西.
插值是对已有的几个点做BEIZER曲线插值或者球形插值.以达到平滑的目的.
而预测是在已有的几个点的位置情况下,预测出下个点的位置.
具体的过程是先预测,再插值.由于预测本身不是最准,所以,当服务器下次发来准确的位置时得修正位置.
先说下预测。我们可以利用到的资源是,前几次服务器发来的位置信息。每个点位置的速度和加速度。
假设,上一次收到的信息是 Pos0,Vel0,Acc0 分别表示位置,速度和加速度,那么下一次的的位置可能是Pos0+(vel0+acc0)*deltatime其中deltatime表示UPDATE间隔。但是你直接使用的话,结果是很惨的,人物会在预测位置和修正位置间来回的“跳跃”,因为这样的预测并不是想象中的那么准确。因为下一次服务器来的信息这段时间间隔内,速度和加速度都在改变,这是由玩家控制的,完全离散的信息。
但是我们可以根据速度和加速度的历史来得知速度是加快了还是变慢了。(加速度一般是个固定数)
速度的方向是大致上是哪个方位。这样,我们可以对速度进行第2层的预测。预测方法和以上类似。
如果判断出,速度是正在下降的。那么可以判断下一次的速度是vel-acc逐步下降。如果是上升的那么可
以判断是vel+acc速度逐渐上升。根据这个情况可以初步得到一个预测速度 Vel1
现在总可以加到Pos0上去了吧?且慢!等你以插值的方式移动到那个点的时候,服务器会无情的发给你真正的当前位置,如果你直接过去,也会发生“跳跃”。考虑到这点,Vel1必须缩小。
我的做法是缩小0.8,还有0.2让服务器的位置来的时候看上去不至于太“跳跃”直接修正。然后,我们把这个Scale过的Vel1向量放到World里进行COLLISION,来防止角色“穿墙”,最后得到Vel2.
下一个预测位置就是Pos1=Pos0+Vel2.你将Pos1放到插值队列里,进行中间的插值然后每次Update时候移动过去,就行了~~
网路的硬件也有限,而人的创造也无限,在公网平均130ms的Latency下,是不存在“完全的”的同步情况。如何通过消除/隐藏延时,将用户带入快速的交互式实时游戏中,体验完美的互动娱乐呢?

以下六点,将助你分清楚哪些我们可以努力,哪些我们不值得努力,弄明白实时游戏中同步问题关键之所在,巧妙的化解与规避游戏,最终在适合普遍用户网络环境中(200ms),实现实时快速互动游戏:

1. 基本情况:
  (A) 网络性能指标一:带宽,限制了实时游戏的人数容量
  (B) 网络性能指标二:延时,决定了实时游戏的最低反应时间

2. 两个基本原则:
  (A) 让所有的用户屏幕上面表现出完全不同的表象是完全没有问题的。
  (B) 把这些完全不同表象完全柔和在一个统一的逻辑中也是完全没有问题的。

3. 同步的十二条应对策略:
  (A) 最大可能减少游戏中的数据传输
  (B) 将阻塞通信放到线程池中实现
  (C) 永远不要为了等待某个数据而不让游戏进行下去
  (D) 利用预测和插值改进游戏的效果
  (E) 当使用预测插值的时候传送的数据不仅包括坐标,还需要速度和加速度
  (F) 将输入数据枷锁或者队列化(例如键盘消息队列),直到下次发送数据的时刻,传统的方法是在固定的时间(发送数据前)检测键盘,在游戏的原理上隐藏延时
  (G) 使用事件调度表,将需要在所有用户客户端同时发生的事件,提前广播到所有用户
  (H) 使用多次攻击来杀死一个精灵,尽量减少一次性的、确定性的、延时敏感的事件
  (I) 延长子弹或者火箭在空中飞行的时间(在其飞行的同时,在所有客户端进行预测插值)
  (J) 所有物体从一个地方移动到另外一个地方都需要时间,避免诸如“瞬间移动”的设计
  (K) 尽量使游戏中所有精灵,飞船或者其他物体,都按照可预测的轨迹运行,比如在移动中增加惯性
  (L) 充分发挥创造力,尽最大可能的合并游戏中前后相关的事件,合并游戏中存在的延时此问题,需要在技术上改进的同时也需要策划有所重视,规避一些影响较大的设计,巧妙的隐藏"延时"

4. 同步问题现状:
  (A) 重视程度不够:很多人尚未意识到此问题的存在,曾有公司花半年时间打算做一款“松鼠大战”的网络版。
  (B) 技术上无彻底解决方案:对于多数程序员,单机游戏技术善未成熟就匆匆步入网络时代。
  (C) 研究这个技术需要条件:需要有实力的公司才能提供,无此条件,即便有能力的程序员也无法成功。

5. 目前网游的三大技术难题:
  (A) 服务器的响应问题:如何使服务器在支持越来越多的人数的情况下提供最高的响应。
  (B) 同步问题:如何在有限的网络响应情况下,实现快速实时类游戏,提供最完美的交互。
  (C) 服务器分布式问题:如何在统一用户数据的情况下,利用分部式将各个分散的“世界”统一到一个“世界”中。
  谁能真正解决好以上三个问题,配合策划在设计上的突破,将使其他人在至少两年内无法超越。
 
6. 相关补充:
  (A) 网格技术现在还是炒作,真正用到游戏中,还有很多技术难点需要突破(比如:目前网格的单位计算时间是以秒计算).
  (B) 其实与很多人想法相反的是现在3D技术早已不是主要的矛盾。而现在国内外对于以上三个问题可以说处于同一个起跑线上,完全有机会取得先机。
  (C) 现在解决同步问题已经很紧迫,而同时所需要的环境也已经成熟,只要有所关注,半年之内可以得出较成熟的结论
getElementById getElementsByName getElementsByTagName 大概介绍

  getElementById ,getElementsByName ,getElementsByTagName

  后两个是得到集合,byid只是得到单个对象

  getElementById 的用法

  举个例子:

  <a id="link1" name="link1" href=http://www.kungfuman.net>功夫之王</a>

  同一页面内的引用方法:

  1、使用id:

  link1.href,返回值为http://www.kungfuman.net

  2、使用name:

  document.all.link1.href,返回值为http://www.kungfuman.net

  3、使用sourseIndex:

  document.all(4).href //注意,前面还有Html、HEAD、TITLE和BODY,所以是4

  4、使用链接集合:

  document.anchors(0).href

  //全部的集合有all、anchors、applets、areas、attributes、behaviorUrns、bookmarks、boundElements、cells、childNodes、children、controlRange、elements、embeds、filters、forms、frames、images、imports、links、mimeTypes、options、plugins、rows、rules、scripts、styleSheets、tBodies、TextRectangle,请参考MSDN介绍。

  其实方法3和方法4是一样使用的集合,只是一个是all,可以包括页面所有标记,而anchors只包括链接。

  5、getElementById:

  document.getElementById("link1").href

  6、getElementsByName:

  document.getElementsByName("link1")[0].href //这也是一个集合,是所有name等于该方法所带参数的标记的集合

  7、getElementsByTagName:

  document.getElementsByTagName("A")[0].href //这也是一个集合,是所有标记名称等于该方法所带参数的标记的集合

  8、tags集合:

  document.all.tags("A")[0].href

  //与方法7一样是按标记名称取得一个集合

  除此之外:

  event.scrElement可以获得触发时间的标记的引用;

  document.elementFromPoint(x,y)可以获得x和y坐标处的元素的引用;

  document.body.componentFromPoint(event.clientX,event.clientY)可以获得鼠标所在处元素的引用;

  还可以通过元素的父子节点和兄弟节点关系来引用,如nextSibling(当前节点的后一节点)、previousSibling(当前节点的前一节点)、childNodes、children、firstChild、lastChild、parentElement等都是父子节点和兄弟节点的一些引用;还不仅限于此。

  上面是同一页面内的常见引用方法,另外还涉及到不同页面中的

  getElementsByName返回的是所有name为指定值的所有元素的集合

  “根据 NAME 标签属性的值获取对象的集合。”

  集合比数组要松散的多, 集合里每个子项的类型可以不同, 集合只是把某些元素放在一起作为一类来使用, 相比之下数组就严格多了, 每个子项都是统一的类型. document.getElementsByName, document.getElementsByTagName, document.formName.elements 这类方法所得到的结果都是集合.

  例:

<html>

  • <head>

    <title>fish</title>

    function get(){

    var xx=document.getElementById("bbs")

    alert("标记名称:"+xx.tagName);

    }

    function getElementName(){

    var ele = document.getElementsByName("happy");

    alert("无素为happy的个数:" + ele.length);

    }

    <body>

    <h2 id="bbs">获取文件指定的元素</h2>

    <hr>

    <form>

    <input type="button" onclick="get()" value="获取标题标记">

    <input type="button" name="happy" onclick="getElementName()" value="click "><input type="button" name="happy" onclick="getElementName()" value="click "><input type="button" name="happy" onclick="getElementName()" value="click "><input type="button" name="happy" onclick="getElementName()" value="click "><input type="button" name="happy" onclick="getElementName()" value="click ">

    </form>

    </body>

    </html>

      document.getElementsByName()这个方法.它对一个和多个的处理是一样的,我们可以用:

      Temp = document.getElementsByName('happy')来引用

      当Temp只有1个的时候,那么就是Temp[0],有多个的时候,用下标法Temp[i]循环获取

      也有例外:

      在ie 中getElementsByName(“test“)的时候返回的是id=test的object数组,而Firefox则返回的是name= test的object的数组。

      按照w3c的规范应该是返回的是name= test的object的数组。

      firefox和ie中的getElementByID相同:获取对 ID 标签属性为指定值的第一个对象的引用。

      注意getElementsByName 有s在里面

      document.getElementById()可以控制某个id的tag

      document.getElementsByName(),返回的是一个具有相同 name 属性的元素的集合,而不是某个,注意有“s”。

      而 document.getElementsByTagName() 返回的是一组相同 TAG 的元素集合。

     同一个name可以有多个element,所以用document.getElementsByName("theName")

      他return 一个collection,引用的时候要指名index

      var test = document.getElementsByName('testButton')[0];

      id那个,是唯一的

      还应该注意:对类似没有name属性,对它name属性为伪属性document.getElementsByName() 会失效,当然TD可以设置ID属性,然后用 document.getElementsByID("DDE_NODAY"); 调用.

  • Tags:
    很多时候我们在检测一个数组里是否包含了某个键值时会使用arrar_key_exists这个函数,这个函数最大的有点就是不管这个数组的值是什么,只要这个键值存在就返回true。那么如果在一个大数据量的数组中找到一个键值是否存在用这个函数呢效率就很低了。
      我们从工作原理上来说,要想知道一个数组里是否包含一个键值,那肯定是遍历这个数组一个键值一个键值的检查是否相等,如果全部遍历结束,没找到就返回false,但是这里面就有问题了,如果在大数据量中经常查找一个键值,这么不断的遍历势必造成效率低下。
      其实只要我们知道需要查找的那个数组值不为NULL,那么我们直接使用检查值函数isset(),或者直接使用$arr['key'],效率就会高很多。下面我们做一个例子测试一下:

    <?php
    include("common.php");
    $time1 = getTime();
    $arr = array('a'=>1,'b'=>3);
    for($i=0;$i<10000000;$i++){
    //if ($arr['a']) 1==1;
    if (array_key_exists('a',$arr)) 1==1;
    }
    $time2 = getTime();
    echo $time2-$time1;
    ?>
    结果为:11.054秒

    <?php
    include("common.php");
    $time1 = getTime();
    $arr = array('a'=>1,'b'=>3);
    for($i=0;$i<10000000;$i++){
    if ($arr['a']) 1==1;
    //if (array_key_exists('a',$arr)) 1==1;
    }
    $time2 = getTime();
    echo $time2-$time1;
    ?>
    结果为:4.079秒
    由些可知:用array_key_exists程序效率慢了很多,但是在直接使用的时候,千万一定要注意,对于值为NUll或者0的数组检查,还是需要使用array_key_exists函数的。
    分页: 56/128 第一页 上页 51 52 53 54 55 56 57 58 59 60 下页 最后页 [ 显示模式: 摘要 | 列表 ]