帧同步技术是早期RTS游戏常用的一种同步技术,本篇文章要给大家介绍的是RTX游戏中帧同步实现,帧同步是一种前后端数据同步的方式,一般应用于对实时性要求很高的网络游戏,想要了解更多帧同步的知识,继续往下看。

一.背景

帧同步技术是早期RTS游戏常用的一种同步技术。与状态同步不同的是,帧同步只同步操作,其大部分游戏逻辑都在客户端上实现,服务器主要负责广播和验证操作,有着逻辑直观易实现、数据量少、可重播等优点。

部分PC游戏如帝国时代、魔兽争霸3、星际争霸等,Host(服务器或某客户端)只当接收到所有客户端在某帧输入数据后,才会继续执行,等待直至超时认为该客户端掉线。很明显,当部分客户端因网络或设备问题无法及时上传操作数据,会影响其它客户端的表现,造成不好的游戏体验。考虑到游戏公平竞争性,这种需要等待的机制是必需的,但并不符合手游网络环境的需求。为此,需要使用“乐观”模式,即是Host采集客户端上传操作并按固定频率广播已接收到的操作数据,不在乎部分客户端的操作数据是否上传成功,且不会影响到其它客户端的游戏表现,如图1所示。

(图1)

二.剖析Unity3D

帧同步技术最基础的核心概念就是相同输入,经过相同计算过程,得出相同计算结果。按照该概念,下面将简单描述Unity3D实现帧同步时所需要改造的一些方面,Unity3D中脚本生命周期流程图如图2所示。

(图2)

帧同步需要避免使用本地计时器相关数值。因此,使用Unity3D实现帧同步的过程所需注意的几点:

1. 禁用Time类相关属性及函数,如Time.deltaTime等。而使用帧时间(第N帧 X 固定频率)

2. 禁用Invoke()等函数

3. 避免在Awake()、Start()、Update()、LateUpdate()、OnDestroy()等函数中实现影响游戏逻辑判断的代码

4. 避免使用Unity3D自带物理引擎

5. 避免使用协程Coroutine

三.具体实现

对于本文的实现,有如下定义:

关键帧:服务器按固定频率广播的操作数据帧,使用唯一ID标识,主要包括客户端输入数据或服务器发送的关键信息(例如游戏开始或结束等消息)

填充帧:由于设备性能和网络延迟等原因,服务器广播频率不可能达到客户端的更新频率。若只使用关键帧来驱动游戏运作,就会造成游戏卡顿,影响体验。因此,除关键帧外,客户端需要自行添加若干空数据帧,以使游戏表现更为流畅

逻辑帧更新时间:客户端执行一帧所需时间,可根据设备性能和网络环境等因素动态变化

服务器帧更新时间:服务器广播帧数据的固定频率,一般用于帧间隔时间差的逻辑计算

3.1 主循环

帧同步要求相同的计算过程,这就涉及到两个方面,其一是顺序一致,Unity3D主循环不可控,需自定义游戏循环,统一管理游戏对象以及脚本的执行,确保所有对象更新与逻辑执行顺序完全一致。另一方面是结果一致,凡有浮点数参与的逻辑计算需要特殊处理。

