Unity-Mirror网络框架-从入门到夺目之Benchmark示例

打印 上一主题 下一主题

主题 1031|帖子 1031|积分 3093

前言

在当代游戏开发中,网络功能日益成为提升游戏体验的关键构成部门。Mirror是一个用于Unity的开源网络框架,专为多人游戏开发筹划。它使得开发者能够轻松实现网络毗连、数据同步和游戏状态管理。本文将深入先容Mirror的基本概念、怎样与其他网络框架进行比较,以及怎样从零开始创建一个使用Mirror的简单网络项目。

什么是Benchmark?

Benchmark示例 通常用于测试和评估Mirror网络框架在不同场景下的性能表现。该示例可以通过实施多种网络条件来测量服务器和客户端之间的数据传输速度、延迟、带宽使用情况以及不同数量的同时毗连用户对团体性能的影响。
在测试过程中,开发者可以观察到游戏在进行高并发情况下的响应能力,例如在多人游戏场景中,怎样处置惩罚大量玩家的输入、状态同步和变乱广播等。同时,该示例还可以帮助开发者识别潜在的性能瓶颈,并对代码进行优化,以便实现更流通的用户体验。
通过这些基准测试,开发者能够获取关键的性能指标,从而更好地调解游戏的网络架构和优化计谋,以提高整个平台的稳固性和反应速度,同时确保用户在游戏中能够得到最佳的游戏体验。
Benchmark 扼要说明

Mirror的Example包含哪些 基准测试 示例呢?


  • 1.Benchmark:这个示例用于基准测试网络性能,通常会测试数据传输速率、延迟等网络参数,以帮助开发者评估他们的网络实现。
  • 2.BenchmarkIdle:这个示例主要用于在空闲状态下进行基准测试,目的是评估在没有任何活动情况下的网络性能。这有助于明确网络在闲置时的行为情况。
  • 3.BenchmarkPrediction:这个示例涉及到网络预测技能,通常用于展示怎样在网络延迟情况下进行状态预测,以提高游戏的流通度和响应性。
  • 4.BenchmarkStinkySteak:这是一个更具趣味性的示例,通常用于展示服务器与客户端之间的交互。可能涉及一些风趣的机制或模仿网络延迟的场景,以帮助开发者明确网络通讯是怎样工作的。
这些示例可以帮助开发者更好地明确 Mirror 的功能和用法,并进行性能优化。
Benchmark示例

BenchmarkNetworkManager

