Unity Mirror

由于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

点赞

发表回复

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像(已失效)