精华内容
下载资源
问答
  • 同步如何分离逻辑和渲染?
    2021-02-05 13:51:32

    说说我的理解吧,感觉下面的很多答主都没说到点子上。

    先解释渲染与逻辑分离,这个方案早在上世界90年代的游戏《帝国时代》里面就有用到。当时看完论文我就有很多疑惑,由于渲染要根据逻辑数据执行,那么就算把二者拆离,只要逻辑帧不运行,渲染再多跑多少帧那下一个逻辑帧来之前不还是显示静止的画面么?比如下图第二个例子,高延迟下(1000ms)执行一个逻辑帧以及19个渲染帧,假如逻辑帧执行完算出A对象的位置为(0,0,1),那么后面19帧渲染不还是拿(0,0,1)的位置进行渲染么?

    后来我思考得到结论,其实这个例子中的1000ms都属于一个逻辑帧,而不只是最前面的那一帧,但是涉及到游戏核心逻辑的计算确实只在第一帧执行。渲染与逻辑分离并不是说逻辑执行完各个端的Input逻辑后就完全不执行任何逻辑了。而是在当前逻辑帧先处理完玩家行为逻辑后,游戏还可以在所谓的“渲染帧”里面执行插值的“逻辑”来平滑对象的位置,但是后续的渲染帧里面并不会执行任何和玩家行为有关的逻辑。也就是渲染帧不会有影响到游戏玩法逻辑的计算,也不会去修改物理碰撞位置。在虚幻引擎里面(采用状态同步,不过思路一致),对象的平滑是通过胶囊体和Mesh模型计算分离而实现的,也就是说一个对象在收到服务器数据并等待下次数据前会去用Mesh模型去进行插值(类比渲染帧),而胶囊体的位置在刚收到数据后设置过一次后就不再变化过(类似逻辑帧)。

    回到题主的问题:

    1.我个人觉得王者荣耀这样的moba游戏一般不需要那么高精度的碰撞检测,也就是没有必要严格按照动画渲染的位置进行碰撞检测。在稳定帧率以及网络比较稳定的情况下不会向我上面图画那样逻辑位置与渲染位置差距那么大(把蓝色方块想象成一个武器),所以你把武器碰撞挂在动画骨骼上用逻辑帧去检测就可以了(渲染帧不会修改物理碰撞盒的位置)。如果想让动画与碰撞保持一致,可能需要提高逻辑帧的计算频率,像其他答主说的那样事先设置好武器的位置变化曲线我认为并不能做到完全一致。

    2.为什么有“我”的概念?你如果做过网络游戏就会明白,本地玩家/本地其他玩家的同步策略一般是不同的。因为你在执行操作的时候可以立刻在本地响应并发送给服务器,如果服务器认为你的操作合法你就可以继续执行,而其他的玩家行为是通过服务器转发同步过来的,所以必定有延迟(其他客户端->服务器->本地客户端)。根据这种情况,我们会制定各种策略来优化玩家的体验(本地玩家行为立刻响应就是一种,我们一般叫预测回滚)。在虚幻引擎中,我们常常会看到这样的代码

    if(GetLocalRole() == ROLE_AUTONOMOUS) //如果角色是本地玩家控制

    if(GetLocalRole() == ROLE_SIMULATS)//如果角色是其他玩家控制

    但是在帧同步中,我们不希望有这种区分,因为无论是本地玩家还是其他的玩家都是“收到Input->执行对应的逻辑”。所以,我们不应该假设当前的对象属于哪种玩家, 也不应该出现类似上面的判断代码并进入不同的分支。

    最后一个问题,由于没有实际做MOBA的项目经验,这里就不多说了。

    更多相关内容
  • 显卡帧,物理帧(渲染帧),逻辑帧(轮)辨析 首先显卡帧是指显卡接口输出的采样时间,这个理论采样时间无限短?但是由于物理信道线材的限制,传输是带通/低通信道,所以实际受限于接口和线材的带宽,所以才有 30帧...

    显卡帧,物理帧(渲染帧),逻辑帧(轮)辨析

    • 首先显卡帧是指显卡接口输出的采样时间,这个理论采样时间无限短?但是由于物理信道线材的限制,传输是带通/低通信道,所以实际受限于接口和线材的带宽,所以才有 30帧 60 帧的限制,这个实现是通过采样 frame buffer 然后给绘制出来。而且由于并不能并行传输(指并行16亿bit)所以显示器必须用扫描的方法显示像素。这里我补充一下购买游戏显示器的时候,一般除了刷新率,还有一个响应时间的参数,这个响应时间基本就是从接口采样到冲刷到液晶面板(或者mini led+液晶、oled whatever)的时间,我们知道 60 帧也就 16ms 一帧,所以 16ms 的延时可能发生在任何地方(层次的思想,因为实际游戏从运算完成到显示出来是层层分包层层克扣的,看完下面会加深这一理解,实际到人眼再到人脑,也是有一个采样分包的,总之实际运输时间成本是不可忽略的,而且还相当大(相对于 CPU、GPU 1ns 能做的事情而言),就像一个西瓜产地买会很便宜),这也是为什么要有垂直同步技术(由于 CPU 可以通过协商时的硬件时钟知道显示器什么时候会冲刷,所以垂直同步是纯OS端软件实现的,而类似 Gsync、Freesync 等技术是为了减轻 OS 端 CPU 的负担,让这个同步过程让显示器的单片机做)来缓解这个 16ms 和实际渲染完成的 intersect 的 interval 不一致引发的撕裂问题,总之如果你加入了一些滤波比如护眼模式、动态模糊等显示器/TV 特效,这个响应时间是层层递增的。
    • 逻辑帧是每秒进行物体对象状态更新的计算次数,他决定了世界时间的运行推进。
    • 物理帧是显卡渲染 pipeline 进行渲染的帧数,基本最简单不影响游戏性能统计帧数的方式是通过一个变量记录 lastframebegin ,然后每次 render 开始的时候用 current time - lastframebegin 得出单帧渲染时间,然后用 1000 除以他,比如渲染一帧间隔了 33.33333 ms,那么就是 30 帧,如果隔了 16.6666 ms 就是 60 帧 etc。对于第三方显示帧数的方法应该是利用 GPU 的中断,这个可能要 O/S 提供 API 支持,因为渲染任务完成后,GPU硬件模块会产生中断告诉CPU,CPU响应中断后触发中断服务例程,这个如果提供了钩子,就能给上层获取数据一个方法了,之后再叠加渲染一个帧数数字就行了(额,可能要用比较底层的方法,写显存之类的?)。
    • 控制逻辑帧和渲染帧分离的方法,通过限制 gameloop 中 update 的时间间隔,如果没有达到时间 offset,直接等于什么都不做,然后这里的渲染也不一定是要 GPU 重新渲染一遍,如果根本就没有更新自然也可以不渲染

    • 限制,这种情况是理想的更新逻辑小于 offset 的,比如整个世界更新外加显卡的渲染都保证在 33 ms 内完成,就能保证有稳定的 30 帧。RoboCatRTS 的方案是 timer spin。如果没有计算上性能的延迟理论上 spin 是正确的,但是对于下面这种情况,说明 spin 是不能采用的:

    这种情况是当计算或渲染超时的时候,下一轮 gameloop 开始的时候,更新了这次渲染的时间,这样显卡或者 CPU 突然的卡顿或者短暂处理复杂场景时情况来说整个世界时间推移对于所有终端来说就不是同步的了(而且他的延迟越来越大)。

    • 一种正确的方法是 spin update 来追赶时间,而渲染不是必要的,渲染只是游戏状态的一个 graphic 采样,重要的是保证世界的状态是和真的时钟同步的。

    灰色点是 gameloop,红色点是触发了 update,空白(红字)是阻塞了,不管阻塞在哪。(但是一般的情况要么是渲染卡了要么是网络 io 卡,除非超大 RTS CPU 100% 一般都不会卡在计算上的)。

    • 这里的一个要点是 lag 是累计的,而不是上面那种丢弃的,只要不管什么延迟,只要回到了新的一轮 gameloop,而且 lag 有超过一逻辑帧的延迟就要不断地 update,而且就算是 update 他里面延迟了,比如网络包没收到,那也是延迟,下次进入 game loop 照样有 lag 累积。
    • 由于这里的 update 完了并不一定 lag 就清零了,这说明实际的逻辑时间应该发生在当前 update 完状态再过了一小会儿,所以需要 render 传入一个时间进行插值渲染,这个渲染实际是指之前games101里学的定制 rendering pipeline 的整个东西,包括各种物体的运动,光线等。
    • 无论是 spin 还是这种,都很消耗 CPU,如果要用 sleep 来实现较少的 update 对应较多的帧数,比如 16 个逻辑帧对应60个渲染帧,中间的状态让 GPU 插值渲染,这样就节省 CPU 的功耗了,这个需要用到系统的定时器支持,而 OS 就还要 spin,所以是无解的。一般要用硬件的计时器,不过这样别的更多问题了和更加多复杂度了我们有。不过实际这个和 granularity 有关,一个 context switch 大概会是几 us 到 几十 us,linux 除了 sleep 按秒计的还有 usleep 按微秒计的。(理论还有 nanosleep,但是没意思,一个 context switch 肯定几千ns)。
    • kernel 的计时器是这样的,对于芯片而言,分频器有正确的基频,硬件给操作系统提供一个时钟中断(在 CLINT 里,8086 是用 8253),这样能够更新系统的时间。对于 time sharing 系统,就必须做一个 mux,这个有点麻烦,只能用数据结构来实现!所有的超时回调和阻塞都和这个 mux 有关。mux 的基本实现的无非链表,队列,最小堆,其他高级一点的就是时间轮(Linux 用的就是多级时间轮实现的),红黑树(The high-resolution timer code uses an rbtree to organize outstanding timer requests.),至于为什么用红黑树而不是普通的 priority queue,有两个原因,一个是内存需要连续的,不然就要用数组指针来搞,然后 heap(pq)的删除是必须 O(n) 查找的,say that 某个注册回调不需要了或者某个进程需要 block 掉或者出异常 kill 掉就需要 remove 了,而红黑树全体有序,所以更胜一筹。而且如果都用红黑树,不用维护太多内核数据结构 adt。kernel 进行操作的延迟不会超过几十条指令,对 1Ghz CPU 来说,不过点 ns 而已,这样反而对 nanosleep 的精确度存疑了。
    • 但是 gameloop 如果要用 os 的定时器一般要用 windows 的,windows 的定时器分辨率太低了(普通 15ms,最高清也只有 1ms whereas 一帧 16.666ms in 60fps)基本用不了。不知道服务器演算的要不要用?也不用,因为 render 的bound(这里是因什么而受阻的意思) 本来就不在 server 的 gameloop 上,而客户端的 render 本来是独立于世界状态可以先行的(比如各种大招过场动画,中途放 CG,技能前摇,更不用说那些模拟类的如 fps,比如守望先锋等,一般的技能本身到生效就有 1s 内的延时,这一段时间内基本够硬件发挥了。如果网络卡爆了丢包延迟,像 PUBG 就直接掉线 T 出去了,显卡卡则没关系,只是你看到卡而已,充当别人的游戏体验吧。。。根据外挂猖狂的实现,推测应该是帧同步吧,搞外挂的应该知道是什么同步)。
    展开全文
  • 如题,同步算法代码实现,本科毕设够用了
  • 物理帧和渲染帧分开,目的就是确保逻辑的同步性,ab两个客户端pk,手机性能不一样,虽然...逻辑帧一秒 6--10次,渲染帧一秒 60.目前安卓是这样,苹果可能是30.如果把人物角色放在逻辑帧里,那么动作看起来就不流畅,...

    物理帧和渲染帧分开,目的就是确保 逻辑的 同步性,ab两个客户端 pk,手机性能不一样,虽然服务端同时发送,但不能因为a手机配置低,卡了,导致跟不上。

    虽然 做了跳帧处理,但加速  不一定要让渲染帧也加速,否则 手机 性能更不上,还是卡。所以必须让 逻辑渲染分开。

    逻辑帧 一秒 6--10次,渲染帧 一秒 60.目前安卓是这样,苹果可能是30.

    如果把 人物角色放在 逻辑帧里,那么 动作看起来就不流畅,所以要放到 渲染帧里面,具体如下。

    voidOnSetPK()

    {

    int count =GModel.getInstance.list.Count;//添加 英雄

    for (int i = 0; i < count; i++)

    {

    hero hr=GModel.getInstance.list[i];

    hr.OnAddHero();//添加英雄

    }

    int counts = GModel.getInstance.list.Count;//

    //删除 没有血英雄

    for (int i = 0; i < counts; i++)

    {

    hero hr=GModel.getInstance.list[i];

    hr.OnDestroyHero();//删除英雄

    }

    int countt = GModel.getInstance.list.Count;//

    //处理逻辑英雄

    for (int i = 0; i < countt; i++)

    {

    hero hr=GModel.getInstance.list[i];

    hr.OnFrame();//处理英雄

    }

    创建英雄  删除英雄 英雄逻辑处理,必须是在 逻辑帧里面 统一管理。

    先 查看  当前帧 有没有需要 创建的,其次是血量不够要删除的,最后是 现有的 英雄逻辑处理。

    在英雄对象 里面,要做一个 跟踪。因为逻辑帧不流畅,放到update里面做,不知道下一步位置,那么就要用 跟踪方式。

    voidUpdate()

    {float newPositionX = Mathf.SmoothDamp(transform.position.x, vec.x, refxVelocity, smoothTime);float newPositionY = Mathf.SmoothDamp(transform.position.y, vec.y, refyVelocity, smoothTime);

    transform.position= new Vector3(newPositionX, newPositionY, 11);

    }

    大概如上,这样只要跟的紧,不会让 玩家看出 有没有碰到。

    86cefc8bfc4793f77172b2675cd50d56.gif

    最后效果如图,拖拽后,前面是    白色后面是彩色卡牌,白色卡牌 是逻辑帧,只要碰撞就可,现在显示为了大家看清。

    后面的彩色卡牌是 渲染帧里面执行。可以发现,白色 每次一大步,但看起来卡,彩色每次一小步但流畅。玩家到时 只看到彩色。但发生碰撞或逻辑,

    程序都按白色卡牌来计算,他们 走的 很近,误差忽略不计,也可以通过速度调整,让更快的 移动能跟的 更近。这样,就算手机卡,我可以关闭渲染帧,逻辑跳帧继续执行,然后等跳帧结束,我再打开渲染帧,那时渲染帧 虽然离 逻辑帧 更远,但他只执行 最后 目标,所以 很快就 看起来自然了。

    展开全文
  • 同步的一些坑

    2021-01-17 15:31:08
    客户端检测服务器的逻辑帧 -> 拿到逻辑帧 -> 进行处理 -> 处理出结果 -> 完成本次逻辑帧处理 -> 表现层将处理结果渲染到屏幕上 -> 结束客户端检测用户操作 -> 打包成action -> 上报到服务器...

    一. 简述

    我们用最精简的模型来描述一下帧同步。

    客户端检测服务器的逻辑帧 -> 拿到逻辑帧 -> 进行处理 -> 处理出结果 -> 完成本次逻辑帧处理 -> 表现层将处理结果渲染到屏幕上 -> 结束

    客户端检测用户操作 -> 打包成action -> 上报到服务器 -> 结束

    在此基础上,客户端可以通过缓存帧,平滑帧,预测,插值,等方案优化表现层的效果及手感,但是底层模型是一样的。

    比如缓存帧就是客户端检测到新的逻辑帧之后不立即处理,而是先缓存到一个队列中,等到满足一定数量之后,才对队列popFrame进行处理。

    而平滑帧,则是在缓存帧的基础上,将popFrame的操作做成定时操作,从而抵抗网络波动导致逻辑帧到达的时间间隔不一致问题。

    帧同步游戏一定要分为逻辑层和表现层。

    表现层怎么折腾都可以,但是逻辑层必须保证确定性。

    那么哪一部分属于逻辑层呢?

    拿到逻辑帧 -> 进行处理 -> 处理出结果 -> 完成本次逻辑帧处理

    打包成action -> 上报到服务器

    所以,平缓帧的实现方案中,才能用普通的setTimeout来直接定时popFrame而没有问题。

    再举个例子,比如用户操作摇杆,摇杆获取到的方向是浮点型,将浮点型直接打包成action是否ok呢?答案是不行的,因为一旦这么处理,服务器的逻辑帧就包含了浮点数。

    那么如果将浮点型先转换为string再打包到action中是否OK呢,这样就是可以的。但是有个条件,就是客户端取到逻辑帧中的这个字段需要做数学运算时,需要从string直接转为定点数,一定不能出现浮点数。

    二. 容易导致不一致的坑

    注意:以下说的都是逻辑层。

    初始化/释放物理实体的顺序

    比如之前在start里面将entity加入到物理世界,而每个客户端的start函数执行顺序不确定,导致物理世界的entities的顺序不一致。从而在进行碰撞检测的时候,检测出来的结果顺序会不一致。

    我们具体来验证一下start/onDestroy函数的执行顺序问题。

    cocos creator对一个对象生命周期主要包含几个方面:

    ctor

    onLoad

    start

    ...

    onDestroy

    而我们可以通过测试代码来确定他们的执行顺序:

    cc.log('ins before');

    // 使用给定的模板在场景中生成一个新节点

    let objStar = cc.instantiate(this.starPrefab);

    cc.log('addChild before');

    // 将新增的节点添加到 Canvas 节点下面

    this.node.addChild(objStar);

    cc.log('addChild after');

    打印出来的结果为:

    ins before

    ctor

    addChild before

    onLoad

    addChild after

    并没有直接打印start。可见,start不会在当前立刻执行,而是要等之后才执行。

    也即start顺序不可控。

    那么onDestroy函数呢?测试代码如下:

    cc.log('destroy before');

    other.custom.node.destroy();

    cc.log('destroy after');

    输出结果为:

    destroy before

    destroy after

    并没有直接打印onDestroy,可见onDestroy函数也不是立刻执行,而是要等之后才执行。

    也即,onDestroy顺序不可控。

    所以不要依赖于cocos自带的start/onDestroy回调函数来增加/删除物理实体,会导致不一致。

    字典keys/values的遍历顺序

    不同的字典实现的排序方案是不一样的,这也导致keys/values的顺序很可能无法统一。

    为了安全,如果一定要遍历字典,需要先对keys做一次sort,而values需要通过遍历sorted_keys来进行获取。

    数学运算确定性

    有几个关键点:

    定点数

    定点数的使用务必保证正确,可以使用 string/int/定点数 来创建定点数。但是绝对不能用double/float来创建定点数,拿来中转也不行。

    随机数

    随机数生成器的种子要一致,并且需要将随机数生成器实例私有化,避免与其他业务公用。比如js中的Math.random()就绝对不可以使用。

    逻辑帧率是否越高越好

    并非如此,建议15帧/秒。

    逻辑帧率增加带来的影响如下:

    逻辑层计算次数增多,cpu消耗越大。

    网络流量消耗越多

    对服务器CPU和带宽压力增大

    三. 表现层优化方案

    I. 插值

    在精简的帧同步模型中,我们提到了

    表现层将处理结果渲染到屏幕上

    但由于逻辑帧率一般比较低(15左右),远不能达到表现层的帧率要求,所以看起来就会是卡卡的,实际上是因为物体的位置是离散的。

    我们可以使用cocos creator中的缓动动画很容易的解决这一点。

    // 这是最简单的方案,但是效果比较差

    // 因为相当于渲染层的帧率与逻辑层一致了

    syncPosWithEntity: function() {

    this.node.setPosition(AppUtils.conventToCCV2(

    this.entity.getPos(),

    ));

    },

    // 尝试平滑移动到物理层的位置

    smoothSyncPosWithEntity: function() {

    // 第一次赋值的位置

    // 在一个帧间隔内,移动过去

    // 说明没有变化

    if (this.moveDstPos != null && this.moveDstPos.equals(this.entity.getPos())) {

    // cc.log("here");

    return;

    }

    this.moveDstPos = this.entity.getPos().clone();

    if (this.moveTween != null) {

    this.moveTween.stop();

    }

    // 使用设定的逻辑帧间隔

    // let duration = this.game.dLogicDt.toNumber();

    // 使用客户端实际接收到逻辑帧的间隔。如果要再复杂一点,就算最近一段时间的平均值。

    let duration = this.game.frameRecvDt || 0;

    // 限制最小值

    duration = Math.max(

    duration,

    this.game.dLogicDt.toNumber()

    );

    // 限制最大值

    duration = Math.min(

    duration,

    this.game.dLogicDt.toNumber() * 3

    );

    // cc.log('duration:', duration);

    // 这样,如果动画慢了的话,会自然追上

    this.moveTween = cc.tween(this.node)

    .to(duration, {

    position: AppUtils.conventToCCV2(

    this.moveDstPos

    )

    }).start()

    },

    为什么是moveto动画呢,这样当新的逻辑帧处理完产生了新的逻辑位置,而我们表现层还没有移动到指定位置时,新的moveto动画会自动加速,不用我们人工干预了。

    当然,在物体刚刚创建并指定了位置的时候,需要调用一次syncPosWithEntity(),否则就会出现物体刚出生,表现层就从(0,0)位置往出生位置移动的动画了。

    至于其中的duration值得好好聊一聊。

    一开始我们是使用

    let duration = this.game.dLogicDt.toNumber();

    经过测试后发现,当逻辑帧率越低的时候,这种方式表现越好。比如在逻辑帧为30帧/秒的时候,卡顿的感觉很明显,但是15帧/秒就比较正常。

    但是核心的原因都是:因为逻辑帧率越低,让动画中断的次数越少。

    所以我们想尽量减少动画的中断。

    本能的想到解决方案就是让动画的播放时间稍微长一点,即让动画能够尽量看起来是在连续播放的(虽然实际上还是先stop后又创建的)。

    this.game.dLogicDt.toNumber() * 1.5

    上面的1.5,其实就是给了下个逻辑帧一点缓冲时间,相当于我们多等了0.5个逻辑帧间隔。 只要下个逻辑帧在这个时间内到达,就不会出现表现层的动画停止。

    但是这样其实还是有问题,因为这个1.5是写死的,并且寄希望于逻辑帧能够在这个时间范围内到达,万一这个时候网速更差呢?

    有没有更好的方法呢?

    有的,就是将客户端算出的当前逻辑帧与上一个逻辑帧的实际间隔时间传入进去。或者通过算法,取出一段时间内的平均值,来反映出平均网络情况。

    let nowMs = Date.now();

    if (this.frameRecvTimeMs != null) {

    this.frameRecvDt = (nowMs - this.frameRecvTimeMs) / 1000;

    }

    this.frameRecvTimeMs = nowMs;

    之后将这个时间与上面那个时间取较大值,但是我们也不能让这个值无限大,所以还要再取一次较小值。其中的3是可以自己调整的,看业务需要。

    // 使用客户端实际接收到逻辑帧的间隔。如果要再复杂一点,就算最近一段时间的平均值。

    let duration = this.game.frameRecvDt || 0;

    // 限制最小值

    duration = Math.max(

    duration,

    this.game.dLogicDt.toNumber()

    );

    // 限制最大值

    duration = Math.min(

    duration,

    this.game.dLogicDt.toNumber() * 3

    );

    但是即使我们做了上面这一切后,单独使用插值的效果也不是特别好。

    直到我们将插值与下面的缓存帧+平滑帧的方案结合,并将逻辑帧率设置为15帧/秒,效果才特别优秀。

    另外有人可能会问,万一逻辑层正在追帧呢?也就是说虽说在渲染层只看到了一次pos变化,但是逻辑层其实经过了好几次变化,那用一个逻辑帧间隔时间作为duration会不会有问题呢?。

    答案是:没问题。既然是追帧,表现层当然要保持与现实时间一致,所以快速追上pos是合理的。

    II. 缓存帧+平滑帧

    一般这两个方案是要结合在一起使用的,也可以和第三大项中的插值一起使用。

    简单的类比就是:看视频很卡的时候,我们会先缓存一会,之后以一个恒定的速度来稳定播放视频。

    虽然说延迟了一点,但是体验会舒服很多。

    这里就直接贴出cocos creator的代码了,比较简单,大家参考一下就好:

    constructor() {

    this._frameIntervelMS = 30;

    // 初始化为-1,确保第一次一定会启动

    this._smoothFramePopIntervalFactor = -1;

    // 软上限。软上限的设置与calcSmoothFramePopIntervalFactor中factor=0时的设置一致

    this._smoothFrameSoftLimitCount = 5;

    }

    setPlayInterval() {

    gg.intervalManager.setIntervalByKey('loopSmoothFramePop', () => {

    let factor = this.calcSmoothFramePopIntervalFactor();

    if (factor === this._smoothFramePopIntervalFactor) {

    // console.log('equal factor, return');

    return;

    }

    this._smoothFramePopIntervalFactor = factor;

    // console.log('not equal factor:', this._usingSmoothFramePopIntervalFactor);

    // 如果已经存在,会直接覆盖

    gg.intervalManager.setIntervalByKey('handleSmoothFramePop',

    () => {

    // 这里只能用临时函数,用类的内部函数会丢失this

    // cc.log("this.popArray.length:", this._popArray.length);

    if (this._popArray.length <= 0) {

    return;

    }

    // 总要先执行一次

    do {

    this.receiveFrameData(this._popArray.shift());

    } while (this._popArray.length > this._smoothFrameSoftLimitCount);

    gg.gameManager.gameScene.arrayLengthLabel.string = this._popArray.length;

    },

    this._frameIntervelMS * factor);

    }, 10);

    }

    // 计算出使用的帧间隔系数

    calcSmoothFramePopIntervalFactor () {

    let framesLen = this._popArray.length;

    let factor = 1;

    if (framesLen === 0) {

    // 说明网络帧有点慢,pop速度可以慢一点

    factor = 1.2;

    }

    else if (framesLen === 1) {

    factor = 1.1;

    }

    else if (framesLen <= 3) {

    // 以同样的速度首发

    factor = 1;

    }

    else if (framesLen <= 5) {

    factor = 1 / framesLen;

    }

    else {

    factor = 0;

    }

    return factor;

    }

    缓存帧+平滑帧带来的效果还是很好的,手感明显的好了很多。但也会有个小问题。

    因为存在追帧的问题,所以有时候会出现隔空碰撞的表现。其实就是因为逻辑层同时处理了多个逻辑帧,而表现层只表现出了碰撞前和碰撞后的画面,中间过程给跳过了。可以通过调整_smoothFrameSoftLimitCount来调整。

    另外代码中使用的定时器一定要使用cocos creator或者其他引擎内部自带的timer来实现,不要使用js的SetInterval,性能很低。

    当然,引擎内部的timer依赖于渲染帧的帧间隔,也就是精度是无法高于一个帧间隔时间的。

    所以我们为什么要把逻辑帧通常设置为15帧/秒而不是30帧/秒,其实也是这个原因,因为担心表现层的定时器会处理不过来。

    当然,设置为30帧/秒也有好处就是表现层即使不做插值看起来也很流畅就是了,总之各有各的好处吧。

    四. 架构设计

    帧同步的游戏架构还是有很多写法的,只要保证最关键的前提:逻辑层与表现层分离。

    我个人总结的几个比较好的架构是:

    方案I(适合大部分游戏)

    彻底分离逻辑层与表现层代码。

    在目录上即完全分开。

    逻辑层游戏可以脱离界面运行,你可以理解为是在写一个命令行程序。

    逻辑层与表现层的交互主要通过事件完成。

    即逻辑层发送事件,表现层监听事件。

    表现层允许读取逻辑层,但是不允许修改。逻辑层禁止读写表现层。

    以位移举例,逻辑层的物体位置是离散的,每次位置变化的时候,就广播一个事件出去。

    渲染层可以通过一个主类来监听,之后分发处理;也可以直接让表现层的类直接监听自己感兴趣的事件。

    比如DisplayPlayer收到LogicPlayer发来的位置变化事件后,就可以选择赋值位置/插值的方式来进行渲染。

    方案II(适合物理模拟类游戏)

    每个表现层对象都有一个逻辑对象,并且逻辑对象作为一个插件挂载到表现层对象上。

    这里千万要注意逻辑对象的初始化顺序不能依赖表现层对象的onLoad/onStart,而是应该在主类里统一初始化。

    表现层对象在每一次update函数中,从逻辑对象获取并更新数据。

    还是以位移为例,整个游戏是受物理引擎驱动的,当逻辑层对象的位置发生变化后,表现层对象在update函数中就会检测到,从而选择赋值位置/插值的方式来进行渲染。

    先这样,其他的等想到了再补充吧。

    展开全文
  • 标识CAN数据报的结构,包括各个场,各个数据位的分布,固定数据位的取值。 通过上下边沿的变换,标识CAN报文在总线中的实际传输过程。
  • 自己实现的简易的Unity同步框架,包含客户端服务器端,运行即用 ip端口全部写好了 也没多少代码, 游戏逻辑完全没有实现(只写了一个简易的测试用的第三人称控制器,完全不适合同步) ,还有很多逻辑需要完善,...
  • 同步-逻辑渲染分离(客户端)

    千次阅读 2020-02-08 10:56:37
    同步-逻辑渲染分离(客户端) 客户端逻辑渲染分离主要解决以下问题: 1,抗网络抖动导致的渲染抖动(各种表现不平滑),不分离的话网络抖动导致逻辑抖动进而影响渲染 2,新建线程处理逻辑层,避免逻辑渲染共用线程...
  • 同步优化难点及解决方案

    万次阅读 2018-08-07 18:22:22
    这是侑虎科技第418篇文章,感谢作者Gordon供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群465082844) ... 作者也是U Sparkle活动参与者...同步这部分...
  • 对于游戏中的各种帧,我之前专门总结了一下,从显卡,操作系统的硬件中断,定时器和 gameloop 来讲到,这里放个连接:游戏开发基础笔记:逻辑帧和物理帧辨析 | Gameloop | 游戏循环_我说我谁呢 --CSDN博客 帧同步...
  • 帧率和同步基础 渲染帧(一般为30到60帧),则是根据逻辑帧(10到20帧)去插值 相同的初始状态(不受画质,本地顺序和状态影响) 完全一致的输入驱动内容及顺序 ​完全一直的代码执行流程(模块调用顺序,容器顺序)...
  • 状态同步与同步

    千次阅读 2022-04-08 11:49:04
    状态同步与同步
  • 游戏中的同步概念

    2020-06-01 11:41:25
    帧同步,介绍帧同步之前首先确定帧的概念,在游戏中有两种帧,逻辑帧和渲染帧(表现帧),逻辑帧和渲染帧的分离就是业务逻辑(数据)和表现的分离,通常大多数游戏都是混在一起的; 而帧同步的实现方式,客户端发送...
  • 帧同步中有两种帧率: 一种是逻辑帧率:逻辑帧率是... 这种情况,我们需要加快逻辑帧率的处理,比如一次同时处理4帧逻辑帧,甚至是8帧逻辑真。 以上的处理方法,也可以适用在客户端追赶进度,或者回放设置进度的情况。
  • 2.服务端将这些操作指令集保存到对应序列字典,并记录号,并在下一将其广播给所有客户端; 3.客户端收到指令集后,分别按序,号进行执行指令集中的操作命令。 4.也就是:相同的时机(序列) + 相同的操作...
  • 要解决这位问题,可以从两个思维解决, ①将物理时间和渲染时间调得相同 ②设计功能时角色及背景都采用同一种模式,要不都用渲染要不都用 物理 我在开发功能的时候,由于 角色的功能很难不采用物理引擎,...
  • Unity3D简单的同步方案

    千次阅读 2018-06-04 11:05:44
    公司的游戏准备上线了,我呢也在准备新项目,这几天看了一下策划文档,写了整体流程和需求接口的代码,终于有一点点时间来写自己的博客了。今天我们就来讲讲同步吧。...
  • 为了解决这个问题,有一些游戏会将逻辑和渲染分开处理(分为逻辑帧和渲染帧),逻辑帧每隔固定的时间去处理所有逻辑事件。在不是严格锁帧的情况下,你本地即使没有收到网络数据也可以在继续执行其他的逻辑并维持高...
  • 基于Altera Cyclone III的可配置间隔数字逻辑设计,王涛,戴志涛,在涉及串行通信的系统中,为了满足通信设备的特定要求,实现串行信号的间隔可配置是十分必要的。本文基于Altera公司的Cyclone III系�
  • 中继上实现点对多点的配置(OSPF)逻辑子接口
  • unity 实现同步

    万次阅读 多人点赞 2019-02-09 17:14:18
    此框架为有同步需求的游戏做一个简单的示例,实现了一个精简的框架,本文着重讲解同步游戏开发过程中需要注意的各种要点,伴随框架自带了一个小的塔防sample作为演示. 目录: 哪些游戏需要使用同步 如何实现一个...
  • 数是指每秒可进行渲染和逻辑计算的次数,游戏每运行一,都要处理逻辑,物理,动画,渲染等工作,其中最耗时的操作往往是就是渲染,模拟代码如下: while(!Stop()) { doRender(); doAnimation(); doLogical();...
  • 用golang写的同步服务器,目标是作为一个可以横向扩展,完全脱离玩法逻辑同步服务器。 特性 采用KCP(可根据需求改成其他协议)作为网络底层 采用同步方式 protobuf作为传输协议 支持断线重连 运行example/...
  • 基于可编辑逻辑器件实现IEEE 802.11协议生成器的应用方案.docx
  • 任何对同步有疑问的人,都应该来看这篇文章,这是参考了2个同步模型,遇到各种问题并一一解决之后,彻底明白同步讲的是什么玩意的一篇文章。断断续续修改了将近2个月,说多了都是泪:(。 言归简短,书归正传。...
  • 同步

    千次阅读 2019-05-07 11:49:29
    同步技术是早期RTS游戏常用的一种同步技术,本篇文章要给大家介绍的是RTX游戏中同步实现,同步是一种前后端数据同步的方式,一般应用于对实时性要求很高的网络游戏,想要了解更多同步的知识,继续往下看。
  • 1、逻辑驱动归一管理,这个主要是要将战斗过程的所有逻辑运算update驱动要统一管理,而不是简单的通过UI 层的update分散驱动,这样做的话你的update是不确定的,这里的确定分两部分1)执行时间上的确定性,2...
  • 同步的应用场景很多,(同步与状态同步的区别,感谢知乎作者云影)从格斗游戏,Rts,到现在的几乎所有Moba类游戏,他们各自的实现方式细节各不相同,同步对不同类型的游戏也有各种变种。交替一下例子,如格斗...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 124,595
精华内容 49,838
关键字:

逻辑帧