Version: 2021.2
设置多人游戏项目
使用 Network Manager HUD

使用 Network Manager

Important: UNet is a deprecated solution, and a new Multiplayer and Networking Solution (Netcode for GameObjects) is under development. For more information and next steps see the information on the Unity Netcode for GameObjects website.

Network Manager 是用于管理多人游戏的网络方面的组件。

Network Manager 功能包括:

  • 游戏状态管理
  • 生成管理
  • 场景管理
  • 调试信息
  • 配对
  • 自定义

开始使用 Network Manager

Network Manager 是多人游戏的核心控制组件。首先,在起始场景中创建一个空游戏对象,然后添加 NetworkManager 组件。新添加的 Network Manager 组件如下所示:

Inspector 窗口中显示的 Network Manager
Inspector 窗口中显示的 Network Manager

Editor 中的 Network Manager Inspector 可用于配置和控制与网络相关的许多设置。

注意:每个场景中只应有一个激活的 Network Manager。不要将 Network Manager 组件放在联网游戏对象(具有 Network Identity 组件的游戏对象)上,因为 Unity 会在加载场景时禁用这些组件。

如果您已经熟悉多人游戏开发,可能会发现完全使用高级 API (HLAPI) 实现 Network Manager 组件很有用,因为这样就能通过脚本执行该组件的全部功能。如果您是高级用户,想要扩展 Network Manager 组件的功能,则可以使用脚本从 NetworkManager 派生自己的类,并通过覆盖其提供的任何虚拟函数挂钩来自定义其行为。但是,Network Manager 组件将许多有用的功能整合到一个地方,并使创建、运行和调试多人游戏的过程尽可能简单。

游戏状态管理

网络多人游戏可在三种模式下运行,即作为客户端、作为专用服务器或作为“主机”(同时充当客户端和服务器)。

如果使用 Network Manager HUD 组件,该组件会根据玩家选择的选项自动告知 Network Manager 启动哪种模式。如果要自己编写允许玩家启动游戏的 UI,必须使用自己的代码调用这些 UI。这些方法是:

Network Manager 组件中的网络地址和端口设置
Network Manager 组件中的网络地址和端口设置

无论游戏以何种模式(客户端、服务器还是主机)启动,都会使用 Network AddressNetwork Port 属性。在客户端模式下,游戏尝试连接到指定的地址和端口。在服务器或主机模式下,游戏会监听指定端口上的传入连接。

在开发游戏期间,为这些属性指定固定地址和端口设置会很有用。但是,最终您可能希望玩家能够选择想要连接的主机。到达该阶段时,可使用 Network Discovery 组件(请参阅本地发现)在局域网 (LAN) 上广播和查找地址和端口,并可为玩家使用 Matchmaker 服务来查找要连接到的互联网比赛(请参阅 Multiplayer 服务)。

生成管理

使用 Network Manager 可管理基于预制件的联网游戏对象生成(联网实例化)。

Network Manager 组件的Spawn Info部分
Network Manager 组件的“Spawn Info”部分

大多数游戏都有一个代表玩家的预制件,因此 Network Manager 有一个 Player Prefab 字段。应为此字段分配玩家预制件。设置玩家预制件后,该预制件将用于为游戏中的每个用户自动生成玩家游戏对象。该机制适用于托管服务器上的本地玩家和远程客户端上的远程玩家。必须将 Network Identity 组件附加到玩家预制件。

分配玩家预制件后,即可作为主机开始游戏,并看到玩家游戏对象生成。停止游戏会销毁玩家游戏对象。如果构建并运行另一个游戏副本并将其作为客户端连接到 localhost,则 Network Manager 会使另一个玩家游戏对象出现。停止该客户端时会销毁该玩家的游戏对象。

除了玩家预制件之外,还必须向 Network Manager 注册要在游戏过程中动态生成的其他预制件。

可将预制件添加到 Inspector 中标记为 Registered Spawnable Prefabs 的列表。还可使用 ClientScene.RegisterPrefab() 方法通过代码来注册预制件。

如果只有一个 Network Manager,则需要向其注册可能在任何场景中生成的所有预制件。如果每个场景中都有单独的 Network Manager,则只需注册与该场景相关的预制件。

自定义玩家实例化

Network Manager 使用其 NetworkManager.OnServerAddPlayer() 实现来生成玩家游戏对象。如果要自定义玩家游戏对象的创建方式,可重写该虚拟函数。以下代码显示了默认实现的示例:

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() 方法,以便生成该游戏对象并与客户端的连接关联。AddPlayerForConnection 会生成游戏对象,因此无需使用 NetworkServer.Spawn()

开始位置

要控制玩家的生成位置,可使用 Network Start Position 组件。要使用 Network Start Position 组件,请将其附加到场景中的游戏对象,并将该游戏对象放置在希望某一个玩家开始的位置。可根据需要为场景添加任意数量的起始位置。Network Manager 会检测场景中的所有起始位置,并在生成每个玩家实例时使用其中一个位置和方向。

Network Manager 具有 Player Spawn Method 属性,用于配置如何选择起始位置。

  • 选择 Random 可根据随机选择的 startPosition 选项生成玩家。

  • 选择 Round Robin 可循环使用预设列表中的 startPosition 选项。

如果 Random 或 Round Robin 模式不符合游戏需求,可使用代码自定义如何选择起始位置。可通过 NetworkManager.startPositions 列表访问可用的 Network Start Position 组件,并可使用 Network Manager 上的 helper 方法 GetStartPosition(),该方法可用于实现 OnServerAddPlayer 以便查找起始位置。

场景管理

