[Unity Mirror] 生成GameObject

81 篇文章 31 订阅

  在Unity中,你通常用Instantiate来 “spawn”(也就是创建)新的游戏对象。然而,在Mirror中,"spawn "这个词的意思更明确。在Mirror的服务器授权模型中,在服务器上 "spawn "一个游戏对象意味着这个游戏对象是在连接到服务器的客户端上创建的,并由生成系统管理。

  一旦游戏对象使用这个系统生成,只要服务器上的游戏对象发生变化,就会向客户端发送状态更新。当Mirror在服务器上销毁游戏对象时,它也会在客户端上销毁它。服务器将生成的游戏对象与所有其他联网的游戏对象一起管理,因此,如果另一个client后来加入游戏,服务器可以在该client上生成游戏对象。这些生成的游戏对象有一个独特的网络实例ID,称为 “netId”,在服务器和客户端的每个游戏对象都是一样的。这个唯一的网络实例ID被用来将网络上的信息传递给游戏对象,并识别游戏对象。

  当服务器生成一个具有网络身份组件的游戏对象时,在客户端生成的游戏对象具有相同的 “状态”。这意味着它与服务器上的游戏对象是相同的;它有相同的Transform、运动状态和(如果使用网络变换和SyncVars)同步的变量。因此,客户端游戏对象在Mirror创建时总是最新的。这就避免了诸如游戏对象在错误的初始位置生成,然后在状态更新到来时重新出现在正确的位置等问题。

  一个游戏对象Prefab在试图向Network Manager注册之前,必须有一个Network Identity组件。

  要在编辑器中向Network Manager注册Prefab,选择Network Manager游戏对象,在Inspector中,导航到Network Manager组件。点击再生信息旁边的三角形来打开设置,然后在已注册的可再生预制件下,点击加号(+)按钮。将Prefabs拖入空栏,将其分配到列表中。

在没有Network Manager的情况下生成

  对于更高级的用户,你可能会发现你想注册Prefabs和生成游戏对象而不使用Network Manager组件。

  要在不使用Network Manager的情况下生成游戏对象,你可以通过脚本自己处理Prefab的注册。使用NetworkClient.RegisterPrefab方法向Network Manager注册Prefabs。

using UnityEngine;
using Mirror;

public class MyNetworkManager : MonoBehaviour 
{
    public GameObject treePrefab;

    // Register prefab and connect to the server  
    public void ClientConnect()
    {
        NetworkClient.RegisterPrefab(treePrefab);
        NetworkClient.RegisterHandler<ConnectMessage>(OnClientConnect);
        NetworkClient.Connect("localhost");
    }

    void OnClientConnect(ConnectMessage msg)
    {
        Debug.Log("Connected to server: " + conn);
    }

  在这个例子中,你创建了一个空的游戏对象作为NetworkManager,然后创建并将MyNetworkManager脚本(如上)附加到该游戏对象上。创建一个prefab,该prefab上有一个 "Network Identity "组件,并将其拖到检查器中MyNetworkManager组件的treePrefab插槽上。这可以确保当服务器生成树形游戏对象时,它也会在客户端创建相同类型的游戏对象。

  注册prefab可以确保在创建资产时没有停顿或加载时间。

  为了使该脚本工作,你还需要为服务器添加代码。将此添加到MyNetworkManager脚本中。

public void ServerListen()
{
    NetworkServer.RegisterHandler<ConnectMessage>(OnServerConnect);
    NetworkServer.RegisterHandler<ReadyMessage>(OnClientReady);

    // start listening, and allow up to 4 connections
    NetworkServer.Listen(4);
}

// When client is ready spawn a few trees  
void OnClientReady(NetworkConnection conn, ReadyMessage msg)
{
    Debug.Log("Client is ready to start: " + conn);
    NetworkServer.SetClientReady(conn);
    SpawnTrees();
}

void SpawnTrees()
{
    int x = 0;
    for (int i = 0; i < 5; ++i)
    {
        GameObject treeGo = Instantiate(treePrefab, new Vector3(x++, 0, 0), Quaternion.identity);
        NetworkServer.Spawn(treeGo);
    }
}

void OnServerConnect(NetworkConnection conn, ConnectMessage msg)
{
    Debug.Log("New client connected: " + conn);
}

  服务器不需要注册任何东西,因为它知道被生成的是什么游戏对象(asset ID在生成消息中被发送)。客户端需要能够查找游戏对象,所以它必须在客户端注册。

  在编写你自己的network manager时,重要的是在调用服务器上的spawn命令之前让客户端准备好接收状态更新,否则它们将不会被发送。如果你使用Mirror的内置network manager组件,这将自动发生。

  对于更高级的使用,如对象池或动态创建的资产,你可以使用NetworkClient.RegisterSpawnHandler方法,它允许回调函数被注册为客户端的spawn。请参阅关于Custom Spawn Functions的文档,了解这方面的一个例子。

  如果游戏对象有一个网络状态,比如同步变量,那么这个状态就会与spawn message 同步。在下面的例子中,这个脚本被附在树形Prefab上。

using UnityEngine;
using Mirror;

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

    public override void OnStartClient()
    {
        Debug.Log("Tree spawned with leaf count " + numLeaves);
    }
}

  通过附加的这个脚本,你可以改变numLeaves变量并修改SpawnTrees函数,以看到它准确地反映在客户端上。

