深入Unity Mirror AutoCreatePlayer 机制(1)

559 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第33天,点击查看活动详情

前言

本文分析Mirror的NetworkManager中Player的自动生成机制,包含Player生成的时机,服务器上Player的生成以及客户端Player镜像的生成机制。

Player何时开始生成

Mirror中的游戏对象都是现在服务器上生成,然后再在客户端生成对应的镜像对象,Player也是如此。但是对于普通的游戏对象,生成是在服务器上主动触发的,比如动态掉落的道具,是由服务器上运行的逻辑判断要掉落,然后开始对象生成。而Player对象是客户端连接上之后才触发开始生成的。其实只要是服务器知道客户端已经连接成功,就可以开始为客户端生成对应的Player对象。而Mirror的默认操作,是在客户端上发送AddPlayerMessage给服务器,然后服务器开始Player生成的流程。 而这又分为两种情况。

OnClientConnect

public virtual void OnClientConnect()
{
    // OnClientConnect by default calls AddPlayer but it should not do
    // that when we have online/offline scenes. so we need the
    // clientLoadedScene flag to prevent it.
    if (!clientLoadedScene)
    {
        // Ready/AddPlayer is usually triggered by a scene load completing.
        // if no scene was loaded, then Ready/AddPlayer it here instead.
        if (!NetworkClient.ready)
            NetworkClient.Ready();

        if (autoCreatePlayer)
            NetworkClient.AddPlayer();
    }
}

客户端连接时,如果启用autoCreatePlayer,则调用NetworkClient.AddPlayer()自动创建Player。

OnClientSceneChanged

public virtual void OnClientSceneChanged()
{
    // always become ready.
    if (!NetworkClient.ready) NetworkClient.Ready();

    // Only call AddPlayer for normal scene changes, not additive load/unload
    if (clientSceneOperation == SceneOperation.Normal && autoCreatePlayer && NetworkClient.localPlayer == null)
    {
        // add player if existing one is null
        NetworkClient.AddPlayer();
    }
}

当切换场景时(指的是切换单一场景,而不是加载卸载子场景),Player有可能被删除,此时调用NetworkClient.AddPlayer()来重新创建Player。

AddPlayer发送消息给服务器

AddPlayer是在客户端调用的,其内部主要的操作就是向服务器发送AddPlayerMessage:

connection.Send(new AddPlayerMessage());

这个消息本身并没有参数,因此如果你想创建自定义的角色,就需要使用自定义的消息。

服务器处理AddPlayerMessage消息

服务器使用OnServerAddPlayerInternal处理AddPlayerMessage消息,其内部调用了OnServerAddPlayer方法来实际创建和Spawn Player。

OnServerAddPlayer

该函数是在服务器上创建Player的核心函数,如果你要自定义Player创建,也需要做该函数内部做的事情。

public virtual void OnServerAddPlayer(NetworkConnectionToClient conn)
{
    Transform startPos = GetStartPosition();
    GameObject player = startPos != null
        ? Instantiate(playerPrefab, startPos.position, startPos.rotation)
        : Instantiate(playerPrefab);

    // instantiating a "Player" prefab gives it the name "Player(clone)"
    // => appending the connectionId is WAY more useful for debugging!
    player.name = $"{playerPrefab.name} [connId={conn.connectionId}]";
    NetworkServer.AddPlayerForConnection(conn, player);
}
  • 首先获取出生点,这可以使用GetStartPosition直接获取
  • 然后实例化playerPrefab,这个prefab就是注册在NetworkManager上的Player Prefab。
  • 实例化获得新的游戏对象player,设置他的位置和旋转,并设置name(仅debug用,非必要)。

至此,我们在服务器内存里面生成了一个Player对象,但是这个对象还没有注册给服务器,因此还算不上真正的在服务器上生成。而所谓在服务器生成,按照Mirror的术语来说,是进行了Spawn,而NetworkServer中有一个spawned字典,该字典的key是对象NetworkIdentity组件的netId属性,而value就是对象。只有进入了这个字典,才算是在服务器上生成了。另外当对象生成时,还需要为他创建所有的观察者。所谓观察者,是指存在于各个可以看到该对象的客户端上的镜像对象,镜像并不是Mirror的术语,是我自己起的名字(当然Mirror本身的含义就是镜像)。观察者对象将同步服务器上对象的状态。对于客户端来说,当连接上服务器时,还需要为服务器上已经存在的对象创建观察者。这一切操作,都反生在NetworkServer.AddPlayerForConnection(conn, player);中。我们下回再细说。