4.11 网络编程 (Networking) (Mirror, Photon) 4.11 网络编程 (Networking) (Mirror, Photon) 4.11.1 网络编程基础概念回顾 在深入 Mirror 和 Photon 之前,我们先简要回顾一些网络编程的基础概念,这些概念是理解后续内容的关键: 客户端-服务器 (Client-Server) 架构: 这是游戏网络中最常见的架构。服务器负责维护游戏世界的权威状态,处理逻辑运算,并同步状态给所有客户端。客户端则负责用户输入、渲染和与服务器进行通信。 P2P (Peer-to-Peer) 架构: 对等网络架构,每个玩家的设备既是客户端又是服务器。这种架构适用于玩家数量较少、延迟要求不高的场景,但管理和安全性较为复杂。
在深入 Mirror 和 Photon 之前,我们先简要回顾一些网络编程的基础概念,这些概念是理解后续内容的关键:
客户端-服务器 (Client-Server) 架构: 这是游戏网络中最常见的架构。服务器负责维护游戏世界的权威状态,处理逻辑运算,并同步状态给所有客户端。客户端则负责用户输入、渲染和与服务器进行通信。
P2P (Peer-to-Peer) 架构: 对等网络架构,每个玩家的设备既是客户端又是服务器。这种架构适用于玩家数量较少、延迟要求不高的场景,但管理和安全性较为复杂。
网络协议: 定义了数据在网络中传输的规则和格式。常见的协议包括 TCP (传输控制协议) 和 UDP (用户数据报协议)。
TCP: 面向连接的协议,提供可靠的、有序的数据传输,但速度相对较慢,开销较大。适用于对数据完整性要求高的场景,如玩家动作同步、交易等。
UDP: 无连接的协议,速度快,开销小,但数据传输不可靠,可能丢包或乱序。适用于对实时性要求高的场景,如玩家位置同步、射击游戏等。
序列化与反序列化: 将游戏对象的数据转换为字节流以便在网络上传输的过程称为序列化;反之,将接收到的字节流还原为游戏对象数据的过程称为反序列化。
延迟 (Latency) 与抖动 (Jitter): 延迟是指数据从发送端到接收端所需的时间;抖动是指延迟的变化幅度。网络延迟和抖动直接影响游戏的实时性和玩家体验。
状态同步 (State Synchronization) 与 确定性同步 (Deterministic Synchronization):
状态同步: 客户端将自身状态(如位置、速度等)发送给服务器,服务器再广播给其他客户端。适用于大多数类型的游戏,实现相对简单,但对带宽和延迟要求较高。
确定性同步: 客户端仅发送输入指令给服务器,服务器根据确定性的游戏逻辑模拟所有客户端的状态,并将结果同步回客户端。适用于 RTS、MOBA 等对同步精度要求极高的游戏,实现复杂,但带宽占用低。
Mirror 是一个为 Unity 打造的 高层 API (HLAPI) 网络框架。它旨在提供一个易于使用、灵活且可扩展的网络解决方案,特别适合中小型多人游戏项目。Mirror 的设计理念深受 Unity 官方的 UNet (旧版 Unity Networking) 框架的影响,并在其基础上进行了改进和优化。由于 UNet 已被 Unity 官方弃用,Mirror 成为了许多 Unity 开发者迁移和新项目网络开发的首选。
开源免费: Mirror 是完全开源且免费的,您可以自由地使用、修改和分发它。
易于上手: Mirror 提供了清晰简洁的 API 和完善的文档,即使是网络编程新手也能快速入门。
基于 HLAPI: Mirror 封装了底层的网络细节,开发者可以专注于游戏逻辑的实现,而无需深入复杂的 Socket 编程。
传输层可插拔: Mirror 的传输层是可插拔的,默认支持 Telepathy (基于 TCP) 和 KCP (基于 UDP 的可靠传输协议)。您可以根据项目需求选择合适的传输协议,甚至可以自定义传输层。
组件化架构: Mirror 采用组件化的架构,核心功能通过组件实现,例如 NetworkManager、NetworkIdentity、NetworkTransform 等。这种架构使得 Mirror 非常灵活,可以根据需要添加或移除组件。
社区活跃: Mirror 拥有活跃的社区支持,遇到问题可以方便地寻求帮助。
Mirror 的核心功能围绕以下几个关键组件展开:
NetworkManager: 网络管理器,是 Mirror 的核心组件,负责管理网络连接、场景加载、玩家生成等全局网络事件。
场景管理: NetworkManager 负责同步网络场景的加载和卸载,确保所有客户端加载相同的场景。
连接管理: 处理客户端的连接和断开连接事件。
玩家生成: 负责在客户端连接成功后生成玩家角色。
自定义事件: NetworkManager 提供了许多虚函数 (Virtual Functions),例如 OnServerConnect、OnClientDisconnect 等,允许您自定义网络事件的处理逻辑。
NetworkIdentity: 网络身份标识,每个需要在网络上同步的对象都必须附加 NetworkIdentity 组件。NetworkIdentity 组件负责对象的唯一标识、所有权管理和网络可见性。
服务器权威: NetworkIdentity 默认情况下是服务器权威的,这意味着只有服务器才能修改其附加对象的状态。
客户端预测: Mirror 支持客户端预测,可以在客户端本地预测玩家的操作,提高响应速度。
所有权: NetworkIdentity 可以管理对象的所有权,例如玩家控制的角色对象的所有权属于该玩家。
NetworkTransform: 网络变换组件,用于同步游戏对象的变换信息 (位置、旋转、缩放)。NetworkTransform 可以自动将对象的变换信息同步给所有客户端。
平滑同步: NetworkTransform 提供了平滑同步功能,可以减少网络抖动带来的视觉跳跃感。
压缩算法: NetworkTransform 支持数据压缩,可以减少网络带宽占用。
自定义同步间隔: 您可以自定义 NetworkTransform 的同步间隔,平衡同步精度和网络性能。
NetworkAnimator: 网络动画组件,用于同步 Animator 动画状态。NetworkAnimator 可以确保所有客户端上的动画状态保持一致。
参数同步: NetworkAnimator 可以同步 Animator 的参数,例如 Float、Integer、Boolean 等。
状态同步: NetworkAnimator 可以同步 Animator 的状态机状态。
NetworkBehaviour: 网络行为脚本基类,所有需要进行网络同步的自定义脚本都应该继承自 NetworkBehaviour。NetworkBehaviour 提供了用于网络通信的关键方法和属性,例如 Command、ClientRpc、SyncVar 等。
下面我们通过一个简单的代码示例,演示如何使用 Mirror 实现玩家移动同步。
1. 环境搭建:
创建一个新的 Unity 3D 项目。
从 Unity Asset Store 或 GitHub 下载并导入 Mirror 插件。
创建两个场景:ServerScene (服务器场景) 和 ClientScene (客户端场景)。
在 ClientScene 中创建一个空物体,并添加 NetworkManager 和 NetworkManagerHUD 组件。NetworkManagerHUD 组件提供了一个简单的 UI 界面用于启动服务器、连接客户端等操作。
创建一个 Player 预制体,包含一个 Cube 或 Capsule 作为角色模型,并添加 CharacterController 组件用于角色控制。
在 Player 预制体上添加 NetworkIdentity 和 NetworkTransform 组件。
创建一个新的 C# 脚本 PlayerController.cs,并添加到 Player 预制体上。
2. PlayerController.cs 脚本代码:
using UnityEngine; using Mirror; public class PlayerController : NetworkBehaviour { public float moveSpeed = 5f; private CharacterController controller; public override void OnStartLocalPlayer() { // 当该玩家是本地玩家时执行 controller = GetComponent<CharacterController>(); } void Update() { if (!isLocalPlayer) return; // 只处理本地玩家的输入 float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); Vector3 moveDirection = new Vector3(horizontal, 0f, vertical).normalized; Vector3 velocity = moveDirection * moveSpeed * Time.deltaTime; controller.Move(velocity); } }
3. 配置 NetworkManager:
在 NetworkManager 组件的 Inspector 面板中,将 Player Prefab 属性设置为我们创建的 Player 预制体。
将 ClientScene 和 ServerScene 添加到 NetworkManager 的 Network Scenes 列表中。
设置 NetworkManager 的传输协议,例如选择 Telepathy Transport (TCP)。
4. 运行测试:
构建并运行两个客户端程序。
在其中一个客户端程序中,点击 NetworkManagerHUD 上的 "Server Only" 或 "Host (Server + Client)" 按钮启动服务器。
在另一个客户端程序中,输入服务器的 IP 地址 (如果服务器和客户端不在同一台机器上) 或保持默认的 "localhost",然后点击 "Client" 按钮连接服务器。
连接成功后,您应该能在两个客户端程序中看到两个 Player 角色,并且在一个客户端中控制角色移动,另一个客户端也能实时看到同步的移动。
代码详解:
NetworkBehaviour:PlayerController 继承自 NetworkBehaviour,使其具备网络功能。
isLocalPlayer:isLocalPlayer 属性用于判断当前脚本是否运行在本地玩家的客户端上。我们只处理本地玩家的输入,避免其他客户端控制本地玩家的角色。
OnStartLocalPlayer():OnStartLocalPlayer() 是一个 Hook 函数,当该玩家角色在本地客户端生成并成为本地玩家的角色时会被调用。我们在这里获取 CharacterController 组件的引用。
NetworkTransform:NetworkTransform 组件会自动同步 Player 预制体的 Transform 信息,包括位置、旋转和缩放。因此,我们只需要在 PlayerController 中控制本地玩家角色的移动,NetworkTransform 会自动将移动信息同步到其他客户端。
Mermaid 图表:Mirror 玩家移动同步流程
Photon 是一个由 Exit Games 开发的商业级多人游戏网络解决方案。Photon 提供了多种产品,包括 Photon Unity Networking (PUN)、Photon Realtime、Photon Chat 和 Photon Fusion。其中 Photon PUN 是最流行的 Unity 网络库之一,以其易用性、稳定性和强大的功能而闻名。
易于使用: Photon PUN 提供了简洁的 API 和完善的文档,上手非常容易。
托管服务: Photon 提供云端托管服务 Photon Cloud,开发者无需自己搭建和维护服务器,极大地简化了服务器部署和运维工作。
跨平台支持: Photon 支持 Unity 支持的所有平台,包括 PC、移动设备、Web 等。
房间 (Room) 管理: Photon 提供了强大的房间管理功能,方便玩家进行匹配、组队和游戏会话管理。
RPC (远程过程调用): Photon 提供了便捷的 RPC 机制,用于客户端和服务器之间的通信。
可靠性和扩展性: Photon Cloud 具有高度的可靠性和扩展性,能够支持大规模的并发连接。
丰富的特性: Photon 提供了许多高级特性,例如兴趣组 (Interest Groups)、状态同步 (State Synchronization)、区域匹配 (Region Matching) 等。
Photon PUN 的核心概念包括:
PhotonNetwork: Photon 网络类,是 Photon PUN 的入口点,提供了连接 Photon Cloud、创建房间、加入房间、发送 RPC 等核心功能。
连接 Photon Cloud: 使用 PhotonNetwork.ConnectUsingSettings() 方法连接 Photon Cloud。需要提供 Photon App ID,App ID 是您在 Photon 官网注册应用后获得的唯一标识。
房间管理: 使用 PhotonNetwork.CreateRoom() 创建房间,PhotonNetwork.JoinRoom() 加入指定房间,PhotonNetwork.JoinOrCreateRoom() 加入或创建房间。
玩家管理: PhotonNetwork.LocalPlayer 获取本地玩家对象,PhotonNetwork.PlayerList 获取房间内所有玩家列表。
PhotonView: Photon 视图组件,类似于 Mirror 的 NetworkIdentity,每个需要在网络上同步的对象都需要附加 PhotonView 组件。PhotonView 负责对象的唯一标识、所有权管理和 RPC 调用。
View ID: PhotonView 自动分配唯一的 View ID,用于在网络上标识对象。
所有权: PhotonView 可以管理对象的所有权,使用 photonView.IsMine 判断是否是本地玩家拥有的对象。
RPC 调用: 使用 photonView.RPC() 方法调用远程过程调用 (RPC) 函数。
RPC (Remote Procedure Call): 远程过程调用,用于在网络上调用其他客户端或服务器上的函数。
[PunRPC] 属性: 使用 [PunRPC] 属性标记需要被远程调用的函数。
RPC 调用方法: 使用 photonView.RPC("FunctionName", RpcTarget.All, parameters) 调用 RPC 函数。 RpcTarget 参数指定 RPC 的目标客户端,例如 RpcTarget.All 表示广播给所有客户端,RpcTarget.Others 表示广播给其他客户端,RpcTarget.Server 表示发送给服务器。
房间 (Room): 游戏会话的容器,玩家在房间中进行游戏互动。
房间属性: 房间可以设置自定义属性,例如房间名称、游戏模式、最大玩家数量等。
房间事件: Photon 提供了房间事件回调,例如 OnJoinedRoom()、OnPlayerEnteredRoom()、OnPlayerLeftRoom() 等,用于处理房间相关的事件。
下面我们通过一个简单的代码示例,演示如何使用 Photon PUN 实现玩家移动同步。
1. 环境搭建:
创建一个新的 Unity 3D 项目。
从 Unity Asset Store 下载并导入 Photon PUN 插件。
在 Photon 官网注册账号并创建一个新的 Photon 应用,获取 App ID。
创建一个场景 GameScene。
在 GameScene 中创建一个空物体,并添加 PhotonServerSettings 组件。在 PhotonServerSettings 组件的 Inspector 面板中,输入您的 Photon App ID。
创建一个 Player 预制体,包含一个 Cube 或 Capsule 作为角色模型,并添加 CharacterController 组件用于角色控制。
在 Player 预制体上添加 PhotonView 和 PhotonTransformView 组件。PhotonTransformView 组件是 Photon PUN 提供的用于同步 Transform 信息的组件,类似于 Mirror 的 NetworkTransform。
创建一个新的 C# 脚本 PlayerControllerPUN.cs,并添加到 Player 预制体上。
2. PlayerControllerPUN.cs 脚本代码:
using UnityEngine; using Photon.Pun; public class PlayerControllerPUN : MonoBehaviourPun { public float moveSpeed = 5f; private CharacterController controller; void Start() { controller = GetComponent<CharacterController>(); if (photonView.IsMine) { // 当该玩家是本地玩家时执行 // 可以添加摄像机跟随等本地玩家特有的逻辑 } } void Update() { if (!photonView.IsMine) return; // 只处理本地玩家的输入 float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); Vector3 moveDirection = new Vector3(horizontal, 0f, vertical).normalized; Vector3 velocity = moveDirection * moveSpeed * Time.deltaTime; controller.Move(velocity); } }
3. 连接 Photon Cloud 和房间管理:
Launcher.cs,用于连接 Photon Cloud 和房间管理。添加到场景中的一个空物体上。using UnityEngine; using Photon.Pun; using Photon.Realtime; public class Launcher : MonoBehaviourPunCallbacks { string gameVersion = "1.0"; public GameObject controlPanel; public GameObject progressLabel; void Awake() { PhotonNetwork.AutomaticallySyncScene = true; // 自动同步场景 } void Start() { progressLabel.SetActive(true); controlPanel.SetActive(false); Connect(); } public override void OnConnectedToMaster() { Debug.Log("PUN Basics Tutorial/Launcher: OnConnectedToMaster() was called by PUN"); progressLabel.SetActive(false); controlPanel.SetActive(true); PhotonNetwork.JoinLobby(); // 加入默认大厅 } public override void OnDisconnected(DisconnectCause cause) { progressLabel.SetActive(false); controlPanel.SetActive(true); Debug.LogWarningFormat("PUN Basics Tutorial/Launcher: OnDisconnected() was called by PUN with reason {0}", cause); } public void Connect() { progressLabel.SetActive(true); controlPanel.SetActive(false); if (PhotonNetwork.IsConnected) { PhotonNetwork.JoinRandomRoom(); // 尝试加入随机房间 } else { PhotonNetwork.GameVersion = gameVersion; PhotonNetwork.ConnectUsingSettings(); // 连接 Photon Cloud } } public override void OnJoinRandomFailed(short returnCode, string message) { Debug.Log("PUN Basics Tutorial/Launcher:OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom"); PhotonNetwork.CreateRoom(null, new RoomOptions()); // 创建新房间 } public override void OnJoinedRoom() { Debug.Log("PUN Basics Tutorial/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room."); PhotonNetwork.LoadLevel("GameScene"); // 加载游戏场景 (确保 GameScene 在 Build Settings 中) } public override void OnJoinedLobby() { Debug.Log("Joined Lobby"); } }
4. 生成 Player 预制体:
GameScene 中创建一个空物体,并添加 PlayerManager.cs 脚本,负责在玩家加入房间后生成 Player 预制体。using UnityEngine; using Photon.Pun; public class PlayerManager : MonoBehaviourPunCallbacks { public GameObject playerPrefab; public Transform spawnPoint; public override void OnJoinedRoom() { base.OnJoinedRoom(); if (playerPrefab != null && spawnPoint != null) { PhotonNetwork.Instantiate(playerPrefab.name, spawnPoint.position, spawnPoint.rotation); } else { Debug.LogError("Player Prefab or Spawn Point is not assigned!"); } } }
在场景中创建一个 Spawn Point (空物体) 作为玩家的出生点,并将 Spawn Point 拖拽到 PlayerManager 组件的 Spawn Point 属性上。
将 Player 预制体拖拽到 PlayerManager 组件的 Player Prefab 属性上。
5. 配置 Build Settings:
将 Launcher 场景添加到 Build Settings 的 Scenes In Build 列表中,确保其索引为 0。
将 GameScene 场景添加到 Build Settings 的 Scenes In Build 列表中,确保其索引为 1。
6. 运行测试:
构建并运行两个客户端程序。
两个客户端程序都会自动连接 Photon Cloud 并尝试加入房间或创建房间。
连接成功后,两个客户端程序都会加载 GameScene 场景,并在场景中生成 Player 角色。在一个客户端中控制角色移动,另一个客户端也能实时看到同步的移动。
代码详解:
MonoBehaviourPunCallbacks:Launcher 继承自 MonoBehaviourPunCallbacks,可以接收 Photon PUN 的各种事件回调。
PhotonNetwork.ConnectUsingSettings():连接 Photon Cloud。
PhotonNetwork.JoinRandomRoom():尝试加入随机房间。
PhotonNetwork.CreateRoom():创建新房间。
PhotonNetwork.JoinLobby(): 加入默认大厅,可以用于房间列表获取等功能。
PhotonNetwork.LoadLevel():加载网络场景。
PhotonNetwork.Instantiate():在网络上实例化预制体,会在所有客户端上同步生成。
PhotonView.IsMine:photonView.IsMine 属性用于判断当前脚本是否运行在本地玩家拥有的对象上。
PhotonTransformView:PhotonTransformView 组件会自动同步 Player 预制体的 Transform 信息。
Mermaid 图表:Photon PUN 玩家移动同步流程
Mirror 和 Photon 都是优秀的 Unity 网络库,但它们在设计理念、功能特性和适用场景上存在一些差异。以下是一些关键的比较点,帮助您选择合适的网络库:
| 特性 | Mirror | Photon (PUN) |
|---|---|---|
| 许可 | 开源免费 (MIT License) | 商业授权,有免费版本和付费版本 |
| 服务器 | 自托管服务器 (Dedicated Server 或 Host) | 托管服务 (Photon Cloud) 或 自托管 (Photon Server SDK) |
| 易用性 | 相对易学,HLAPI,但需要一定网络编程基础 | 非常易用,API 简洁,快速上手 |
| 灵活性 | 高度灵活,可自定义传输层,开源可修改 | 灵活性稍低,但功能完善,满足大多数需求 |
| 扩展性 | 自托管服务器扩展性取决于服务器架构设计 | Photon Cloud 具有良好的扩展性,支持大规模并发 |
| 可靠性 | 自托管服务器可靠性取决于服务器运维 | Photon Cloud 具有高可靠性,专业团队维护 |
| 成本 | 服务器成本 (自托管服务器) | Photon Cloud 费用 (基于 CCU 或流量) 或 服务器成本 (自托管) |
| 适用场景 | 中小型项目,预算有限,需要高度定制化 | 中大型项目,需要快速开发,稳定可靠的托管服务 |
总结:
选择 Mirror 的理由:
预算有限,希望使用免费开源的网络库。
需要高度定制化,例如自定义传输协议、服务器逻辑等。
熟悉网络编程,愿意自己搭建和维护服务器。
项目规模较小,对服务器扩展性要求不高。
选择 Photon PUN 的理由:
追求快速开发,希望使用易用性高的网络库。
需要稳定可靠的托管服务,不想自己搭建和维护服务器。
项目需要跨平台支持,并具有一定的扩展性需求。
预算充足,可以承担 Photon Cloud 的费用。
本章节深入探讨了 Unity 中的网络编程,重点介绍了 Mirror 和 Photon 这两个流行的网络库。我们学习了网络编程的基础概念,详细讲解了 Mirror 和 Photon 的核心组件和使用方法,并通过代码实践演示了玩家移动同步的实现。最后,我们对 Mirror 和 Photon 进行了比较,帮助您根据项目需求选择合适的网络库。
网络编程是一个复杂而充满挑战的领域。Mirror 和 Photon 为 Unity 开发者提供了强大的工具,简化了多人游戏开发流程。掌握 Mirror 和 Photon,将为您打开多人游戏开发的大门,创造更多精彩的游戏体验。
未来,网络技术将持续发展,云游戏、边缘计算、5G 等新技术将为游戏网络带来新的机遇和挑战。作为游戏开发者,我们需要不断学习和探索,掌握最新的网络技术,才能在未来的游戏开发领域保持竞争力。