注:UNet 已经被废弃, 且未来会被Unity移除。在本文中,将会使用到 NetworkManager/High Level API,import方法:Window->Package Manager->Multiplayer HLAPI。
Networking
网络功能有两种类型的用户:
- 制作多玩家的用户。这类用户应该使用 NetworkManager/High Level API。
- 搭建网络基础设施或 advanced 多玩家游戏(Multiplayer game) 的用户。这类用户应该使用 NetworkTransport API。
High Level API
Unity网络有一个 “high-level”脚本API(HLAPI)。使用它可以满足你开发多人游戏的大部分需求,而不用去担心 “lower level”实现细节。HLAPI允许你:
- 使用 “Network Manager”控制游戏的网络状态。
- 操作 “client hosted”游戏,其中 host也是一个 player client。
- 使用通用的序列化器(serializer)序列化数据。
- 发送和接收网络信息。
- 从 clients发送网络命令到 servers。
- 从 servers到 clients进行远程过程调用(remote procedure calls,RPCs)。
- 从 servers发送联网事件到 clients。
引擎和编辑器集成(Engine and Editor integration)
Unity网络被集成到引擎和编辑器中,从而能让你使用组件和可视化工具来构建多人游戏。它提供了:
- 用于联网物体的 NetworkIdentity组件
- 用于联网脚本的 NetworkBehaviour。
- 物体 transforms的可配置自动同步。
- 脚本变量的自动同步。
- 在 Unity场景中支持放置联网物体。
- Network组件。
网络传输实时传输层
Unity 实时传输层(Real-Time Transport Layer)提供了:
- 基于UDP协议的优化。
- 多通道设计以避免 队首阻塞(head-of-line blocking)问题。
- 每个通道都支持各种级别的 Quality of Service (QoS)。
构建多玩家项目
接下来介绍构建多玩家项目的最基础且常见的事项。项目中需要:
- Network Manager
- 用户接口(玩家可以找到并加入游戏)
- 联网玩家 Prefabs
- 多玩家感知的 脚本和 GameObjects
对于不同的游戏,这个列表会有一些变化。比如说,在多人象棋游戏,或实施策略游戏中,你不需要代表玩家的可视化 GameObject。然而,你可能始终需要一个不可见的空 GameObject来表示玩家,并将其与脚本关联。
后面会简要介绍上面列出的每个条目。你需要理解以下两点重要概念并在构建游戏时做出适当的选择: - client、server和 host的关系。
- GameObject和动作的 authority概念。(The idea of authority over GameObjects and actions)
Network Manager
Network Manager负责管理你的多玩家游戏的网络方面的东西。在你的 Scene中同时应当只有一个激活的 Network Manager。
Unity内置的 Network Manager组件集中了所有用于管理多玩家游戏的特性。如果你有特殊需求,可以自己写一个专属 Network Manager,否则还是老老实实用这个组件。
用户接口
几乎每款多玩家游戏都要提供用于玩家发现,创建和加入游戏 “instances”(或“matches”)的方法。游戏的这部分通常被称为 “大厅(lobby)”,且有时还能在里面交谈。
Unity有一个最为基础的内置接口:NetworkManagerHUD。他在你构建游戏的早期极为有用,它能让你简单地构建matches并测试游戏。然而,由于它功能性和可视化设计过于基础,你应该在完成project前替换它。
联机玩家 GameObjects
大多数多玩家游戏都有玩家可以操纵的对象,比如说角色,汽车等等。一些多人游戏没有可见的“玩家对象”,取而代之地,玩家能控制许多单位或物品,实时战略游戏就是这样。有些甚至就没有特定的对象,比如说画布绘画游戏。在这些场景中,你通常需要创建一个 GameObject来表示玩家。将此 GameObject设置为 Prefab,并关联上所有控制玩家能做的操作的脚本。
如果你使用的是 Network Manager组件,将此 Prefab分配给 Player Prefab域。
当游戏运行时,Network Manager为每个连接到此match的玩家创建一份你的玩家 Prefab拷贝(“instance)。
然而,多人游戏编程新手很容易陷入疑惑————你需要确认你的玩家 Prefab实例上的脚本 “意识到”,玩家是在使用 host(管理游戏的计算机)还是 client(与host不同的计算机)控制此实例。
多玩家意识脚本(Multiplayer-aware Scripts)
为多玩家游戏写脚本与单玩家游戏是不同的。这是因为你需要考虑脚本运行的不同上下文。比如说,你添加在你的玩家 Prefab上的脚本应该允许该玩家实例的“owner”来操控它,而不是其他玩家。你需要考虑是否server或client有控制该脚本的权力(authority)。有时,你想让脚本同时运行在 server和 clients上,有时,你又只想让脚本运行在 server上,并且只希望 clients复制 GameObjects的移动方式。(比如说,在一个游戏中,玩家拾起可收集的 GameObject,脚本应该只在 server上运行,这样 server就能成为已收集 GameObjects数量的官方权威。
取决于你脚本的作用,你应该决定你脚本的哪些部分在哪些情形是可用的。
对于玩家 GameObject,每个人通常都有控制它们玩家实例的权限。这意味着每个 client都自己的 local authority,且 server接收 client告诉它的信息,比如说玩家正在做什么。
对于非玩家 GameObjects,server通常都有 authority,来知晓它们发送了什么(物品是否已经被收集),所有 clients接收 server发布的,关于此 GameObject的信息。
使用 Network Manager
Network Manager有以下特性:
- 游戏状态管理(Game state management)
- 产生管理(Spawn management)
- 场景管理(Scene management)
- Debug信息(Debugging information)
- Matchmaking
- 定制化(Customization)
开始使用 Network Manager
Network Manager是多人游戏地核心控制组件。首先先创建一个 empty GameObject,然后添加 NetworkManager组件:
注:你应该只有一个激活的 Network Manager。不要把 Network Manager组件放到一个联网的 GameObject(有 Network Identity组件),因为 Unity会在场景加载时 disable它们。
因为 Network Manager是基于 High-level API (HLAPI)实现的,所以它能做的你也可以使用脚本来实现。对于高级开发者,如果需要扩展 Network Manager组件的特性,可以在脚本中继承 NetworkManager类,并通过重载虚函数来自定义它的行为。
游戏状态管理
网络多人游戏有三种模式:client,专用server,或“Host”,“Host”即是client又是server。如果你正在使用 Network Manager HUD,它会自动告诉 Network Manager要以哪种模式开始,基于玩家选择的选项。如果你用的是自己的UI,那么你需要在你的代码中调用这些:
- NetworkManager.StartClient
- NetworkManager.StartServer
- NetworkManager.StartHost
网络地址和端口设置
无论游戏以哪种模式(client,server,host)开始,网络地址和端口属性都会被使用。在client模式,游戏会试图连接特定的地址和端口号。在server或host模式,游戏会监听特定端口号的输入连接。
在游戏开发过程中,设置一个固定的地址和端口号是很有用的。然而,你最后可能想要你的玩家能够选择他们想要连接的host。当你到达那个阶段后,Network Discovery组件能被用于广播并寻找局域网上的地址和端口号,且 Matchmaker服务能被用于为玩家寻找网络上可以连接的matches。
产生管理
使用 Network Manager来管理联网 GameObject的产生(联网实例化)。
大多数游戏都有代表玩家的 Prefab,因此 Network Manager有一个 Player Prefab槽。你应该将你的玩家 Prefab分配到此槽中。当你有了一个玩家Prefab设置,玩家 GameObject能为每个玩家使用该Prefab自动自动产生。你必须为 Player Prefab关联一个 Network Identity组件。
一旦你分配了一个玩家 Prefab,你可以以host身份开始游戏并看到玩家 GameObject产生。停止游戏会摧毁玩家 GameObject。如果你以client的身份运行游戏的另一份副本,并连接到localhost,Network Manager会产生另一个玩家 GameObect。
除了玩家 Prefab,你必须在 Network Manager为你想要动态产生的物体注册 Prefabs。你可以添加 Prefabs到Inspector中的 Registered Spawnable Prefabs标签中的列表。你也可以使用代码来注册,通过ClientScene.RegisterPrefab()方法。如果每个场景中都有单独的 Network Manager,你只需要注册Prefabs到与该场景相关的Network Manager中。
自定义玩家实例
Network Manager使用 NetworkManager.OnServerAddPlayer()的实现来产生玩家 GameObject。如果你想要自定义玩家 GameObject产生的方式,你可以重载此虚函数。它的默认实现如下:
public virtual void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
{
var player = (GameObject)GameObject.Instantiate(playerPrefab, playerSpawnPos, Quaternion.identity);
NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
}
注:如果你要实现自定义版本的 OnServerAddPlayer,NetworkServer.AddPlayerForConnection()方法必须呗新产生的玩家 GameObject调用,这样它就能被产生,并与client连接相关联。AddPlayerForConnection产生 GameObject,所以你不需要使用 NetworkServer.Spawn()。
起始位置
要控制玩家产生的位置,你可以使用 Network Start Position组件。要使用它,先关联 Network Start Position组件到场景中的一个 GameObect,并把此 GameObject放置到你希望玩家开始的地方。你可以在场景中添加许多起始点。Network Manager会检测场景中的起始位置,并在每个玩家实例产生时,它会使用其中一个位置和方向。
Network Manager有一个 Player Spawn Method属性:
- Random:随机产生
- Round Robin:循环产生
如果 Random和 Round Robin模式无法满足你,你也通过代码可以自定义起始位置是如何被选取的。你可以通过 NetworkManager.startPositions访问可用的 Network Start Position组件,并在 OnserverAddPlayer的实现中使用 GetStartPosition()方法,来找到一个起始位置。
场景管理
大多数游戏都不止一个场景。至少,除了游戏场景外,每个游戏都有标题场景和起始菜单场景。Network Manager被设计于可以自动管理游戏场景状态和场景切换。
NetworkManager Inspector中有两个槽:Offline Scene和 Online Scene。拖动场景assets到这些槽里来激活联网场景管理。
当server或host启动后,Online Scene会被加载。这会成为当前的网络场景,任何连接到该server的clients都会加载此场景。场景名字存储在 networkSceneName属性中。当网络中断后(停止server/host或client断开连接),Offline Scene会被加载。这能当多人游戏断开连接后,游戏能自动返回菜单场景。
你也可以在游戏运行时调用 NetworkManager.ServerChangeScene()来切换场景。这也可以令所有连接的clients切换场景,并更新 networkSceneName。当联网场景管理处于激活状态时,任何调用游戏状态管理函数,如NetworkManager.StartHost(), NetworkManager.StopClient()都会导致场景变化。通过设置场景并调用这些方法,你可以控制多人游戏的flow。
注意场景切换会导致前一个场景中的所有 GameObjects被摧毁。
你应该确保 NetworkManager在场景中始终存在,否则网络连接会在场景变化时断开。要做到这个,确保 Dont't Destroy On Load应该被选中。
定制化
NetworkManager类有许多虚函数,你可以在你的继承自 NetworkManager的类中自定义行为。在实现这些函数时,确保注意这些默认实现提供的功能性。比如说,在OnServerAddPlayer()中,NetworkServer.AddPlayer函数必须被调用,以激活连接中的玩家 GameObject。
使用 Network Manager HUD
你应该能够理解基础的网络概念,如host,server和client之间的关系。见 Network System Concepts。
Network Manager HUD组件如下所示:
属性 | 功能 |
---|---|
Show Runtime GUI | 点击此 checkbox来在运行时显示 Network Manager HUD GUI。这能让你在快速debug时显示或隐藏它。 |
GUI Horizontal Offset | 设置HUD的水平像素偏移,从屏幕左边缘开始测量。 |
GUI Vertical Offset | 设置HUD的垂直像素偏移,从屏幕顶部开始测量。 |
Network Manager HUD提供了基础功能,让玩你游戏的人能够掌管一个联网游戏,或找到并加入到已存在的联网游戏中。
使用 HUD
Network Manager HUD有两个基础模式:LAN(局域网)和 Matchmaker模式。LAN模式用于创建并加入局域网托管的游戏。Matchmaker模式用于通过互联网创建并寻找加入到游戏中。
Network Manager HUD开始于 LAN模式。要切换到 Matchmaker模式,点击 Enable Match Maker (M)按钮。
LAN HOST
点击 LAN Host按钮来以host身份在局域网上开始游戏。这个 client既是host又是游戏内的一个玩家。它使用来自 Network Info的信息来掌管游戏。
当你点击此按钮后, HUD切换到一个简单的网络细节展示。Stop(X)按钮能让你停止掌管游戏并返回到主 LAN菜单。当你以 host身份开始游戏时,其他玩家才能连接到此游戏。点击 Stop(X)后,连接到host的所有玩家都会断开连接。
LAN Client
要连接到局域网上的host,使用文本域来指定host地址。默认host地址为 “localhost”,这意味着 client会查看本机上的游戏。点击 LAN Client (C)来连接到你指定的host地址。
当你想要在处于同一个网络的多台机器上测试你的游戏时,你需要将host机器的地址填入文本框中。扮演host的机器需要告诉所有人他的IP地址。输入IP地址后点击 LAN Client来连接到host。当client连接时,HUD会展示一个 Cancel Connection Attempt按钮,你可以点击它来停止尝试连接。
如果连接成功,HUD会显示 Stop(X)按钮。点击它可以停止游戏并断开与host的连接:
LAN Server Only
点击 LAN Server Only来以 server身份开始游戏,但是不具有client身份。这类游戏称为 “专用服务器(dedicated server)”。用户不能在这个特殊实例上玩游戏。所有玩家必须以client身份连接。
局域网的专用服务器对于所有连接玩家来说会有更好的性能,因为服务器除了担任服务器之外,不需要处理本地玩家的 gameplay。
如果你想要 host一个能在互联网上玩的游戏,但是希望自己维护对服务器的控制,你可能会选择此选项。比如说,防止client作弊,因为只有 server有游戏的权限。
Enable Match Maker
点击 Enable Match Maker (M)来切换 HUD到 Matchmaker模式。如果你想要创建或连接互联网游戏上,你需要使用 Matchmaker模式。(这个模式具体参数以后再来填坑)
注:记住 Network Manager HUD 特性是开发时的临时帮助。它能让你快速运行多人游戏,但是在你准备好后,你应该把它替换成自己的UI控件。