产成对象

在Unity中,使用Instantiate()创建新的游戏对象有时被称为“spawn”。在网络HLAPI中,“spawn”一词用于表示更具体的内容。在网络HLAPI的服务器权威模型中,为了“产生”服务器上的对象,意味着应该在连接到服务器的客户端上创建对象,并且该对象将由Spawn系统管理。一旦进入Spawn系统,当服务器上的对象发生更改时,状态更新就会发送给客户端,并且当客户端在服务器上销毁时,客户端将被销毁。产生的对象也被添加到服务器正在管理的网络对象集中,这样如果其他客户端稍后加入游戏,对象也将在该客户端上产生。这些对象有一个称为“netId”的唯一网络实例标识,它在每个对象的服务器和客户端上都是相同的。这用于将消息路由到对象并识别对象。

NetworkIdentity对象在客户端上产生时,它们将使用服务器上对象的当前状态创建。这适用于对象的变换,移动状态和同步变量。因此,客户端对象在创建时始终处于最新状态。这样可以避免出现问题,例如对象在错误的初始位置产生,然后在状态更新数据包到达时弹出到正确的位置。

这听起来不错,但有一些即时问题会引发。在客户端上创建的对象如何?而且,如果对象在产生时间和另一个客户端连接之间发生变化,该怎么办?那么为新客户端生成哪个版本的对象?

产生客户端上的对象的实例化来自传递给服务器上的NetworkServer.Spawn的对象的预制件中的客户端对象。NetworkIdentity检查器预览面板显示NetworkIdentityassetID,正是此值标识预制,以便客户端可以创建对象。为了高效工作,客户必须执行注册步骤; 他们必须调用ClientScene.RegisterPrefab来告诉系统有关将从中创建客户端对象的资产。

编辑器中的NetworkManager可以很方便地完成spawn预制件的注册。NetworkManager的“spawn信息”部分允许您注册预制而无需编写任何代码。当创建NetworkClient时,这也可以通过代码完成。要在代码中完成它:

    using UnityEngine;
    using UnityEngine.Networking;

    public class MyNetworkManager : MonoBehaviour 
    {
        public GameObject alienPrefab;

        NetworkClient myClient;

        // Create a client and connect to the server port
        public void SetupClient()
        {
            ClientScene.RegisterPrefab(alienPrefab);

            myClient = new NetworkClient();

            myClient.RegisterHandler(MsgType.Connect, OnConnected);
            myClient.Connect("127.0.0.1", 4444);
        }
    }

在这个例子中,用户会将预制资产拖到MyNetworkManager脚本的alienPrefab插槽中。所以当在服务器上产生一个外来对象时,将在客户端上创建相同类型的对象。这种资产注册可确保资产与场景一起加载,从而在创建资产时不会出现摊档。对于更高级的用例,如对象池或动态创建的资产,有ClientScene.RegisterSpawnHandler,它允许为客户端生成注册回调函数。

下面是一个用随机数叶子创建树的spawner的简单示例。

    class Tree : NetworkBehaviour
    {
        [SyncVar]
        public int numLeaves;
    }

    class MySpawner : NetworkBehaviour
    {
        public GameObject treePrefab;

        public void Spawn()
        {
            GameObject tree = (GameObject)Instantiate(treePrefab, transform.position, transform.rotation);
            tree.GetComponent<Tree>().numLeaves = Random.Range(10,200);
            NetworkServer.Spawn(tree);
        }
    }

为了完成这个例子,该项目将为具有树脚本和NetworkIdentity组件的树提供预制资源。然后在场景中的MySpawner实例上,treePrefab插槽将由树预制资源填充。此外,树预制必须注册为一个可生成的对象 - 使用NetworkManager UI或在代码中使用ClientScene.RegisterPrefab()

当此代码运行时,客户端上创建的树对象将具有来自服务器的numLeaves的正确值。

约束

    • NetworkIdentity必须位于可生成预制件的根游戏对象上
    • NetworkBehaviour脚本必须与NetworkIdentity位于同一个游戏对象上,而不是子游戏对象
    • 预制件不能在NetworkManager中注册,除非它们的根对象上有NetworkIdentity

对象创建流程

创建的实际操作流程是:

    • NetworkIdentity组件的预制注册为spawn
    • GameObject是从服务器上的预制实例化的
    • 游戏代码在实例上设置初始值(请注意,此处应用的3D物理力不会立即生效)
    • NetworkServer.Spawn()与实例一起被调用
    • 通过调用NetworkBehaviour组件上的OnSerialize()来收集服务器上实例上SyncVars的状态
    • 将类型为MsgType.ObjectSpawn的网络消息发送到包含SyncVar数据的连接客户端
    • 在服务器上的实例上调用OnStartServer(),并将isServer设置为true
    • 客户端收到ObjectSpawn消息并从注册的预制件创建一个新实例
    • SyncVar数据通过调用NetworkBehaviour组件上的OnDeserialize()应用于客户端上的新实例
    • 在每个客户端上的实例上调用OnStartClient(),并将isClient设置为true
    • 随着游戏进行,对SyncVar值的更改会自动同步到客户端。这一直持续到游戏结束。
    • NetworkServer.Destroy()在服务器上的实例上被调用
    • 一个类型为MsgType ObjectDestroy的网络消息被发送给客户端
    • OnNetworkDestroy()在客户端上的实例上调用,然后实例被销毁。

玩家对象

网络HLAPI中的玩家对象在某些方面是特殊的。使用NetworkManager生成玩家对象的流程是:

    • NetworkIdentity预制注册为PlayerPrefab
    • 客户端连接到服务器
    • 客户端调用AddPlayer(),类型MsgType.AddPlayer的网络消息被发送到服务器
    • 服务器接收消息并调用NetworkManager.OnServerAddPlayer()
    • GameObject从服务器上的PlayerPrefab实例化
    • 使用服务器上的新玩家实例调用NetworkManager.AddPlayerForConnection()
    • 玩家实例生成 - 您不必为玩家实例调用NetworkServer.Spawn()
    • 一个类型为MsgType.Owner的网络消息被发送到添加了玩家的客户端(仅限该客户端!)
    • 原始客户端收到网络消息
    • 在原始客户端的玩家实例上调用OnStartLocalPlayer(),并将isLocalPlayer设置为true

请注意,OnStartLocalPlayer()OnStartClient()之后被调用,因为它只会在玩家对象产生后所有权消息从服务器到达时发生。所以isLocalPlayer不会在OnStartClient()中设置。

由于OnStartLocalPlayer仅针对您的玩家进行调用,因此它是执行初始化的好地方,只应该为本地玩家完成。这可能包括启用输入处理,并启用播放器对象的摄像头跟踪。通常只有本地玩家才有活动的相机。

产生客户机权限的对象

可以生成对象并将对象的权限分配给特定的客户端。这是通过NetworkServer.SpawnWithClientAuthority完成的,它将客户端的NetworkConnection作为参数进行授权。

对于这些对象,拥有权限的客户端的属性hasAuthority将为true,并且将在具有权限的客户端上调用OnStartAuthority()。该客户端将能够为该对象发出命令。在其他客户端(和主机上),hasAuthority将是false

使用客户端权限生成的对象必须在其NetworkIdentity中设置LocalPlayerAuthority

例如,为了让玩家产生并控制一个物体:

    [Command]
    void CmdSpawn()
    {
        var go = (GameObject)Instantiate(
           otherPrefab, 
           transform.position + new Vector3(0,1,0), 
           Quaternion.identity);

        NetworkServer.SpawnWithClientAuthority(go, connectionToClient);
    }

🔚