玩家对象
在网络系统的HLAPI中,玩家是特殊类型的对象。它们代表服务器上的玩家,因此能够从玩家的客户端运行命令(这是安全的客户端到服务器的远程过程调用)。在此服务器权威系统中,其他非玩家服务器端对象不具有直接从客户端上的对象接收命令的功能。这既是为了安全,又为了降低在分布式环境中工作的复杂性。通过玩家对象路由来自用户的所有传入命令可确保这些消息来自正确的地方,正确的客户端,并且可以在中央位置处理。
使用NetworkManager
时,客户端连接到服务器时会默认添加玩家。但在某些情况下,应该延迟添加玩家,直到发生某些输入事件,因此可以使用NetworkManager
上的AutoCreatePlayer
复选框关闭此行为。当添加玩家时,NetworkManager
将从PlayerPrefab
实例化一个对象并将其与连接相关联。这实际上是由NetworkManager
调用NetworkServer.AddPlayerForConnection
完成的。这种行为可以通过覆盖NetworkManager.OnServerAddPlayer
来修改。OnServerAddPlayer
的默认实现从PlayerPrefab
实例化一个新的玩家实例,并调用NetworkServer.AddPlayerForConnection
来产生新的玩家实例。OnServerAddPlayer
的自定义实现也必须调用NetworkServer.AddPlayerForConnection
,但可以自由执行它需要的任何其他初始化。下面的示例自定义玩家的颜色:
class Player : NetworkBehaviour
{
[SyncVar]
public Color color;
}
class MyManager : NetworkManager
{
public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
{
GameObject player = (GameObject)Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
player.GetComponent<Player>().color = Color.red;
NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
}
}
函数NetworkServer.AddPlayerForConnection
不必从OnServerAddPlayer
中调用。只要传入正确的连接对象和playerControllerId
,就可以在OnServerAddPlayer
返回后调用它。这允许在两者之间发生异步步骤,例如从远程数据源加载玩家数据。
HLAPI将玩家和客户视为独立的对象。在大多数情况下,每个客户端都有一名玩家。但是,在某些情况下 - 例如当有多个控制器连接到控制台系统时,它们可能是单个连接的多个玩家对象。当有多个玩家进行连接时,使用playerControllerId
属性来区分它们。这是一个作用于连接的标识符,以便它真正地映射到与该客户端上的玩家关联的控制器的ID。
传递给服务器上的NetworkServer.AddPlayerForConnection
的玩家对象由系统自动生成,因此无需为玩家调用NetworkServer.Spawn
。一旦玩家准备好了,场景中的活动NetworkIdentity
对象就会在玩家的客户端产生。因此,游戏中的所有网络对象都将以其最新状态在该客户端上创建,因此它们与游戏的其他参与者同步。
NetworkManager
上的playerPrefab
不必用于创建玩家对象。你可以使用不同的方法来创建不同的玩家。
函数AddPlayerForConnection
不必从OnServerAddPlayer
中调用。它可以异步调用,例如,当向数据库等其他服务发出请求时,会返回有关创建哪种类型的玩家的信息。
就绪状态
除了玩家之外,客户端连接还具有“就绪”状态。准备好的客户端被发送产生的对象和状态同步更新; 未准备好的客户端不会发送这些更新。当客户端最初连接到服务器时,它尚未准备就绪。在此状态下,客户端可以执行不需要与服务器仿真进行实时交互的事情,例如加载场景,选择头像或填写登录框。一旦客户完成了所有的游戏前工作并加载了所有资源,他们就可以调用ClientScene.Ready
进入就绪状态。上面这个简单的例子也适用,因为在NetworkServer.AddPlayerForConnection
中添加一个玩家还会将客户端置于就绪状态(如果它尚未处于该状态)。
客户可以在没有准备好的情况下发送和接收网络消息,这也意味着没有活跃的玩家。因此,在菜单或选择屏幕上的客户端可以连接到游戏并与其交互,即使它们没有玩家对象。本文稍后会介绍如何在不使用命令和RPC调用的情况下发送消息。
切换玩家
连接的玩家对象可以用NetworkServer.ReplacePlayerForConnection
替换。这对于限制玩家在特定时间发出的命令很有用,例如在游戏前大厅屏幕中。该函数与AddPlayerForConnection
具有相同的参数,但允许已经存在该连接的玩家。旧玩家对象不必被销毁。当大厅中的所有玩家准备就绪时,NetworkLobbyManager
使用这种技术从LobbyPlayer
切换到玩游戏的玩家。
这也可以用来在对象被销毁后重生玩家。在某些情况下,最好禁用一个对象并在重新生成时重置它的游戏属性,但实际上用一个新对象替换它,你可以使用如下代码:
class GameManager
{
public void PlayerWasKilled(Player player)
{
var conn = oldPlayer.connectionToClient;
var newPlayer = Instantiate<GameObject>(playerPrefab);
Destroy(oldPlayer.gameObject);
NetworkServer.ReplacePlayerForConnection(conn, newPlayer, 0);
}
}
如果连接的玩家对象被销毁,那么该客户端将无法执行命令。但是他们仍然可以发送网络消息。
要使用ReplacePlayerForConnection
,您必须拥有玩家客户端的NetworkConnection
对象才能建立对象和客户端之间的关系。这通常是NetworkBehaviour
类中的属性connectionToClient
,但是如果旧玩家已被销毁,那么可能无法使用。
要找到连接,有一些列表可用。如果使用的是NetworkLobbyManager
,然后大堂玩家都在使用lobbySlots
。此外,NetworkServer
还有连接列表和localConnection
。
🔚