如果对Unity其他的优化方式感兴趣,可以看我之前写的另外一篇文章详细讲了各种优化方法,包括但不限于CPU、GPU、资源优化等。
概念与设计原则
- 例子:
- 一个地图中,如果有100人,每个人都移动一下,那么服务器就需要同步1w次同步消息
- 且实际情况下,每人每秒不可能只同步一次,一般情况下可能会每人1s会发送10次同步消息,就需要10w次消息处理
- 按照经验,平均每个消息有30b,这就需要3MB/s的传输速度,需要24兆的带宽,且服务器的交换机的转发率可能甚至无法应对那么多的发包量(或者成本太高),这显然不合理,因此需要引入AOI来优化
- AOI:Area of interest,感兴趣的范围
- 建立兴趣范围清单
- 只对兴趣范围的目标广播
- 极大的降低消息处理压力和网络负载
- 广播的范围:
- 全服广播(全服公告、全服大喇叭等)
- 地图广播(移动同步、战斗同步等),频率最高,压力最大,因此主要在此引入AOI
- 社交关系(公会、好友、队伍等)
- 交互目标(加好友、挑战、交易等)
- 玩家自身(登录,接/交任务,获得道具)
- 设计原则:
- 分析核心需求:
- 降低压力消耗
- 降低带宽
- 提高负载
- 明确设计目标
- 设计兴趣范围规划方案
- 设计对应的对象与数据结构
- 得到高性能算法
- 不为设计而设计
- 优化思想
- 忘记技术
- 不忘初心:适可而止,避免过度优化,优化的曲线通常是对数函数
- 分析核心需求:
范围区域划分方案
- 每个玩家设置一个以自己为圆心的范围,只需要广播自身信息给自身周围中范围内的目标即可,如下图(理解红圈和篮圈需要引入下面的层级设计)。
- AOI的级别设定:
- 比如Level3级别中的目标快要进入视野了,预先加载部分的的资源,方便后续进入Level2、1时候使用
- Level2中的目标已经进入了玩家的视野,需要把他的外观(比如皮肤、武器等等)等基本数据加载到屏幕空间内,同步给玩家,但是同步不需要那么精准
- Level1中的目标不仅进入了玩家的视野而且已经离得很近了,需要把玩家动作、战斗情况等状态加载出来,同步给玩家,同步需要十分精准
-
//伪代码 void OnEntityMove(who) { foreach(var entity in entities) { //如果是自己,跳过 if(who == entity) continue; bool nowInAOI = who.Distance(entity) < who.AOIRange; bool alreadyInAOI = who.AOI.contains(entity); if(alreadyInAOI && !nowInAOI) { //互相移除出AOI列表 who.onLeaceAOI(entity); entity.onLeaceAOI(who); } if(!alreadyInAOI && nowInAOI) { //互相增加进AOI列表 who.onEnterAOI(entity); entity.OnEnterAOI(who); } } }
- 优缺点:
- 优点
- 不需要实现特殊的数据结构
- 易于实现
- 缺点:
- 给个人需要和每个人进行AOI范围判断,计算成本较高(1+N)*N/2
- 即1000人需要500500次
- 优点
- 改善方案:
- 多线程:并行计算,提高计算效率
- 延迟计算:减少AOI的判断计算间隔,比如每秒才跑一次
- 分批计算:100/Frame,每帧只跑一部分的目标
网格区域划分方案
- 引入网格的概念,你在哪个格子只同步格子内的目标,如下图:
-
//伪代码 void OnEntityMove(who) { //获取当前所在的格子 int new_x = (int)(who.position.x/size); int new_y = (int)(who.position.y/size); //进入了新的格子 if(new_x != who.grid_x || new_y != who) { who.LeaveGrid(who.grid_x, who.grid_y); who.EnterGrid(new_x, new_y); this.grid_x = new_x; this.grid_y = new_y } }
- 优缺点:
- 优点:计算速度快
- 缺点:
- 需要额外的数据结构存储格子信息
- 需要额外的格子管理逻辑
- 实现复杂度高
- 格子边界问题
- 改善方法:
- 格子边界问题:
- AOI的范围增加未当前玩家周围的九宫格
- 增加入口边界和出口边界让各自边界更加松散
- 算法优化:
- 优化数据结构:四叉树、八叉树、BVH树等
- 降低运算消耗
- ECS架构:采用面向数据概念的优化架构,提高运算性能
- 并行运算与GPU加速:
- 利用并行运算提升性能
- 采用GPU加速减少CPU消耗
- 格子边界问题:
AOI优化方案参考
- 背景:由于正在开发的游戏涉及到10W个移动角色,如果单服1W玩家的话,采用双向循环查找,那就是10E的量级,不得不对算法做优化。
- 场景:1000*1000的地图,1W客户端角色,两个角色间距离是10时有效
- 优化前:随机生成1W角色的位置信息,然后计算哪些角色的信息需要发给范围内的客户端,使用最简单的双向查找算法,找到40030个有效值,耗时880ms
- 优化1:代码逻辑优化,位置是双向的,也就是A在B的范围内,B也在A的范围内,因此只需要循环n*(n-1)/2次,结果耗时416ms
- 优化2:网格优化,由于大部分的角色位置相距较远,因此对地图进行分区,以100为单位,整个地图被分成100个区域,创建区域数组Player[100][],然后计算每个客户端的更新范围所在的区域,并将客户端加入到区域中,注意客户端的四个顶点可能在不同的区域上, 此时在几个区域就要加入几个区域。最后计算角色所在区域,并和区域内的Player计算距离。此算法得到的结果是22.5ms。
- 优化3:位运算优化,区域大小用2^n来表示,从而在计算角色所在区域时可以用位运算左移右移来处理,使用64作为区域大小,优化后的平均耗时是15.5ms
- 优化4:优化网格,从算法的耗时来看,区域小一些,则区域数量变多,但每个区域的角色数量就少了,需要计算的量也会变少,使用32作为区域大小后耗时为9.5ms
- 优化5:如果地图变大一些,角色更加稀疏,则计算量会更少,使用10000*10000的地图,同样是64大小的区域耗时为1.8ms