深入Unity Mirror AutoCreatePlayer 机制(3)

605 阅读4分钟

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

Player Spawn机制详解

关于对象Spawn

Spawn机制是Mirror最重要的机制,我觉得甚至比同步机制还重要。因为只有能正确的生成对象才有进一步的同步。而游戏场景中存在大量的对象,且在游戏过程中不断有新对象动态生成,而且客户端也可以随时连接上服务器(包含断线重连),因此在任意情况下,都能让客户端上生成正确的对象是非常复杂的事情。虽然本文是探讨Player生成机制,但其实Player的生成也是使用了对象的通用Spawn机制。我们先接着上篇看。

Respawn方法

static void Respawn(NetworkIdentity identity)
{
    if (identity.netId == 0)
    {
        // If the object has not been spawned, then do a full spawn and update observers
        Spawn(identity.gameObject, identity.connectionToClient);
    }
    else
    {
        // otherwise just replace his data
        SendSpawnMessage(identity, identity.connectionToClient);
    }
}
  • 判断identity.netId,如果为0,说明该对象还没有注册到服务器上,需要进行完整的spawn操作。
  • 否则该对象已经存在,但是由于某种原因,又触发了AddPlayerForConnection,由于该对象已经在服务器上存在,有很多工作都不需要再做,因此这儿只是发送SpawnMessage,给客户端同步该对象的最新状态。

Spawn和SpawnObject

Spawn方法内部只是简单调用了SpawnObject方法,即Player的spawn和普通对象是使用相同的方法。

static void SpawnObject(GameObject obj, NetworkConnection ownerConnection)
{
    //省略条件检测

    NetworkIdentity identity = obj.GetComponent<NetworkIdentity>();

    //省略一些不太重要的内容

    identity.OnStartServer();       

    RebuildObservers(identity, true);
}

这个函数内部我们主要看两个地方,OnStartServerRebuildObservers

NetworkIdentity.OnStartServer

注意这儿省略了很多代码,只列出最核心的部分:

internal void OnStartServer()
{   
    netId = GetNextNetworkId();
    observers = new Dictionary<int, NetworkConnectionToClient>();
    
    NetworkServer.spawned[netId] = this;
    
    foreach (NetworkBehaviour comp in NetworkBehaviours)
    {        
        try
        {
            comp.OnStartServer();
        }
        catch (Exception e)
        {
            Debug.LogException(e, comp);
        }
    }
}
  • 设置netId,Mirror使用netId来标识网络对象,在服务器上和客户端上的同一对象使用相同的netId。
  • 加入NetworkServer.spawned字典
  • 对该对象上的所有NetworkBehaviour组件调用OnStartServer

至此,该Player对象已经在服务器上生成了,存在于spawned字典中并拥有了自己的netId。

RebuildObservers

上篇中我们介绍了观察者的概念,即在存在于客户端上,观察服务器上对象的对象,他和服务器上的对应对象具有相同的netId。

public static void RebuildObservers(NetworkIdentity identity, bool initialize)
{
    // observers are null until OnStartServer creates them
    if (identity.observers == null)
        return;

    // if there is no interest management system,
    // or if 'force shown' then add all connections
    if (aoi == null || identity.visible == Visibility.ForceShown)
    {
        RebuildObserversDefault(identity, initialize);
    }
    // otherwise let interest management system rebuild
    else
    {
        RebuildObserversCustom(identity, initialize);
    }
}

我们只看不使用aoi的默认情况,即RebuildObserversDefault方法。该方法内部调用了AddAllReadyServerConnectionsToObservers。这个函数会遍历所有已经存在的客户端连接,向当前对象的identity添加这些connection。

foreach (NetworkConnectionToClient conn in connections.Values)
{    
    if (conn.isReady)
        identity.AddObserver(conn);
}

这来到了之前我们看过的一个函数AddObserver,这在客户端连接时,向它添加已经存在的对象时曾经使用过。看来我们已经接近最底层的机制了。

AddObserver(NetworkConnectionToClient conn)

该函数调用了conn.AddToObserving(this):

internal void AddToObserving(NetworkIdentity netIdentity)
{
    observing.Add(netIdentity);

    // spawn identity for this conn
    NetworkServer.ShowForConnection(netIdentity, this);
}

记住,我们一直是在执行服务器代码。这儿终于来到了为这个connection执行spawn identity的地方:

internal static void ShowForConnection(NetworkIdentity identity, NetworkConnection conn)
{
    if (conn.isReady)
        SendSpawnMessage(identity, conn);
}

有遇到了之前看到的函数SendSpawnMessage,来了,这就是最后一步了,发送网络消息。服务器和客户端代码的分界线就是发送消息,发送了消息就等着客户端执行了。我们先看下这个方法。

SendSpawnMessage

internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnection conn)
{
    using (NetworkWriterPooled ownerWriter = NetworkWriterPool.Get(), observersWriter = NetworkWriterPool.Get())
    {
        bool isOwner = identity.connectionToClient == conn;
        ArraySegment<byte> payload = CreateSpawnMessagePayload(isOwner, identity, ownerWriter, observersWriter);
        SpawnMessage message = new SpawnMessage
        {
            netId = identity.netId,
            isLocalPlayer = conn.identity == identity,
            isOwner = isOwner,
            sceneId = identity.sceneId,
            assetId = identity.assetId,            
            position = identity.transform.localPosition,
            rotation = identity.transform.localRotation,
            scale = identity.transform.localScale,
            payload = payload
        };
        conn.Send(message);
    }
}

这儿创建了SpawnMessage,这是Mirror预定义网络消息中最复杂的一个。我们看看他的定义:

public struct SpawnMessage : NetworkMessage
{
    // netId of new or existing object
    public uint netId;
    public bool isLocalPlayer;
    // Sets hasAuthority on the spawned object
    public bool isOwner;
    public ulong sceneId;
    // If sceneId != 0 then it is used instead of assetId
    public Guid assetId;
    // Local position
    public Vector3 position;
    // Local rotation
    public Quaternion rotation;
    // Local scale
    public Vector3 scale;
    // serialized component data
    // ArraySegment to avoid unnecessary allocations
    public ArraySegment<byte> payload;
}

最重要的,其实是3个id,即netId,sceneIdassetId。然后会发送对象当前的位置旋转缩放。 这儿的payload涉及到Mirror的消息打包机制,这儿就不展开了。

小结

本篇中,我们终于分析完服务器上Spawn Player的完整过程,最终走到了发送SpawnMessage。而本系列的最后一篇,将分析客户端接收SpawnMessage,在客户端上生成player观察者对象的机制。