这个项目目前由于各种原因已经结束了,最终没能做到上架那一步,不过RTS的所有坑都踩了一遍。本人是RTS游戏爱好者,这篇文章先泛泛谈一下关键技术问题的遇到的大坑。后面有空再补上细节和代码。
一、最重要的问题是网络同步,没有之一
对于RTS,最重要的就是网络同步问题,无法回避,影响全局,甚至决定成败。
0)网游?局域网游戏?
这个问题必须首先回答,如果你选择了网游,首先扪心自问:有没有足够的启动资金。
如果没有足够的启动资金,而又决定做网游RTS,那么这条道路是极其艰辛的,具体看下面。
1)状态同步/帧同步/指令帧同步/网络状态指令帧同步的选择
同步方式:本地玩家的数据和对战玩家的数据以怎样的形式进行同步。
①状态同步:每隔一段时间,如100ms,将所有玩家的所有单位的所有数据(坦克、兵、飞机的坐标、血量、状态和其他一些参数)同步(设置为同一个数据)。
那么这个大家都认同的数据从哪里来?如果是局域网游戏,并且不care反作弊,那么选择其中一个玩家作为主机,其余玩家每次同步从主机这里取数据完成同步就OK.
具体实现:说起来挺复杂,但是在Unity 上有个叫做Mirror(镜像)的插件,只要在主机端添加添加几个组件,设置为主机,并且给每个需要同步的兵/建筑/子弹挂载好相应脚本,所有位置/属性则会自动同步,基本上不用关心网络。这种方式下,Gameplay方面基本上跟开发单机的模式是一样的,这里不过多赘述。
②帧同步:这是一个很空泛的概念。主要有介绍两种它的具体子类。
③指令帧同步:主要区分于状态同步。
1.网络带宽的消耗:状态同步每隔一段时间就必须将所有玩家的所有单位的状态同步一次。如果是局域网游戏还OK,毕竟局域网流量是免费的,但如果是网游:假设人口上限为100,1v1后期就会有150+个单位状态需要实时同步,3v3就会有500+单位需要实时同步,假设每秒同步5次,这个流量消耗买过服务器的都知道有多贵。
2.反作弊与网络中心问题:做网游,基本不会选取一个玩家做为主机,通常的做法是在服务器端也运行同样的游戏,所有玩家同步数据的时候从服务器端取出数据。Unity Mirror可以实现这样的操作,即每次开一把游戏,都在服务器开一个无头模式的Unity程序并设为主机。或者自行用任意语言实现一个逻辑上和客户端一致的游戏。无论如何,服务器是要跑一遍完整逻辑的,这种情况我没有测试,但是RTS动则几百个单位,开一局,服务器运算压力都是爆炸的,要支持几百人/几千人同时上线对战。如果资金不足搞不起高性能服务器,基本不用想了。
3.便宜的指令帧同步:只同步所有玩家的操作指令,而不同步玩家操作的单位的属性,指令,通过服务器转发给所有玩家,所有玩家的客户端运行同样的指令,得到同样的结果,这样也就用另一种方式达成了同步,但是节省了大笔流量。
例1:{id:0;cmd:build;target:[100,200]},指令表示:在100,200的坐标上,建造0号类型的单位,如电厂/矿场
例2:{id:0;cmd:move;target:[100,200]},指令表示:所有玩家的id为0的单位,移动到坐标为100,200的网格上。
例3:{id:[0,1,2,3,10];cmd:attack;target:88},指令表示:id为0,1,2,3,10号的单位,攻击id为88的单位。
听着很美好是吧,然而指令帧同步要求的三个点
完全一致的本地计算、完全一致的运行时序、完全一致的同步时机
只同步指令,而不同不状态,意味着所有玩家客户端收到相同的指令后,必须计算得到完全相同的结果(比如每一帧移动的量等),而由于Unity物理引擎采用浮点运算,每台手机的浮点运算都可能得到不同的结果(大概率不同),如果每台手机接受同样的指令,而不产生同样的结果,那游戏完全是平行世界。因此列举的下述Unity API 绝对不能使用,使用指令帧同步,基本上就是告别了Unity怀抱。
①Unity 碰撞体组件
wtf?游戏还能没有碰撞体?避障怎么办?子弹伤害怎么办?很遗憾如果选择了指令帧同步,这些统统没有了。
解决方法:1.引入定点数物理引擎(B站有)据说性能不太行。2.不用碰撞(我采用的方式):网格化地图,根据子弹的运动和单位所在的网格计算伤害,这是一个大坑,后面再讲。
②Unity Navigation寻路系统
不能用原因1:Navigation本身基于是浮点+物理碰撞,无法同步。
不能用原因2:Navigation动态避障本身有缺陷,这点在Unity论坛上可以找到很多帖子,运行过程中动态添加障碍物会产生瞬移,或者失效。而对于RTS这种频繁有单位走走停停,有新建筑产生/销毁的游戏,除非深入底层修改源码不然无解
解决方法:自行编写寻路算法,这也是一个大坑后面再讲。
③Unity 刚体组件
所有和物理有关的力全部无法使用,原因也是因为浮点运算得不到一致结果。
④Update方法、IEnumorator、Coroutine
Update方法:只能用于渲染帧更新,而不能在逻辑计算中使用,而必须自己另开一个线程,在这个线程中调用单位的更新逻辑。原因是逻辑和渲染帧必须分离。如果不分离,游戏的FPS就会强行和网络同步绑定。具体来说是比如服务器每100毫秒同步一次指令,那么逻辑帧数就是10帧每秒,如果你在Update中更新逻辑,就会导致游戏的实际帧数也必须阻塞到10帧每秒,如果你需要游戏以60FPS运行,那么你服务器就得每秒同步60次,这基本不可能(每次收发数据的基础延时都有50ms),另外还有不同客户端渲染帧数不一致的问题。所有渲染/逻辑帧分离,是帧同步的绝对要做的。
IEnumorator、Coroutine:按照Unity官方的资料,这两个对指令帧同步应该是没有影响的,但是据我观察协程的启停并不是确定时许的,不信邪可以试试,我建议不要去用。
以上是第一部分网络的相关大坑。后面的有时间再更文章来源:https://www.toymoban.com/news/detail-808427.html
文章来源地址https://www.toymoban.com/news/detail-808427.html
到了这里,关于技术复盘:用指令帧同步做 Unity RTS 网游/手游的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!