Unity网络游戏开发:Mirror框架核心概念与实战避坑指南
1. 项目概述为什么选择Mirror框架如果你正在用Unity做网络游戏并且被Unity自带的UNet已弃用或者第三方网络库的复杂性搞得焦头烂额那么Mirror框架很可能就是你的“救命稻草”。我接触过不少网络同步方案从早期的Photon到后来的FishNet再到深度使用Mirror开发了几个上线项目我的体会是Mirror在易用性、社区生态和与Unity的集成度上找到了一个非常棒的平衡点。简单来说Mirror是Unity平台上一个高性能、开源的上层网络库。它最初是基于UNet的高层APIHLAPI的一个分支但经过多年的迭代已经发展成了一个功能完善、文档相对齐全的社区驱动项目。它的核心价值在于它用一套清晰、直观的API把网络游戏开发中最繁琐的底层通信、状态同步、远程过程调用RPC和命令Command机制给封装好了。你不需要从Socket编程开始写起而是可以更专注于游戏逻辑本身。为什么我要特别强调“避坑”和“实战入门”因为网络游戏开发本身就是坑多路滑。Mirror虽然降低了门槛但如果你不理解其核心架构尤其是Host模式和纯Server模式的区别与应用场景你依然会掉进各种同步问题、性能瓶颈和架构设计的陷阱里。网上很多教程只讲“怎么连上线”但一个稳定的、可扩展的网络游戏从第一行代码开始就需要正确的设计。这篇文章我就结合自己趟过的雷带你从零开始不仅把Mirror用起来更要理解背后的“为什么”让你在开发第一个多人游戏功能时就能建立起正确的认知。2. 核心概念与架构解析Host vs. Server在深入代码之前我们必须把Mirror里最核心、也最容易混淆的两个概念掰扯清楚Host模式和Server/Client模式。这是你设计任何网络功能的基石。2.1 纯Server/Client模式经典架构这是最传统、最经典的网络游戏架构也是大型多人在线游戏MMO、竞技游戏如MOBA、FPS的基石。Server服务器一个独立的、权威的进程。它运行着游戏世界的“唯一真理”Single Source of Truth。所有核心逻辑如伤害计算、物品掉落、胜负判定都在服务器上执行。客户端发送操作请求如移动、施法服务器验证后执行再将结果同步给所有客户端。Client客户端玩家操作的终端。它主要做三件事1. 收集本地输入并发送给服务器2. 接收服务器的状态更新并渲染3. 进行预测和插值以平滑显示。这种模式的优势非常明显安全、公平、易于反作弊。因为所有关键逻辑都在服务器端客户端很难通过修改本地数据来作弊。但代价是延迟和服务器成本。每一次操作都需要经过“客户端-服务器-所有客户端”的回路对网络延迟敏感。同时你需要部署和维护独立的服务器程序。在Mirror中当你运行一个构建好的“Server Build”在构建设置中勾选或者通过代码以服务器模式启动时就是这种架构。NetworkManager组件上的Start Server()方法启动的就是一个不包含本地客户端的纯服务器。2.2 Host模式开发者的利器Host模式是Mirror继承自UNet一个非常贴心的设计它本质上是Server和Client运行在同一个进程里。Host主机它既是服务器也是一个拥有特殊权限的客户端我们常叫它“本地玩家”或“主机玩家”。这个本地客户端通过本地回环地址Localhost与“内置”的服务器通信延迟几乎为零。远程Client客户端其他玩家通过IP地址连接到这个Host进程的服务器部分。Host模式的最大优势是便捷特别适合原型开发、测试和小型合作游戏。你不需要单独启动一个服务器程序在编辑器里点一下Play就能同时模拟服务器和一名玩家。你可以快速测试网络逻辑其他玩家也能直接连进来。很多独立游戏、局域网联机游戏都直接采用Host作为最终架构。但是Host模式潜藏着巨大的隐患也是新手最容易踩坑的地方。因为主机玩家既是“裁判”又是“运动员”。如果你的游戏逻辑没有严格区分“只在服务器运行”和“只在客户端运行”就很容易出现只在Host上工作正常但在纯服务器环境下不同步的Bug。例如如果你不小心在客户端权限的方法里修改了某个网络物体的位置在Host模式下因为本地客户端有特殊权限可能看起来生效了但换到纯服务器模式这个修改会被服务器拒绝导致其他客户端看不到变化。2.3 模式选择与设计影响如何选择我的经验是开发与测试阶段无条件使用Host模式。这是最快的迭代方式。在编辑器里NetworkManager默认的Start Host()按钮就是干这个的。小型合作游戏、派对游戏、局域网游戏可以考虑使用Host作为最终架构。让创建房间的玩家担任Host。你需要处理好主机迁移Host Migration的问题即当主机玩家退出时如何将服务器权威转移给另一个玩家。竞技游戏、MMO、任何涉及排名或虚拟资产的中大型项目必须使用纯Server/Client模式。从项目第一天开始就要假设客户端是完全不可信的。所有逻辑都要在服务器端验证。关键心得无论你最终采用哪种模式在代码编写上必须按照纯Server/Client的权威架构来思考。即明确哪些逻辑应该放在[Server]标签的方法里哪些应该放在[Client]标签的方法里。这样当你的代码在Host模式下测试通过后迁移到纯服务器环境时出问题的概率会大大降低。Mirror提供的[Server]、[Client]、[ServerCallback]等属性标签就是用来帮你做这件事的。3. 环境搭建与基础配置实战理论说再多不如动手搭一个。我们从头开始创建一个最简单的Mirror项目并理解每一个配置项的意义。3.1 安装Mirror与必要组件创建新项目使用Unity Hub创建一个新的3D或2D项目。建议使用Unity 2020.3 LTS或更高版本长期支持版更稳定。安装Mirror最推荐的方式是通过Unity的Package Manager从Git URL安装这样可以获得最新版本。打开Window - Package Manager。点击左上角的号选择Add package from git URL...。输入Mirror的Git仓库地址https://github.com/vis2k/Mirror.git。你可以先到Mirror的GitHub页面确认最新的稳定版分支如#release。点击Add。Unity会开始下载和编译Mirror。选择传输层Mirror支持多种底层传输协议。默认安装后会使用KCP协议一个快速可靠的UDP协议这对于大多数游戏来说是个不错的开始。你会在NetworkManager物体上看到一个Kcp Transport组件。如果你想换用Unity新的Unity Transport Protocol (UTP)需要额外安装com.unity.transport包并参考Mirror的UTP Transport示例进行替换。对于入门我强烈建议先用默认的KCP它更成熟社区问题也更容易搜索。3.2 创建第一个网络管理器与场景创建空场景保存为MainScene。创建网络管理器在场景中创建一个空GameObject命名为NetworkManager。为其添加NetworkManager组件在Add Component中搜索。再添加Kcp Transport组件如果未自动添加。此时你的NetworkManager物体应该至少有两个组件Network Manager和Kcp Transport。理解NetworkManager核心配置Offline Scene/Online Scene当服务器停止或启动时自动加载的场景。这是管理大厅和游戏场景切换的利器。Player Prefab至关重要当一个新玩家连接时服务器会在该玩家控制的“出生点”生成这个预制体。这个预制体必须挂载NetworkIdentity组件。Spawnable Prefabs一个列表包含了所有需要在网络上动态生成的物体预制体如子弹、怪物、宝箱。同样这些预制体都必须有NetworkIdentity。Network Address/Network Port服务器监听的地址和端口。localhost或127.0.0.1表示本地。创建玩家预制体在Project窗口创建一个Cube拖到场景中然后将其拖回Project窗口做成预制体命名为PlayerPrefab。删除场景中的Cube实例。选中PlayerPrefab预制体添加NetworkIdentity组件。将其Local Player Authority勾选上这允许本地客户端控制这个物体的一些属性如位置同步。添加NetworkTransform组件。这个组件会自动同步这个物体的位置、旋转和缩放。这是实现玩家移动同步最简单的方式。最后将这个PlayerPrefab拖拽到NetworkManager组件的Player Prefab插槽中。3.3 编写第一个网络脚本玩家移动现在我们来创建一个脚本让玩家既能被控制又能在网络上同步。创建脚本创建一个C#脚本命名为PlayerMovement。编写基础移动逻辑using UnityEngine; using Mirror; public class PlayerMovement : NetworkBehaviour { public float moveSpeed 5f; private CharacterController controller; // 假设我们使用CharacterController void Start() { // 获取CharacterController组件 controller GetComponentCharacterController(); // 如果不是本地玩家控制的物体禁用输入处理可选项但推荐 if (!isLocalPlayer) { // 可以禁用摄像机、输入脚本等 // 例如GetComponentInChildrenCamera().enabled false; // 本例中我们只是禁用这个脚本对非本地玩家的控制 // 注意我们不禁用整个脚本因为可能还有其他网络逻辑需要运行 // 控制逻辑将在Update中通过isLocalPlayer判断 } } void Update() { // 关键判断只有本地玩家才处理输入 if (!isLocalPlayer) return; float horizontal Input.GetAxis(Horizontal); float vertical Input.GetAxis(Vertical); Vector3 move new Vector3(horizontal, 0, vertical).normalized; if (move.magnitude 0.1f) { // 注意这里只是简单移动实际网络游戏中移动逻辑通常由服务器验证 // 这里为了演示我们直接在客户端计算并依赖NetworkTransform同步 controller.Move(move * moveSpeed * Time.deltaTime); } } }代码解析NetworkBehaviour所有Mirror网络脚本必须继承自它而不是MonoBehaviour。isLocalPlayer这是一个非常重要的属性。它为true时表示这个网络物体是由当前运行的客户端实例所控制的玩家。我们只在isLocalPlayer为真时处理输入这样就避免了其他玩家的角色被你的键盘控制。目前移动逻辑在客户端执行并通过NetworkTransform同步。这不是权威服务器模式在正式项目中你应该将移动向量发送到服务器由服务器计算并广播新位置。这里为了入门演示我们先用简单方式。将脚本挂载到PlayerPrefab上。3.4 运行测试体验Host与Client保存场景并将其添加到Build Settings中。Host模式测试在Unity编辑器中选中NetworkManager物体。在Inspector中你会看到NetworkManager组件下方有一个NetworkManagerHUD的折叠区域如果没有可以手动添加NetworkManagerHUD组件。它提供了一个简单的UI。点击Play运行游戏。游戏运行时屏幕左上角会出现几个按钮。点击Host (Server Client)。你会看到一个玩家Cube生成。你可以用WASD控制它移动。这就是Host模式你既是服务器也是玩家1。连接第二个客户端保持第一个编辑器实例运行。在Unity编辑器中选择File - Build and Run或Build Settings里的Build And Run构建一个独立的客户端程序。运行构建出的.exe文件。在构建出的客户端程序里点击NetworkManagerHUD上的Client按钮地址保持localhost。连接成功后你会在第一个编辑器Host的窗口中看到第二个Cube生成。在构建的客户端窗口里你可以用WASD控制第二个Cube并看到第一个Cube在移动。恭喜你已经完成了一个最简单的多人同步场景。4. 核心网络功能深度实现基础连接跑通了但真正的网络游戏逻辑远不止移动。接下来我们深入Mirror的核心功能远程过程调用RPC、命令Command和同步变量SyncVar。这是实现游戏玩法的三驾马车。4.1 同步变量SyncVar自动同步状态[SyncVar]是Mirror中最简单的状态同步工具。你只需要在一个继承自NetworkBehaviour的脚本中给一个字段加上[SyncVar]特性当这个字段在服务器端发生变化时Mirror会自动将其同步给所有客户端。实战玩家生命值同步using UnityEngine; using UnityEngine.UI; using Mirror; public class PlayerHealth : NetworkBehaviour { [SyncVar(hook nameof(OnHealthChanged))] // 使用hook在值变化时执行自定义方法 public int currentHealth 100; public int maxHealth 100; public Slider healthSlider; // UI血条 public override void OnStartLocalPlayer() { // 只有本地玩家才需要找到并设置自己的UI if (healthSlider null) { // 假设血条UI在Canvas下根据实际情况查找 healthSlider GameObject.Find(HealthSlider).GetComponentSlider(); healthSlider.maxValue maxHealth; healthSlider.value currentHealth; } } // Hook方法当currentHealth在服务器上改变并同步下来后在所有客户端上调用 void OnHealthChanged(int oldHealth, int newHealth) { Debug.Log($Player {netId}: Health changed from {oldHealth} to {newHealth}); // 更新UI if (healthSlider ! null) healthSlider.value newHealth; // 可以在这里触发受伤/治疗特效 if (newHealth oldHealth) { // 播放受伤特效 } } [Server] // 这个方法只能在服务器上调用 public void TakeDamage(int damageAmount) { // 服务器进行权威计算 currentHealth Mathf.Max(0, currentHealth - damageAmount); Debug.Log($Server: Player {netId} took {damageAmount} damage, health now {currentHealth}); if (currentHealth 0) { Die(); } } [Server] void Die() { // 处理玩家死亡逻辑比如重生、掉落物品等 Debug.Log($Server: Player {netId} died.); // 例如RpcRespawn(); // 调用一个客户端RPC来播放死亡动画 // 然后销毁物体或设置为非活跃稍后重生 // NetworkServer.Destroy(gameObject); } }要点解析[SyncVar]标记后currentHealth的修改权在服务器。客户端直接修改这个变量是无效的。hook这是SyncVar最强大的功能之一。它指定一个方法名当同步变量的值从服务器同步到客户端时会自动调用该方法。oldHealth和newHealth参数由Mirror自动传入。这是更新UI、播放音效/动画的绝佳位置。[Server]属性确保TakeDamage和Die方法只能在服务器端执行。这是实现服务器权威的关键。OnStartLocalPlayer一个有用的回调当这个网络物体被确定为本地玩家时调用。适合在这里初始化本地玩家的UI、摄像机等。4.2 命令Command客户端让服务器做事Command是客户端请求服务器执行某个操作的方式。方法名以Cmd为前缀并用[Command]特性标记。Command只能由本地玩家对象isLocalPlayer为真发起且最终在服务器上执行。实战发射子弹public class PlayerShooting : NetworkBehaviour { public GameObject bulletPrefab; public Transform firePoint; public float bulletForce 30f; void Update() { if (!isLocalPlayer) return; if (Input.GetButtonDown(Fire1)) { CmdFire(); } } [Command] // 这个方法在客户端调用在服务器上运行 void CmdFire() { // 服务器端验证检查冷却、弹药等... // 服务器端生成子弹 GameObject bullet Instantiate(bulletPrefab, firePoint.position, firePoint.rotation); // 给子弹施加力在服务器进行权威计算 Rigidbody rb bullet.GetComponentRigidbody(); if (rb ! null) { rb.velocity firePoint.forward * bulletForce; } // 关键步骤在网络上生成这个子弹这样所有客户端都能看到 NetworkServer.Spawn(bullet); // 调用一个RPC在所有客户端上播放开火特效和音效非权威只用于表现 RpcOnFireEffect(); } [ClientRpc] // 这个方法在服务器调用在所有客户端包括发送者客户端上运行 void RpcOnFireEffect() { // 在这里播放粒子效果、音效等。 // 注意因为CmdFire里已经生成了子弹这里通常只播放枪口火焰、音效等附加效果。 Debug.Log(Playing fire effect on all clients.); } }要点解析在Update中我们检查输入但不直接实例化子弹。而是调用CmdFire()。CmdFire在服务器上执行。在这里我们进行权威的实例化、物理计算。NetworkServer.Spawn(bullet)这是让一个GameObject成为网络物体的关键。只有被Spawn的物体才会被Mirror管理并同步到所有客户端。别忘了把你想要生成的预制体如bulletPrefab添加到NetworkManager的Spawnable Prefabs列表里RpcOnFireEffect是一个客户端RPC用于同步非权威的视觉效果。确保开火看起来是即时的即使子弹的生成和同步有微小延迟。4.3 客户端RPCClientRpc服务器通知所有客户端ClientRpc简称Rpc与Command相反。它由服务器调用在所有连接的客户端上执行。方法名以Rpc为前缀并用[ClientRpc]特性标记。常用于播放全局动画、音效、更新非玩家专属UI等。实战服务器广播聊天消息public class ChatSystem : NetworkBehaviour { public Text chatDisplay; // UI Text组件用于显示聊天记录 [ClientRpc] public void RpcReceiveMessage(string playerName, string message) { // 这个方法会在所有客户端上执行 string formattedMessage $[{playerName}]: {message}; Debug.Log(formattedMessage); if (chatDisplay ! null) { chatDisplay.text formattedMessage \n; } } // 这个方法可以由任何客户端调用例如通过UI输入框 [Command(requiresAuthority false)] // 注意这里允许非玩家对象调用比如UI事件系统 public void CmdSendMessage(string message) { // 服务器收到消息可以在这里进行过滤、审查... string senderName connectionToClient.identity.GetComponentPlayerInfo().playerName; // 然后广播给所有人 RpcReceiveMessage(senderName, message); } }要点解析CmdSendMessage客户端UI触发发送消息到服务器。RpcReceiveMessage服务器收到后调用此RPC将消息和发送者名字广播给所有客户端。requiresAuthority false默认情况下Command只能由拥有该网络物体权限的客户端调用即isLocalPlayer。对于聊天系统我们可能希望UI元素不属于某个玩家物体也能触发这时可以设置这个属性。使用时要非常小心安全风险。4.4 权限与所有权Authority这是Mirror中一个高级但至关重要的概念。每个NetworkIdentity都有一个netId网络唯一ID和一个clientAuthorityOwner客户端权限所有者。服务器拥有所有物体的默认权限。玩家预制体通常被赋予Local Player Authority这意味着对应的客户端对其有“部分”权限例如可以对其发起Command。当服务器使用NetworkServer.SpawnWithClientAuthority(gameObject, connection)生成一个物体时这个物体就归指定的客户端连接所有。该客户端可以对这个物体调用Command。常见的例子是玩家拾取的物品、召唤的宠物。你可以通过identity.hasAuthority在NetworkBehaviour中来判断当前客户端是否对这个物体拥有权限。理解所有权对于设计物品拾取、技能召唤等机制非常关键。一个黄金法则是涉及游戏状态改变的核心逻辑最终决定权必须在服务器。客户端权限只是一种“请求执行”的便利通道。5. 高级主题与性能优化避坑指南当你的游戏有了基本功能玩家数量增多复杂交互出现时下面这些坑就等着你了。5.1 网络序列化与自定义消息SyncVar很方便但只能同步基本类型int, float, string, Vector3等和某些Unity类型。如果你想同步一个自定义的类或结构体或者需要更高效地同步大量数据你需要了解序列化。Mirror使用一个名为Weaver的代码生成工具在编译时处理网络相关代码。对于自定义类型你需要做两件事为自定义类/结构体添加[System.Serializable]特性。让这个类型实现一个静态的Serialize和Deserialize方法或者使用Mirror提供的NetworkWriter/NetworkReader扩展方法。示例同步玩家装备信息using Mirror; using System; [System.Serializable] public struct Equipment { public string headItemId; public string weaponItemId; public int armorLevel; } public static class EquipmentSerializer { public static void WriteEquipment(this NetworkWriter writer, Equipment equipment) { writer.WriteString(equipment.headItemId); writer.WriteString(equipment.weaponItemId); writer.WriteInt(equipment.armorLevel); } public static Equipment ReadEquipment(this NetworkReader reader) { Equipment equipment new Equipment { headItemId reader.ReadString(), weaponItemId reader.ReadString(), armorLevel reader.ReadInt() }; return equipment; } } public class PlayerEquipment : NetworkBehaviour { // 现在你可以同步这个自定义结构体了 [SyncVar] public Equipment myEquipment; }要点你需要在项目中的某个地方如一个静态类定义这些扩展方法。Weaver会自动发现它们。这是优化网络流量的重要手段比如你可以把一个玩家的状态打包成一个结构体一次性同步而不是用多个SyncVar。5.2 场景管理与玩家重生NetworkManager的Offline Scene和Online Scene属性可以帮你自动切换场景。但更复杂的情况比如多个游戏关卡、大厅、房间你需要手动管理。关键APINetworkManager.ServerChangeScene(“SceneName”)服务器切换场景所有客户端会自动跟随。NetworkServer.SpawnObjects()在新场景加载后服务器需要调用此方法来生成场景中标记为“Network Start Position”的出生点和已存在的网络物体。OnServerSceneChanged/OnClientSceneChanged网络场景加载完成后的回调适合在这里进行场景特定的初始化。玩家重生流程玩家死亡时服务器销毁玩家物体NetworkServer.Destroy或将其设为非活跃。服务器记录重生计时。计时结束后服务器在某个出生点通过NetworkManager的GetStartPosition()获取调用NetworkServer.AddPlayerForConnection(connection, playerPrefab)来为连接重新创建玩家物体。注意不是用Spawn而是用AddPlayerForConnection这会将新物体与原来的客户端连接关联起来。5.3 性能优化与同步频率控制网络带宽是宝贵的。不加节制地同步所有数据很快就会导致延迟和卡顿。控制NetworkTransform的同步速率NetworkTransform组件有syncInterval属性默认0.1秒。对于移动缓慢或不重要的物体可以增大这个值如0.5秒。对于高速运动的子弹可能需要更小的值如0.05秒。使用[SyncVar]的hook进行脏检查SyncVar本身是脏检查机制值改变才同步。在hook里不要做昂贵操作。稀疏同步不要每帧都同步所有数据。例如玩家的生命值只有在变化时才需要同步。SyncVar已经做到了这点。对于自定义同步你可以自己实现一个计时器或条件判断。兴趣管理AOIMirror内置了简单的基于距离的兴趣管理。通过NetworkProximityChecker组件可以控制物体只在特定距离内同步给客户端。对于大型开放世界你可能需要实现更复杂的网格或分区兴趣管理。序列化优化如前所述使用自定义序列化来减少数据包大小。避免同步整个大的类只同步变化的部分。5.4 常见问题排查与调试技巧“RPC/Command找不到”或“序列化错误”最常见原因脚本编译后Mirror的Weaver没有成功运行。检查Console窗口是否有Weaver相关的错误或警告。解决确保脚本没有语法错误尝试在Unity中点击Assets - Open C# Project强制重新编译有时需要重启Unity。物体在客户端不显示/不同步检查预制体是否添加了NetworkIdentity组件。检查预制体是否被添加到NetworkManager的Spawnable Prefabs列表。确保服务器端确实调用了NetworkServer.Spawn()并且是在所有客户端都加载完场景之后。Host模式正常纯服务器模式出错这是最典型的权限问题。回顾第2.2节。仔细检查你的逻辑修改SyncVar的代码是否只在服务器执行用[Server]或isServer判断调用Command的代码是否只在本地玩家物体上执行用isLocalPlayer判断生成物体(Instantiate)后是否记得调用NetworkServer.Spawn()使用Mirror提供的NetworkServer.spawned和NetworkClient.spawned字典来调试查看物体是否在正确的端被生成和管理。连接失败/超时检查防火墙是否阻止了游戏端口默认7777。检查NetworkManager上的地址和端口是否正确。如果是远程连接确保服务器有公网IP或正确配置了端口转发/UDP打洞。考虑使用中继服务如Unity的Relay或Photon Fusion来解决NAT穿透问题。使用Mirror的调试工具在NetworkManager上启用Show Debug Messages可以在Console看到详细的网络日志。使用NetworkDiagnostics类可以在运行时收集和分析网络流量数据。6. 从开发到部署构建与架构考量当你完成开发后就需要考虑如何让玩家真正玩到你的游戏。6.1 构建独立服务器Headless Build对于纯Server/Client架构你需要一个不包含图形界面的、专门运行游戏逻辑的服务器程序以节省资源。在Unity中打开File - Build Settings。选择目标平台如WindowsLinux。勾选Server Build选项。这个选项会定义UNITY_SERVER编译符号并通常禁用音频、图形等不必要的模块。点击Build生成一个可执行文件如MyGameServer.exe。在命令行中运行这个服务器程序通常会看到它开始监听端口。服务器启动参数你可以在命令行传递参数给服务器比如指定端口MyGameServer.exe -port 8888。你需要在服务器的启动代码中如Start()方法里使用Application.dataPath和命令行参数解析库来读取这些配置。6.2 网络架构选择P2P、客户端托管、专用服务器P2P点对点Mirror的Host模式本质上是一种P2P其中一名玩家兼任主机。适合最多4-8人的小型、非竞技性游戏。最大问题是主机迁移和作弊。客户端托管Client-Hosted和P2P类似但可能使用更稳定的第三方托管服务如玩家租用的VPS来运行服务器程序。比纯P2P稳定但仍有中心节点托管者退出的问题。专用服务器Dedicated Server这是商业游戏的标配。你需要在云服务商如AWS、Google Cloud、Azure或游戏专用的DigitalOcean、Linode上租用虚拟机运行你的“Server Build”。你需要掌握基本的Linux/Windows服务器运维、Docker容器化、自动化部署和监控知识。成本最高但最稳定、最公平、最可扩展。6.3 安全考量浅谈网络游戏安全是一个深水区但有些基本原则必须遵守永远不要信任客户端所有关键数据生命值、分数、物品库存必须在服务器存储和验证。客户端只发送“意图”。验证输入服务器收到客户端的Command后要验证其合理性。例如玩家移动速度是否超限技能冷却时间是否已到发射子弹的位置是否合法防范常见攻击如速度黑客Speed Hacking、位置篡改、包重放攻击等。服务器端需要有相应的逻辑检测异常行为如连续收到过快的位置更新。使用权威服务器进行物理模拟对于有物理交互的游戏如赛车、球类尽量在服务器进行物理计算或至少进行结果验证。Unity的NetworkTransform组件对于高速运动物体是不够的你需要实现服务器权威的插值和外推。Mirror是一个强大的工具它为你铺平了道路但建造坚固的城堡仍需你亲手打下每一块砖。从理解Host和Server的根本区别开始严格遵循服务器权威的设计模式善用SyncVar、Command、RPC这三大利器并在性能和安全上多花心思。记住网络游戏的调试比单机游戏复杂数倍养成使用日志、断点和Mirror调试工具的习惯。先从一个小原型开始实现两个方块互相追逐射击把整个流程走通然后再逐步添加更复杂的逻辑。当你第一次成功让朋友通过互联网连接到你的游戏并一起玩耍时那种成就感是无与伦比的。