携手创作,共同成长!这是我参与「掘金日新计划 · 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);中。我们下回再细说。