class MainLoopManager : MonoBehaviour
{bool m_start;int m_logicFrameDelta;//逻辑帧更新时间int m_logicFrameAdd;//累积时间void Loop(){......//遍历所有脚本
    }void Update(){if (!m_start)return;if (m_logicFrameAdd < m_logicFrameDelta){m_logicFrameAdd += (int)(Time.deltaTime * 1000);}else{int frameNum = 0;while(CanUpdateNextFrame() || IsFillFrame()){Loop();//主循环frameNum++;if (frameNum > 10){//最多连续播放10帧break;}}m_logicFrameAdd = 0;}}bool CanUpdateNextFrame();//是否可以更新至下一关键帧bool IsFillFrame();//当前逻辑帧是否为填充帧
}

3.2 自定义MonoBehaviour

Unity3D脚本生命周期中部分函数、Invoke、Coroutine调用时机与本地更新相关,并不满足帧同步机制的要求。我们通过继承MonoBehaviour类来实现上述函数和功能需求,并使所有涉及逻辑计算的组件都继承该自定义类。

class CustomBehaviour : MonoBehaviour
{bool m_isDestroy = false;public bool IsDestroy{get { returnm_isDestroy; }}public virtual void OnDestroy() {};public void Destroy(UnityEngine.Objectobj){......//销毁游戏对象
 }
}

3.2.1 Update()与LateUpdate()

从可控性和高效性两方面来看,不建议采用逐一遍历游戏对象获取CustomBehaviour的方式去调用Update()与LateUpdate(),而是单独使用列表来管理。

delegate void FrameUpdateFunc();
class FrameUpdate
{public FrameUpdateFunc func;public GameObject ower;public CustomBehaviour behaviour;
}class MainLoopManager : MonoBehaviour
{......List m_frameUpdateList;List m_frameLateUpdateList;nnpublic RegisterFrameUpdate(FrameUpdateFunc func, GameObject owner)public UnRegisterFrameUpdate(FrameUpdateFunc func, GameObject owner)public RegisterFrameLateUpdate(FrameUpdateFunc func, GameObject owner)public UnRegisterFrameLateUpdate(FrameUpdateFunc func, GameObject owner)
void Loop(){//先遍历m_frameUpdateList//再遍历m_frameLateUpdateList
    }......
}

采取添加删除的方式,对组件是否需要执行Update()与LateUpdate()进行动态地管理,除了具有相对的灵活性,也保证了执行效率。

3.2.2 Invoke相关函数

Invoke、 InvokeRepeating、 CancelInvoke等函数需要使用C#中的反射机制,根据object对象obj和函数名methodName来获取MethodInfo如:

var type = obj.GetType();
MethodInfo method = type.GetMethod(methodName);

通过接口封装,组成相关数据(InvokeData),放入列表等待执行。

class InvokeData
{public object obj;public MethodInfo methodInfo;public int delayTime;public int repeatRate;public int repeatFrameAt;public bool isCancel = false;
}

如上述结构,delayTime用于记录延迟执行时间,repeatRate代表重复调用的频率,repeatFrameAt则标记上次调用发生的帧序号,而isCancel标记Invoke是否被取消。最后,统一使用MethodBase.Invoke(objectobj, object[] parameters)执行调用。

class MainLoopManager : MonoBehaviour
{......List m_invokeList;void Loop(){//先遍历m_frameUpdateList//再遍历m_frameLateUpdateList//遍历m_invokeList,并根据相关属性分别进行Invoke、 InvokeRepeating、CancelInvoke
    }......
}

3.2.3 协程Coroutine

协程Coroutine较复杂,必需采用的情况较少,本文方案未实现协程Coroutine功能,而是避免使用。

3.2.4 Destroy相关

在Destroy游戏对象或组件后,OnDestroy()将在下一帧执行。因此,需要采取可控的方式代替OnDestroy()函数完成资源的释放。

class CustomBehaviour : MonoBehaviour
{bool m_isDestroy = false;public bool IsDestroy{set { m_isDestroy = value; }get { return m_isDestroy; }}public virtual void DoDestroy() {};public void Destroy(UnityEngine.Object obj){if (obj.GetType() == typeof(GameObject)){GameObject go = (GameObject)obj;CustomBehaviour behaviours = go.GetComponents();for (int i = 0; i < behaviours.Length; i++){behaviours[i].IsDestroy = true;behaviours[i].DoDestroy();}}else if (obj.GetType() == typeof(CustomBehaviour)){CustomBehaviour behaviour = (CustomBehaviour)obj;behaviour.IsDestroy = true;behaviour.DoDestroy();}UnityEngine.Object.Destroy(obj);}
}

3.3 Time类与随机数

帧同步游戏逻辑所有涉及时间的计算都应采用帧时间,即:当前帧序列数 * 服务器帧更新时间 /(填充帧数 + 1),而每帧随机数计算都由服务器下发种子来控制。如下:

class MainLoopManager : MonoBehaviour
{.......int m_serverFrameDelta;//毫秒int m_curFrameIndex;int m_fillFrameNum;int m_serverRandomSeed;public int serverRandomSeed{get { return m_serverRandomSeed; }}public int curFrameIndex{get { return m_curFrameIndex; }}public static int curFrameTime{return m_curFrameIndex * m_serverFrameDelta / (1 + m_fillFrameNum);}public static int deltaFrameTime{return m_serverFrameDelta / (1 + m_fillFrameNum);}.......
}

可写入CustomBehaviour中,便于自定义Time类的调用,避免误用Unity3D的Time类,Random类同理。

class CustomBehaviour : MonoBehaviour
{protected class Time{public static Fix time{get { return (Fix)MainLoopManager.curFrameTime / 1000; }}public static Fix deltaTime{get { return (Fix)MainLoopManager.deltaFrameTime / 1000; }}}protected class Random{public static Fix Range(Fix min, Fix max){Fix diff = max - min;Fix seed = MainLoopManager.serverRandomSeed;return min + (int)FixMath.Round(diff * (seed / 100));}}
}

其中Fix是定点数,3.4小节会简单描述如何将定点数运用在Unity3D中。本文实现中约定随机种子范围在0-100之间,并采用简单的计算方式。如有特殊需求,自行实现。

3.4 定点数

客户端必须保证对网络帧操作的运算过程和结果一致,然而不同系统平台对浮点数的处理有差别,即便差别甚微,也会造成“蝴蝶效应”,导致不同步现象出现。绝大多数情况下,只需要对游戏对象方位进行定点数改造即可。而Unity3D并非开源游戏引擎,无法对底层transform的position和rotation进行修改。因此,逻辑层计算时需要使用到自定义以定点数为基础的position和rotation,并在每次循环结束之前,将自定义的方位逻辑计算之后所得信息转化Unity3D transform,以便Unity3D更新表现层。使用Unity3D的协程功能Coroutine以及WaitForEndOfFrame()可满足上述需求,即在逻辑层计算完成后,在Unity3D渲染之前更新底层transform的position和rotation。

3.5 网络波动

帧同步机制下,玩家输入发送到网络,所有响应都必须要等网络逻辑帧才能进行处理。理想环境下,网络帧操作接收到的频率是固定的,能保证客户端表现正常不卡顿。但事实是,绝大多数情况下网络都是不稳定的,时快时慢难以预测。最简单的方案就是建立一个网络逻辑帧的缓冲区,设置一个缓冲区上限,当存入缓存区的帧数满足上限之后,按照固定频率播放。若缓冲区变空,等待其重新填满。通过累积网络逻辑帧延迟,平均分布到固定频率,平滑处理了网络波动造成的卡顿。

原文地址:http://gad.qq.com/article/detail/7195472

转载于:https://www.cnblogs.com/ring1992/p/7089028.html

Unity3D RTS游戏中帧同步实现相关推荐

  1. 在《王者荣耀》来聊聊游戏的帧同步

    系列文章目录 文章目录 系列文章目录 前言 一.服务器架构 二.通信方式 1.tcp保证数据可靠性是有代价的 2.udp的可靠性-DIY手动组装 三.同步方案 1.帧率 2.Lockstep-帧同步 ...

  2. Unity ECS实现RTS游戏中的游戏单位框选、集结和移动控制

    今天想给大家分享的主题是如何实现RTS类型游戏中的游戏单位角色控制 本文中会介绍如何运用最新的ECS架构来实现游戏单位控制 效果演示 效果实现 选中多个游戏单位 public class UnitCo ...

  3. B1:Unity制作Moba类游戏——Socket帧同步

    刚上大学就喜欢玩,英雄联盟陪伴了我整个大学生涯,承载了满满的回忆,现在老娘要亲手做一个!![文末有福利哦~] 相信做开发的小伙伴们都有一个"游戏梦",就是亲手制作出自己热爱的游戏, ...

  4. 从王者荣耀聊聊游戏的帧同步

    这是王者荣耀技术分析系列第二篇,有兴趣请持续关注我的blog和公众号. 1.像<王者荣耀>一样红过 2.从<王者荣耀>来聊聊游戏的帧同步 3.游戏服务器的架构演进 农药自从上线 ...

  5. unity3d 2D游戏中摄像机投影类型

    我们盘点一下unity3d 2D游戏中必备的几个元素. 摄像机:无论是3D游戏还是unity3d 2D游戏摄像机都是非常重要的属性,移动摄像机即可更改屏幕中显示的内容,游戏地图的坐标永远都不会发生改变 ...

  6. 从《王者荣耀》谈游戏的帧同步

    转载:http://www.gameres.com/774540.html 农药自从上线以来,依靠着强大的产品力以及腾讯的运营能力,在游戏市场上表现可谓是风生水起,根据第三方的调研数据显示,<王 ...

  7. 从《王者荣耀》来聊聊游戏的帧同步

    农药自从上线以来,依靠着强大的产品力以及腾讯的运营能力,在游戏市场上表现可谓是风生水起,根据第三方的调研数据显示,<王者荣耀>渗透率达到22.3%,用户规模达到2.01亿人,每日的日活跃用 ...

  8. Unity游戏开发 帧同步战斗框架 框架篇

    本篇主要是提供帧同步主体框架的实现方法以及源码.还有一些坑点的分析.如果对帧同步理论感兴趣的话可以看看这里<帧同步理论篇>. ps:我的同步框架是在lua里面写的,已经经过了测试没有什么问 ...

  9. RTS游戏中的战争迷雾原理

    原文:http://blog.csdn.net/xoyojank/article/details/12259161 说到战争迷雾(Fog of War, FOW), 其实还是非常普遍的一项技术, 在R ...

最新文章

  1. 浅谈Aho-Corasick automaton(AC自动机)
  2. attr和prop的区别以及在企业开发中应该如何抉择
  3. 论文浅尝 | 知识图谱相关实体搜索
  4. 当我们谈微服务,我们在谈什么?谈谈我对微服务的理解!
  5. Python 进阶 —— x = x+1 vs x += 1
  6. 显示隐藏、淡入淡出、上卷下展的区别
  7. 数学与逻辑思维(一)
  8. python 拼音搜索中文_JS实现输入拼音搜索中文列表
  9. 面试题:用两个队列实现一个栈
  10. 视频教程-网页设计速成之Dreamweaver-Dreamweaver
  11. 10000marker_嘉美生物-精准的DNA分子量标准 Marker。
  12. 卡耐基:一个人的成功,只有15%是靠专业技术
  13. Windows XP IIS 500错误
  14. 信用卡上了“灰名单”怎么办?如何解除?
  15. 凉凉!写了个脚本,不小时锁了1W台手机。。
  16. java ActionListener 接口如何判断触发事件来源。getSource()和 getActionCommand()
  17. UI层自动化测试介绍
  18. IOT专用IOP平台
  19. 计算机科学与技术中文核心期刊
  20. (五)以太坊——委托投票智能合约

热门文章

  1. 最详细的word文档自动编号
  2. 孕期补微量元素钙后不要马上喝茶
  3. [转] 移动信息网商业模式讨论会-文字实录
  4. 沪牌学院-沪拍拍课堂3: 网络优化
  5. 托尔斯泰经典语录30条
  6. ComponentScan
  7. Matlab中rgb2gray函数用法
  8. 1069. 凸多边形的划分
  9. Canal监控MySQL数据库实现数据同步
  10. 漫谈牛熊之四,政策取向