深入Unity Mirror AutoCreatePlayer 机制(4)

288 阅读3分钟

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

客户端生成Player机制

注册SpawnMessage处理器

客户端连接时,调用Connect方法:

public static void Connect(string address)
{    
    RegisterSystemHandlers(false);
    Transport.activeTransport.enabled = true;
    AddTransportHandlers();

    connectState = ConnectState.Connecting;
    Transport.activeTransport.ClientConnect(address);

    connection = new NetworkConnectionToServer();
}

再实际使用Transport连接服务器之前,会先调用RegisterSystemHandlers注册系统处理器。 RegisterSystemHandlers方法中,会根据当前是Host模式还是纯客户端模式,注册不同的处理器。而对于SpawnMessage,会注册如下处理器,我们只关注纯客户端模式。 RegisterHandler<SpawnMessage>(OnSpawn);

OnSpawn方法

internal static void OnSpawn(SpawnMessage message)
{
    if (FindOrSpawnObject(message, out NetworkIdentity identity))
    {
        ApplySpawnPayload(identity, message);
    }
}

FindOrSpawnObject

该方法首先调用FindOrSpawnObject来查询该message中所描述的对象是否已经在客户端存在,如果不存在则返回true表示需要生成对象。

internal static bool FindOrSpawnObject(SpawnMessage message, out NetworkIdentity identity)
{
    
    identity = GetExistingObject(message.netId);

    // if found, return early
    if (identity != null)
    {
        return true;
    }

    identity = message.sceneId == 0 ? SpawnPrefab(message) : SpawnSceneObject(message.sceneId);

    if (identity == null)
    {
        Debug.LogError($"Could not spawn assetId={message.assetId} scene={message.sceneId:X} netId={message.netId}");
        return false;
    }

    return true;
}
  • 首先我们要关注一下GetExistingObject方法,该方法接受的参数为netId。
static NetworkIdentity GetExistingObject(uint netid)
{
    spawned.TryGetValue(netid, out NetworkIdentity localObject);
    return localObject;
}

该方法内部的spawned是NetworkClient类的一个静态字典,包含了客户端上所有的网络对象。和服务器类似,在客户端上spawn对象,也需要加入字典中。所以查询这个字典就可以知道该对象是否已经存在于客户端中。

  • 如果客户端还没有该对象,则使用SpawnPrefabSpawnSceneObject生成。这儿是根据是否存在sceneId来判断该对象是否是场景游戏对象,如果没有sceneID,则需要从prefab动态生成,否则会特殊处理。关于场景游戏对象,请参考Unity Mirror联网游戏开发(4) 场景中的Game Objects

SpawnPrefab

该方法会根据message.assetId,去获取注册到Mirror中的prefab,然后使用该prefab实例化出游戏对象。不过该方法内部也处理了自定义实例化,即spawnHandler。本文就不细说了。

SpawnSceneObject

场景中原有的网络对象,会存在于NetworkClient的spawnableObjects字典中,这些对象会在场景载入后被隐藏。而SpawnSceneObject方法会从中取出对象,并将对象从该字典中删除。

ApplySpawnPayload

如果对象找到了或者生成了,则调用该方法进行设置。

internal static void ApplySpawnPayload(NetworkIdentity identity, SpawnMessage message)
{
    if (message.assetId != Guid.Empty)
        identity.assetId = message.assetId;

    if (!identity.gameObject.activeSelf)
    {
        identity.gameObject.SetActive(true);
    }

    // apply local values for VR support
    identity.transform.localPosition = message.position;
    identity.transform.localRotation = message.rotation;
    identity.transform.localScale = message.scale;
    identity.hasAuthority = message.isOwner;
    identity.netId = message.netId;

    if (message.isLocalPlayer)
        InternalAddPlayer(identity);

    // deserialize components if any payload
    // (Count is 0 if there were no components)
    if (message.payload.Count > 0)
    {
        using (NetworkReaderPooled payloadReader = NetworkReaderPool.Get(message.payload))
        {
            identity.OnDeserializeAllSafely(payloadReader, true);
        }
    }

    spawned[message.netId] = identity;

    // the initial spawn with OnObjectSpawnStarted/Finished calls all
    // object's OnStartClient/OnStartLocalPlayer after they were all
    // spawned.
    // this only happens once though.
    // for all future spawns, we need to call OnStartClient/LocalPlayer
    // here immediately since there won't be another OnObjectSpawnFinished.
    if (isSpawnFinished)
    {
        identity.NotifyAuthority();
        identity.OnStartClient();
        CheckForLocalPlayer(identity);
    }
}
  • 如果对象不可见(主要是对于scene game object),则会调用SetActive(true)
  • 然后,最重要的是调用了spawned[message.netId] = identity;将其添加到客户端的spawned字典中,这样该对象就真正在客户端spawn成功了。
  • 这儿的isSpawnFinished其实是针对之前客户端刚连接时,生成所有已经在服务器上存在的对象时,会有ObjectSpawnStartedObjectSpawnFinished两个消息发送。当客户端接收到ObjectSpawnFinished消失时,表示已经生成了所有需要生成的对象,会设置isSpawnFinished为true。

总结

本系列文章深入分析了默认情况下Mirror自动创建Player的机制,该机制其实是基于Mirror的Spawn System。

  • 客户端连接时会发送AddPlayerMessage消息给服务器请求添加player
  • 服务器会调用OnServerAddPlayer生成player对象实例
  • 服务器会继续调用NetworkServer.AddPlayerForConnection进行处理。首先会根据可见性和aoi系统,找到该客户端可以看到的所有对象,发送消息让客户端生成这些对象
  • 之后服务器会对于所有连接上的客户端,不仅仅是刚刚连接的这个客户端,发送消息,让这些客户端生成player
  • 客户端收到消息后在客户端生成对象。