大多数游戏都有多个场景。至少,除了实际进行游戏的场景之外,通常还有标题屏幕或开始菜单场景。Network Manager 旨在以适合多人游戏的方式自动管理场景状态和场景转换。

NetworkManager Inspector 上有两个用于场景的字段:Offline Scene 和 Online Scene。将场景资源拖动到这些字段中可激活联网场景管理。

启动服务器或主机时将加载联机场景 (Online Scene)。然后,此场景将成为当前的网络场景。连接到该服务器的所有客户端都被要求也加载此场景。此场景的名称存储在 networkSceneName 属性中。

网络停止(通过停止服务器或主机或者通过断开客户端连接)时将加载脱机场景 (Offline Scene)。因此,游戏在与多人游戏断开连接时会自动返回菜单场景。

还可以通过调用 NetworkManager.ServerChangeScene() 在游戏处于活动状态时更改场景。这样也会使所有当前连接的客户端改变场景,并更新 networkSceneName,让新客户端也能加载新场景。

联网场景管理处于激活状态时,对游戏状态管理函数(如 NetworkManager.StartHost()NetworkManager.StopClient())的任何调用都可能导致场景改变。此规则适用于运行时控件 UI。通过设置场景并调用这些方法,可控制多人游戏的流程。

请注意,场景改变会导致前一个场景中的所有游戏对象都被销毁。

通常应确保 Network Manager 在场景之间保持不变,否则在场景改变时网络连接会中断。为此,请确保在 Inspector 内选中 Don’t Destroy On Load 复选框。但是,也可在每个场景中使用具有不同设置的单独 Network Manager,如果希望控制增量预制件加载或不同的场景转换,可能会很有用。

自定义

NetworkManager 类上有一些可自定义的虚拟函数;您可以通过自行创建继承自 NetworkManager 的派生类来自定义这些函数。实现这些函数时,请务必注意默认实现所提供的功能。例如,在 OnServerAddPlayer() 中,必须调用函数 NetworkServer.AddPlayer 来激活要连接的玩家游戏对象。

以下是主机/服务器和客户端可能发生的所有回调,在某些情况下,调用基类函数来维持默认行为很重要。要查看实现本身,可在 Bitbucket Networking 代码仓库中进行查看。

using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Networking.Match;

public class CustomManager : NetworkManager {
    // 服务器回调
    public override void OnServerConnect(NetworkConnection conn) {
        Debug.Log("A client connected to the server: " + conn);
    }

    public override void OnServerDisconnect(NetworkConnection conn) {
        NetworkServer.DestroyPlayersForConnection(conn);
        if (conn.lastError != NetworkError.Ok) {
            if (LogFilter.logError) { Debug.LogError("ServerDisconnected due to error: " + conn.lastError); }
        }
        Debug.Log("A client disconnected from the server: " + conn);
    }
    public override void OnServerReady(NetworkConnection conn) {
        NetworkServer.SetClientReady(conn);
        Debug.Log("Client is set to the ready state (ready to receive state updates): " + conn);
    }
    public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId) {
        var player = (GameObject)GameObject.Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
        NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
        Debug.Log("Client has requested to get his player added to the game");
    }

    public override void OnServerRemovePlayer(NetworkConnection conn, PlayerController player) {
        if (player.gameObject != null)
            NetworkServer.Destroy(player.gameObject);
    }

    public override void OnServerError(NetworkConnection conn, int errorCode) {
        Debug.Log("Server network error occurred: " + (NetworkError)errorCode);
    }

    public override void OnStartHost() {
        Debug.Log("Host has started");
    }

    public override void OnStartServer() {
        Debug.Log("Server has started");
    }

    public override void OnStopServer() {
        Debug.Log("Server has stopped");
    }

    public override void OnStopHost() {
        Debug.Log("Host has stopped");
    }

    // 客户端回调
    public override void OnClientConnect(NetworkConnection conn)
    {
        base.OnClientConnect(conn);
        Debug.Log("Connected successfully to server, now to set up other stuff for the client...");
    }

    public override void OnClientDisconnect(NetworkConnection conn) {
        StopClient();
        if (conn.lastError != NetworkError.Ok)
        {
            if (LogFilter.logError) { Debug.LogError("ClientDisconnected due to error: " + conn.lastError); }
        }
        Debug.Log("Client disconnected from server: " + conn);
    }

    public override void OnClientError(NetworkConnection conn, int errorCode) {
        Debug.Log("Client network error occurred: " + (NetworkError)errorCode);
    }

    public override void OnClientNotReady(NetworkConnection conn) {
        Debug.Log("Server has set client to be not-ready (stop getting state updates)");
    }

    public override void OnStartClient(NetworkClient client) {
        Debug.Log("Client has started");
    }

    public override void OnStopClient() {
        Debug.Log("Client has stopped");
    }

    public override void OnClientSceneChanged(NetworkConnection conn) {
        base.OnClientSceneChanged(conn);
        Debug.Log("Server triggered scene change and we've done the same, do any extra work here for the client...");
    }
}

NetworkManager Inspector 提供了更改某些连接参数和超时的功能。有些参数未在此处公开,但可以通过代码更改这些参数。

using UnityEngine;
using UnityEngine.Networking;

public class CustomManager : NetworkManager {
    // 尽早设置自定义连接参数,确保不会太晚实施这些参数
    void Start()
    {
        customConfig = true;
        connectionConfig.MaxCombinedReliableMessageCount = 40;
        connectionConfig.MaxCombinedReliableMessageSize = 800;
        connectionConfig.MaxSentMessageQueueSize = 2048;
        connectionConfig.IsAcksLong = true;
        globalConfig.ThreadAwakeTimeout = 1;
    }
}
设置多人游戏项目
使用 Network Manager HUD