网络系统有多种方式进行指令的调用,通常被称为 **Remote Procedure Calls **,简称 RPC。mirror 有两种方式:
- Commands:从客户端发出指令,在服务器执行。
- ClientRpc:从服务器发出指令,在客户端执行。
远程行为执行图:
Command
1.所有Command标记的函数,名字都要 Cmd 开头。不能是静态函数。
2.通常玩家自能控制自己的 Player,绕过权限限制使用 [Command(requiresAuthority = false)]
。
3.Command 函数支持的函数参数类型见 Data types。
4.不要每帧调用 Command 函数,会造成比较大的网络开销。
public class Player : NetworkBehaviour
{
// assigned in inspector
public GameObject cubePrefab;
void Update()
{
if (!isLocalPlayer) return;
if (Input.GetKey(KeyCode.X))
CmdDropCube();
}
[Command]
void CmdDropCube()
{
if (cubePrefab != null)
{
Vector3 spawnPos = transform.position + transform.forward * 2;
Quaternion spawnRot = transform.rotation;
GameObject cube = Instantiate(cubePrefab, spawnPos, spawnRot);
NetworkServer.Spawn(cube);
}
}
}
绕过权限限制
满足下列条件的时候,也可以在非 Player 游戏对象上,调用 Command 指令。
- 对象是在客户授权下生成的。
- 该对象已通过 NetworkIdentity.AssignClientAuthority 设置了客户权限。
- 命令的 requirementsAuthority 选项设置为 false。
- 命令方法签名中加入一个可选的 NetworkConnectionToClient sender= null 参数,Mirror 会为你设置 sender 变量。
- 请勿尝试为该可选参数设置值…它将被忽略。
从这些对象发送的命令会在服务器实例对象上运行,而不是在客户端的实例对象上运行。
public enum DoorState : byte
{
Open, Closed, Locked
}
public class Door : NetworkBehaviour
{
[SyncVar]
public DoorState doorState;
[Client]
void OnMouseUpAsButton()
{
CmdSetDoorState();
}
[Command(requiresAuthority = false)]
public void CmdSetDoorState(NetworkConnectionToClient sender = null)
{
bool hasDoorKey = sender.identity.GetComponent<PlayerState>().hasDoorKey;
if (doorState == DoorState.Open)
{
doorState = hasDoorKey ? DoorState.Locked : DoorState.Closed;
return;
}
if (doorState == DoorState.Locked && hasDoorKey)
{
doorState = DoorState.Open;
return;
}
if (doorState == DoorState.Closed)
doorState = DoorState.Open;
}
}
ClientRpc Calls
ClientRpc 调用是从服务器上的对象向客户端对象发送的。它们可以从任何已生成的具有 NetworkIdentity 的服务器对象发送。由于服务器具有权限,因此服务器对象发送这些调用不存在安全问题。要将一个函数转化为 ClientRpc 调用,可为其添加 [ClientRpc] 自定义属性,并可选择 添加 “Rpc ”前缀以符合命名规则。现在,在服务器上调用该函数时,客户端将运行该函数。任何允许数据类型的参数都将通过 ClientRpc 调用自动传递给客户端。
ClientRpc 函数的前缀应为 “Rpc”,并且不能是静态的。这是在阅读调用该方法的代码时的一个提示–该函数比较特殊,不会像普通函数那样在本地调用。
public class Player : NetworkBehaviour
{
int health;
public void TakeDamage(int amount)
{
if (!isServer) return;
health -= amount;
RpcDamage(amount);
}
[ClientRpc]
public void RpcDamage(int amount)
{
Debug.Log("Took damage:" + amount);
}
}
以主机身份运行带有本地客户端的游戏时,ClientRpc 调用将在本地客户端上调用,即使它与服务器处于同一进程中。因此,本地客户端和远程客户端的 ClientRpc 调用行为是一样的。
排除拥有者
ClientRpc 消息只会根据对象的网络可见性发送给对象的观察者。Player 对象始终是自己的观察者。在某些情况下,您可能希望在调用 ClientRpc 时排除所有者。这可以使用 includeOwner 选项来实现:[ClientRpc(includeOwner=false)] 。
TargetRpc 的调用
TargetRpc 函数由服务器上的用户代码调用,然后在 NetworkConnection 指定的客户端对象上调用。RPC 调用的参数在网络上进行序列化,因此客户端函数的调用值与服务器上的函数相同。这些函数应以前缀 “Target ”开头,以符合命名规则,并且不能是静态的。
public class Player : NetworkBehaviour
{
public int health;
[Command]
void CmdMagic(GameObject target, int damage)
{
target.GetComponent<Player>().health -= damage;
NetworkIdentity opponentIdentity = target.GetComponent<NetworkIdentity>();
TargetDoMagic(opponentIdentity.connectionToClient, damage);
}
[TargetRpc]
public void TargetDoMagic(NetworkConnectionToClient target, int damage)
{
// This will appear on the opponent's client, not the attacking player's
Debug.Log($"Magic Damage = {damage}");
}
// Heal thyself
[Command]
public void CmdHealMe()
{
health += 10;
TargetHealed(10);
}
[TargetRpc]
public void TargetHealed(int amount)
{
// No NetworkConnection parameter, so it goes to owner
Debug.Log($"Health increased by {amount}");
}
}
远程调用的参数
传递给命令和 ClientRpc 调用的参数会被序列化并通过网络发送。支持类型见 supported mirror type.。
远程调用操作的参数不能是游戏对象的子组件,如脚本实例或 Transform 组件。