void SpawnTrees()
{
    int x = 0;
    for (int i = 0; i < 5; ++i)
    {
        GameObject treeGo = Instantiate(treePrefab, new Vector3(x++, 0, 0), Quaternion.identity);
        Tree tree = treeGo.GetComponent<Tree>();
        tree.numLeaves = Random.Range(10,200);
        Debug.Log("Spawning leaf with leaf count " + tree.numLeaves);
        NetworkServer.Spawn(treeGo);
    }
}

  将Tree脚本附加到先前创建的tree Prefab脚本上,以查看其实际效果。

限制
  • NetworkIdentity 必须位于可生成 Prefab 的根游戏对象上。没有这个,Network Manager无法注册预制件。
  • NetworkBehaviour 脚本必须与 NetworkIdentity 在同一个游戏对象上,而不是在子游戏对象上。

GameObject 创建流程

生成游戏对象的内部操作的实际流程是:

  • 带有网络身份组件的预制件被注册为spawnable。
  • 游戏对象从服务器上的 Prefab 实例化。
  • 游戏代码在实例上设置初始值(注意,在此应用的3D物理力不会立即生效)。
  • NetworkServer.Spawn 与实例一起调用。
  • 服务器上的实例的SyncVars状态是通过调用[Network Behaviour]组件的OnSerialize来收集的。
  • 一个ObjectSpawn类型的网络消息被发送到连接的客户端,其中包括SyncVar数据。
  • 在服务器上的实例上调用OnStartServer,并且isServer被设置为true。
  • 客户端收到ObjectSpawn消息,并从注册的Prefab中创建一个新的实例。
  • 通过调用网络行为组件的OnDeserialize,SyncVar数据被应用到客户端的新实例中。
  • OnStartClient在每个客户端的实例上被调用,并且isClient被设置为true。
  • 随着游戏的进行,SyncVar值的变化会自动同步到客户端。这种情况一直持续到游戏结束。
  • NetworkServer.Destroy在服务器上的实例上被调用。
  • OnStopServer 在服务器上的实例上调用
  • ObjectDestroy 类型的网络消息被发送到客户端。
  • OnStopClient 在客户端上的实例上调用
  • 实例被销毁(如果它是场景对象,则被disabled)

Player Game Objects

  HLAPI中的玩家游戏对象与非玩家游戏对象的工作方式略有不同。用Network Manager生成玩家游戏对象的流程是:

  • 具有 NetworkIdentity 的 Prefab 注册为 PlayerPrefab
  • 客户端连接到服务器
  • 客户端调用AddPlayer,MsgType.AddPlayer类型的网络信息被发送至服务器
  • 服务器接收消息并调用 NetworkManager.OnServerAddPlayer
  • 游戏对象从服务器上的 Player Prefab 实例化
  • 使用服务器上的新 player 实例调用 NetworkManager.AddPlayerForConnection
  • 玩家实例被生成 – 你不需要为玩家实例调用NetworkServer.Spawn。spawn信息会像普通的spawn一样发送给所有客户端。
  • Owner 类型的 network message 被发送到添加玩家的客户端(仅限该客户端!)
  • 原客户端收到 network message
  • OnStartLocalPlayer 在原客户端的player实例上调用,isLocalPlayer 设置为 true。

  注意 OnStartLocalPlayer 是在 OnStartClient 之后调用的,因为它只发生在玩家游戏对象生成后所有权消息从服务器到达时,所以在 OnStartClient 中没有设置 isLocalPlayer。

  因为OnStartLocalPlayer只为客户端的本地玩家游戏对象调用,它是一个很好的地方,可以执行只为本地玩家做的初始化。这可能包括启用输入处理,以及启用玩家游戏对象的相机跟踪。

  要生成游戏对象并将这些游戏对象的权限分配给特定客户端,请使用 NetworkServer.Spawn,它以要成为权限的客户端的 NetworkConnection 作为参数。

  对于这些游戏对象,在有权限的客户端上属性hasAuthority为true,在有权限的客户端上调用OnStartAuthority。该客户端可以为该游戏对象发出命令。在其他客户端(和Host)上,hasAuthority 为 false

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

  例如,上面的tree spawn的例子可以被修改,让树拥有客户端的权限,就像这样(注意,我们现在需要为拥有客户端的连接传入一个NetworkConnection游戏对象)。

void SpawnTrees(NetworkConnection conn)
{
    int x = 0;
    for (int i = 0; i < 5; ++i)
    {
        GameObject treeGo = Instantiate(treePrefab, new Vector3(x++, 0, 0), Quaternion.identity);
        Tree tree = treeGo.GetComponent<Tree>();
        tree.numLeaves = Random.Range(10,200);
        Debug.Log("Spawning leaf with leaf count " + tree.numLeaves);
        NetworkServer.Spawn(treeGo, conn);
    }
}

  现在可以修改Tree脚本,向服务器发送一个命令。

public override void OnStartAuthority()
{
    CmdMessageFromTree("Tree with " + numLeaves + " reporting in");
}

[Command]
void CmdMessageFromTree(string msg)
{
    Debug.Log("Client sent a tree message: " + msg);
}

  请注意,您不能只将 CmdMessageFromTree 调用添加到 OnStartClient 中,因为此时尚未设置权限,因此调用会失败。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值