• 产成对象
    • 约束
    • 对象创建流程
  • 玩家对象
  • 产生客户机权限的对象

    产成对象

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

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

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

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

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

    1. using UnityEngine;
    2. using UnityEngine.Networking;
    3. public class MyNetworkManager : MonoBehaviour
    4. {
    5. public GameObject alienPrefab;
    6. NetworkClient myClient;
    7. // Create a client and connect to the server port
    8. public void SetupClient()
    9. {
    10. ClientScene.RegisterPrefab(alienPrefab);
    11. myClient = new NetworkClient();
    12. myClient.RegisterHandler(MsgType.Connect, OnConnected);
    13. myClient.Connect("127.0.0.1", 4444);
    14. }
    15. }

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

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

    1. class Tree : NetworkBehaviour
    2. {
    3. [SyncVar]
    4. public int numLeaves;
    5. }
    6. class MySpawner : NetworkBehaviour
    7. {
    8. public GameObject treePrefab;
    9. public void Spawn()
    10. {
    11. GameObject tree = (GameObject)Instantiate(treePrefab, transform.position, transform.rotation);
    12. tree.GetComponent<Tree>().numLeaves = Random.Range(10,200);
    13. NetworkServer.Spawn(tree);
    14. }
    15. }

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

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

    约束

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

    对象创建流程

    创建的实际操作流程是:

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

    玩家对象

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

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

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

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

    产生客户机权限的对象

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

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

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

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

    1. [Command]
    2. void CmdSpawn()
    3. {
    4. var go = (GameObject)Instantiate(
    5. otherPrefab,
    6. transform.position + new Vector3(0,1,0),
    7. Quaternion.identity);
    8. NetworkServer.SpawnWithClientAuthority(go, connectionToClient);
    9. }

    ?