基准测试的NetworkManager,主要是在StartServer后,创建了1000个自由飘荡的盒子,代表我们的大场景中的Monster。
逻辑很简单,就是SpawnAll,克隆1000个游戏对象,而且调用NetworkServer.Spawn(go),同步到网络中。
  1. public class BenchmarkNetworkManager : NetworkManager
  2.     {
  3.         public GameObject spawnPrefab;
  4.         public int spawnAmount = 5000;
  5.         public float interleave = 1;
  6.         void SpawnAll()
  7.         {
  8.             float sqrt = Mathf.Sqrt(spawnAmount);
  9.             float offset = -sqrt / 2 * interleave;
  10.             int spawned = 0;
  11.             for (int spawnX = 0; spawnX < sqrt; ++spawnX)
  12.             {
  13.                 for (int spawnZ = 0; spawnZ < sqrt; ++spawnZ)
  14.                 {
  15.                     if (spawned < spawnAmount)
  16.                     {
  17.                         GameObject go = Instantiate(spawnPrefab);
  18.                         float x = offset + spawnX * interleave;
  19.                         float z = offset + spawnZ * interleave;
  20.                         go.transform.position = new Vector3(x, 0, z);
  21.                         
  22.                         NetworkServer.Spawn(go);
  23.                         ++spawned;
  24.                     }
  25.                 }
  26.             }
  27.         }
  28.         public override void OnStartServer()
  29.         {
  30.             base.OnStartServer();
  31.             SpawnAll();
  32.         }
复制代码
MonsterMovement

这个脚本是模仿MMORPG中的Monster,在大场景中自由的移动。
这里着重说一下,Update函数是模仿怪物自由移动的,
但是Update上面有一个[ServerCallback]修饰,代表这是一个服务器才会执行的函数。
在Mirror框架中,如果一个函数被[Server]或者[ServerCallback]修饰,那么他就代表仅在服务器执行,客户端不执行。
这里相当于在服务器直接模仿了大量的AI怪物的移动。保证了不同客户端中显示的AI物体的同步。
  1.     public class MonsterMovement : NetworkBehaviour
  2.     {
  3.         public float speed = 1;
  4.         public float movementProbability = 0.1f;
  5.         public float movementDistance = 20;
  6.         public override void OnStartServer()
  7.         {
  8.             start = transform.position;
  9.         }
  10.         [ServerCallback]
  11.         void Update()
  12.         {
  13.         }
  14.     }
复制代码
PlayerMovement

PlayerMovement模仿的是玩家自由控制移动。纯客户端执行。
由于Player对象上有NetworkTransform组件,以是它的位置等信息会实时同步给其他客户端。
当我们在场景中走动的时候,我们发现可以看到的Monster,会根据自己的位置变革,间隔Player位置近的会显示,间隔远的会隐藏。这个是什么原理呢?
原来是Mirror给我们做了一个网络优化,在NetworkManager游戏对象上面,有一个组件名字叫:SpatialHashingInterestManagement(空间哈希兴趣管理),是一种用于优化网络游戏中对象的管理和交互的方法。它通过将游戏世界划分为多个小区域(或“哈希桶”),来决定哪个玩家必要吸收哪些对象的信息,从而提高性能并减少不必要的数据传输。
他就雷同在做大世界的MMORPG时,只显示间隔我们近的范围的AI怪物,并非全地图绘制和执行全部怪物的逻辑的优化。
InterestManagement

Interest管理器,它的原理就是相当于把地图划分成了无数的格子,然后我们只能看到间隔我们相近的9个格子内的网络对象。
如下图所示:

当我们在一个非常大的世界游戏中时,与其将完整的世界状态发送给每个玩家,不如思量仅将玩家四周的内容发送给玩家。
Mirror给我们提供了InterestManagement的主要缘故原由:


  • 1.规模:想象一下魔兽世界。将整个世界发送给每个玩家将是疯狂的。为了扩展到数千个毗连,我们只必要发送与任何给定玩家相关的内容。
  • 2.可见性:在像 DotA/英雄联盟这样的 MOBA 游戏中,并不是每个人都应该不停看到其他人。玩家应该只看到自己的团队和四周的怪物。不但如此,玩家还不应该看到墙后等。
  • 3.作弊:在像 Counter-Strike 这样的游戏中,玩家自然看不到墙后的敌人,因为相机不会渲染它们。但是,如果整个世界状态在内存中都是已知的,那么黑客无论怎样都可以使用这一点,将玩家显示在墙后。
InterestManagement是一个好主意。
Mirror给我们提供了很多内置的Interest Management组件
选择 Network Manager 并添加一个内置的 Interest Management 组件。


  • 1.Spatial Hashing Interest Management:从前我们 Vector3.Distance 根据毗连实体间隔来判定。如今,我们将每个生成的实体放入一个 Grid 中,对于每个毗连,我们将全部 8 个相邻的 Grid 条目都发送到客户端。这黑白常快的。在早期的 uMMORPG 测试中,它比间隔检查快 30 倍。该算法不太复杂,因此可以很好地扩展到大量实体。
  • 2.Distance Interest Management:是简单的通过间隔判定,来把范围内的毗连发送给客户端,是InteractManager的最简单粗暴的做法。但是缺点就是,通过间隔来判定每个毗连本钱较高。
  • 3.Scene Interest Management:场景Interest 管理与附加场景一起使用,将对象联网到具有物理隔离的子场景。这意味着,即使您在服务器上加载了同一子场景的多个实例,对象之间的碰撞等也只会发生在该子场景内,而不会干扰其他子场景。
  • 4.Scene Distance Interest Management:是上述 Scene 和 Distance 的组合。
  • 5.Match Interest Management :匹配Interest管理,实用于非物理游戏,如纸牌、棋盘、街机游戏。
  • 6.Team Interest Management:用于限定对团队成员的联网对象的可见性。这也可以用于仅限全部者的项目,代替 Network Owner Checker。
性能指标


在Benchmark示例中,您看到的参数主要用于监控和评估网络性能,具体含义如下:


  • buffer:用于存储网络数据包的缓冲区。通常在网络传输中,数据包会被临时存储在缓冲区中,以便后续处置惩罚或发送。
  • driftEMA:表示“漂移的指数移动匀称”(Exponential Moving Average of Drift)。这是一个用于平滑变量的统计量,帮助减少波动并提供更稳固的值,通常用于测量时间戳的漂移或延迟变革。
  • DelTimeEMA:表示“延迟时间的指数移动匀称”(Exponential Moving Average of Delay Time)。此参数用于跟踪网络延迟的匀称值,以便评估网络响应的稳固性。
  • BTM:可能代表“带宽时间测量”(Bandwidth Time Measurement),用于衡量网络在一定时间内能够传输的数据量,帮助评估网络性能。
  • RTT:表示“往返时间”(Round-Trip Time),是从发送数据包开始,到吸收到确认的时间。RTT是网络延迟的重要指标。
  • PredErrUNADJ:表示“未调解的预测毛病”(Unadjusted Prediction Error),用于测量在不调解任何参数的情况下,预测的值与实际值之间的差异。
  • PredErrADJ:表示“调解后的预测毛病”(Adjusted Prediction Error),与未调解的预测毛病相似,但在计算时可能思量了某些调解因素,从而得到更精确的预测效果。
这些参数共同用于评估和优化网络性能,特别是在多人游戏中,以帮助开发者明确网络延迟和数据传输的表现。
BenchmarkIdle示例

该示例,使用大多数空闲对象进行基准测试,以测试dirtyObjects技能。
这次创建的额对象都是空闲不动的,他们的数量大概有10000个
这产生了大量的流量。
该测试必要Telepathy才能不停开客户端。

这个示例的代码比较简单,就是纯创建10000个cube矩阵。
如下所示:
  1. public class BenchmarkIdleNetworkManager : NetworkManager
  2.     {
  3.         [Header("Spawns")]
  4.         public int spawnAmount = 10_000;
  5.         public float      interleave = 1;
  6.         public GameObject spawnPrefab;
  7.         [Range(0, 1)] public float spawnPositionRatio = 0.01f;
  8.         System.Random random = new System.Random(42);
  9.         void SpawnAll()
  10.         {
  11.                 //for循环,创建10000个目标
  12.         }
  13.         public override Transform GetStartPosition()
  14.         {
  15.             startPositions.RemoveAll(t => t == null);
  16.             if (startPositions.Count == 0)
  17.                 return null;
  18.             int index = random.Next(0, startPositions.Count); // DETERMINISTIC
  19.             return startPositions[index];
  20.         }
  21.         public override void OnStartServer()
  22.         {
  23.             base.OnStartServer();
  24.             SpawnAll();
  25.         }
复制代码
BenchmarkPrediction示例

Mirror的Predicted预测基准测试,是针对低端装备/VR进行的优化。
在不与对象交互的情况下,开销为零!
在交互过程中,开销来自同步和校正。
这个基准预测了不停同步和校正的对象。
=>真实游戏中效果会比这个示例好,因为这是我们可以用于分析的最坏情况!

NetworkManagerPredictionBenchmark实用于创建1000个物理小球,
RandomForce:是物理小球上的网络组件,用于不同客户端,不中断的给物理小球不绝地施加随机的力,代表了真实物理世界中,不同客户端的玩家,同时操纵处置惩罚同一个物理网络对象,造成的延迟和不稳固。
  1. public class RandomForce : NetworkBehaviour
  2.     {
  3.         public float force = 10;
  4.         public float interval = 3;
  5.         PredictedRigidbody prediction;
  6.         Rigidbody rb => prediction.predictedRigidbody;
  7.         void Awake()
  8.         {
  9.             prediction = GetComponent<PredictedRigidbody>();
  10.         }
  11.         public override void OnStartClient()
  12.         {
  13.             float randomStart = Random.Range(0, interval);
  14.             InvokeRepeating(nameof(ApplyForce), randomStart, interval);
  15.         }
  16.         [ClientCallback]
  17.         void ApplyForce()
  18.         {
  19.             // calculate force in random direction but always upwards
  20.             Vector2 direction2D = Random.insideUnitCircle;
  21.             Vector3 direction3D = new Vector3(direction2D.x, 1.0f, direction2D.y);
  22.             Vector3 impulse = direction3D * force;
  23.             
  24.             rb.AddForce(impulse, ForceMode.Impulse);
  25.             CmdApplyForce(impulse);
  26.         }
  27.         [Command(requiresAuthority = false)] // everyone can call this
  28.         void CmdApplyForce(Vector3 impulse)
  29.         {
  30.             rb.AddForce(impulse, ForceMode.Impulse);
  31.         }
复制代码
我们注意到,OnStartClient中,每个客户端都会开启一个定时器,不时的给小球施加一个力,然后调用了Command,把命令发送给服务器的同时,客户端自身也执行rb.AddForce,为什么这么做呢?



    • 本地预测:
      当客户端调用 rb.AddForce 时,它使用 PredictedRigidbody 组件在本地进行物理计算。这种方法让客户端可以立刻感受到推动的效果,无需等待服务器的响应,从而提供更流通、即时的反馈给玩家。


    • 网络同步:
      同时,CmdApplyForce 方法是一个 Command 方法,用于将力的应用哀求发送到服务器。即使客户端立刻对物体施加了力,该方法仍然会在服务器上执行一次相同的操纵。这样,其他客户端也能看到这一变革,确保全部客户端的状态一致。


    • 辩论说明:
      由于物理计算是基于每个客户端本地进行的,以是如果网络延迟或不同的客户端施加的力不同,预测可能与服务器的实际状态有所毛病。
      PredictedRigidbody 组件的筹划宗旨是处置惩罚这些辩论。当服务器收到命令执行 rb.AddForce 时,物理效果会被应用,但如果与当前客户端的状态不一致,PredictedRigidbody 将主动处置惩罚这些调解,以确保同步。


    • 并发效果:
      正如注释所述,这个方法允许每个毗连的客户端对全部对象施加影响。因此,随着客户端数量的增长,场景的行为会变得更加复杂且动态。这种并发处置惩罚能够在基准测试时体现网络和同步机制的能力。

使用这种方式的目的是为了在保持快速响应和流通玩家体验的同时,确保每个玩家都能看到一致的游戏状态,从而实现更好的用户体验和网络表现。

BenchmarkStinkySteak示例


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

tsx81429

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表