由于mirror的概念和API同已经弃用的unet很相似。同时mirror的文档在基础概念比较少,而且全英文。所以可以先从unet的官方文档开始入门。UNet
mirror入门
1) 运行环境:
从AssetStore下载mirror插件。
注意.net版本要求。需要在playersetting1里将.net版本改成.net4.x。
2) 构建NetworkManager
建立一个空物体,将networkManager,Transport,networkManagerHUD组件挂在上面。
3) 制作playerPrefab
建立一个cube,在上面挂上networkIdentity作为在网络同步的唯一标识。之后加上networkTransform做位置的网络同步。勾选Client Authority,说明是客户端控制位移(因为是玩家)。在挂上一个随机移动的mono脚本。打包运行后发现server端上有跳跃的现象。
是因为server上物体随机位置计算了一次后,又同步了client上的位置一次造成闪烁。于是为了解决这个问题,就需要将脚本从继承MonoBehaviour改为继承NetworkBehaviour。
Start函数 ——>重写OnStartLocalPlayer函数(视脚本所挂物体而定(这个是只在客户端运行的函数)详看networkBehaviour的必然函数执行顺序)
还是要避免直接使用mono的start来初始化。因为在host下,mono的start只执行一次。但host在内部分为localclient和server。虽然共享场景,但依然可能导致其中一个没有被初始化(执行顺序不一定)。
4) client向server发送命令(command)
要将命令封装为一个方法,方法名以Cmd开头,给方法添加[Command]特性即可。
[Command]
private void CmdSetRandomColor()
{
Debug.Log("CMD");
Color rc = Random.ColorHSV();
SetColor(rc);
RpcSetRandomColor(rc);
}
注意:1.实际上是client调用了server上的方法,显然command不能从server上发出。2.要在调用command方法时应判断是不是localPlayer,否则player即使不是本地玩家会也去调用command方法,导致弹出警告(command也不会执行,因为一个玩家不能在另一个玩家的游戏对象上调用命令,只能在自己的游戏对象上调用命令)。
由于是client——>server,会发现虽然server上的颜色改了,但client上并没有变化。
5) 于是需要一个server的回调,server——>client。
类似command用法,用一个方法封装回调。方法名以Rpc开头,添加[ClientRPC]特性即可。
[Command]
private void CmdSetRandomColor()
{
Debug.Log("CMD");
Color rc = Random.ColorHSV();
SetColor(rc);
RpcSetRandomColor(rc);
}
[ClientRpc]
private void RpcSetRandomColor(Color color)
{
SetColor(color);
}
于是client就能和server同步。
client—调用—>server的[command]方法—调用—>client的[ClientRPC]方法
clientRpc是会向所有client回调这个方法,有时候我们想让特定的client接受特定的回调。
于是就有了回调特定client的方法。同[clientRpc]类似。方法名以Target开头,添加[TargetRPC]特性,要注意的是该方法至少有一个NetworkConnection的形参,用来确定是回调哪一个client。
[Command]
private void CmdSetRandomColor()
{
Debug.Log("CMD");
Color rc = Random.ColorHSV();
SetColor(rc);
var nid = GetComponent<NetworkIdentity>();
TargetSetRandomColor(nid.connectionToClient,rc);
}
[TargetRpc]
private void TargetSetRandomColor(NetworkConnection con, Color color)
{
Debug.Log("target");
SetColor(color);
}
6) 值同步:SyncVal
如果想同步脚本中某一个值的话,可以给值添加[SyncVar]特性。
[SyncVar] private Color _color;
在Inspector面板上就会出现同步模式
默认广播给所有client,同步间隔0.1s
还可以指定值更新的时候,自动调用某个客户端上的方法。
[SyncVar(hook =nameof(SetColor))]
private Color _color;
private void SetColor(Color oldColor, Color newColor)
{
var sr = GetComponent<SpriteRenderer>();
sr.color = newColor;
}
注意:1.调用的方法要有先前值和更新值两个形参。2.host下,所有object都中回调函数都会被调用,要用HasAuthority来限制。(因为host既是server又是client)
7) Object权限详解
在网络中权限是一个很重要的概念。拥有了一个物体的权限就表示有修改该物体的能力。在mirror框架中,server拥有绝大部分联网物体的权限,除了玩家角色和一些特定的物体。拥有了权限的玩家客户端就可以通过commands来同server交互,改变已有权限的物体。
获得权限的方式:
1) 玩家预制体在生成是自动赋予了对应的客户端权限。
2) 玩家可以用command用预制体生成object时可以设置该物体的权限。
如下:
[SerializeField]
private GameObject _objPrefab;
bool check = true;
private void Update()
{
if (check && base.hasAuthority)
{
CmdSpawn();
check = false;
}
}
[Command]
private void CmdSpawn()
{
var obj = Instantiate(_objPrefab);//预制体要先实例化
NetworkServer.Spawn(obj, GetComponent<NetworkIdentity>().connectionToClient);
}
3) 改变已有的物体的权限。
if (!hasAuthority) return;
if (!Input.GetKeyDown(KeyCode.Mouse0)) return;
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100))
{
NetworkIdentity id = hit.collider.GetComponent<NetworkIdentity>();
if (id != null && id.hasAuthority)
{
CmdRequestAuthority(id);
}
}
}
[Command]
private void CmdRequestAuthority(NetworkIdentity otherId)
{
otherId.AssignClientAuthority(base.connectionToClient);//将该id的物体权限转到该客户端下
}
注意:1)一旦客户端掉线,客户端所拥有的所有物体都会被销毁。
2)networkBehaviour中的localplayer和 hasAuthority 。localplayer是判断当前脚本是不是在当前客户端的玩家角色上 。hasAuthority判断当前客户端是不是有权限控制这个物体。
8) 如何传递基本类型数据
在之前的client和server之间的交互(如command,clientRpc等)可以传递参数的。大部分基本类型如int,float等可以直接发送,部分mirror和unity类型也可以直接传输。如transform,vector3,netidentiy,GameObject等。注意Gameobject需要带有NetworkIdentity组件。可以通过Networkwriter类来确认那些类型被默认支持。
9) 如何传递自定义类型数据
对于没有支持的类型,可以通过自定义序列化器来支持。如下:
public class Item
{
public string Name;
}
public class Armour : Item
{
public int Protection;
public int weight;
}
public class Potion : Item
{
public int Health;
}
/// <summary>
/// item序列化器
/// </summary>
public static class ItemSerializer
{
private const byte ITEM_ID = 0;
private const byte POTION_ID = 1;
private const byte ARMOUR_ID = 2;
public static void WriteItem(this NetworkWriter writer, Item item)
{
if (item is Potion potion)
{
writer.WriteByte(POTION_ID);
writer.WriteString(potion.Name);
writer.WritePackedInt32(potion.Health);
}
if (item is Armour armour)
{
writer.WriteByte(ARMOUR_ID);
writer.WriteString(armour.Name);
writer.WritePackedInt32(armour.Protection);
writer.WritePackedInt32(armour.weight);
}
else
{
writer.WriteByte(ITEM_ID);
writer.WriteString(item.Name);
}
}
public static Item ReadItem(this NetworkReader reader)
{
byte id = reader.ReadByte();
switch (id)
{
case POTION_ID:
return new Potion
{
Name = reader.ReadString(),
Health = reader.ReadPackedInt32()
};
case ARMOUR_ID:
return new Armour
{
Name = reader.ReadString(),
Protection = reader.ReadPackedInt32(),
weight = reader.ReadPackedInt32()
};
case ITEM_ID:
return new Item
{
Name = reader.ReadString()
};
default:
throw new Exception($"Unknown ItemType :{id}");
}
}
}
10) NetworkBehaviour的StartCycle
Awake() 最先无论client还是server。
OnstartServer() 仅在server上执行
OnstartAuthority() 仅在client执行,当物体生产时,同时在该客户端有权限时执行
OnStartClient()仅在client执行,用来初始化客户端
OnStartLocalPlayer()仅在client执行,当脚本所在物体为玩家角色时调用,用来设置跟踪相机等
OnStopAuthority()仅在client执行,当客户端失去该物体权限时调用
Start() 顺序不定,通常在最后但不保证每次都是,所以不建议将网络数据放这里处理。
————————————————
原文链接:https://blog.csdn.net/farcor_cn/article/details/110360424