Mirror是一个简单高效的开源的Unity多人游戏网络框架。
官方文档链接:
https://mirror-networking.gitbook.io/docs

必要性

首先解释一下标题的含义,这里复现一下所谓网络ID引用数据不同步的问题场景。

假设玩家A有一个跟随物,他不是玩家A这个对象的儿子,而是同级的一个对象。
它拥有一个独立的网络ID(NetworkIdentity),
我们希望一开始这个跟随物不存在,在玩家A召唤后才出现。
并且玩家A引用到跟随物的网络ID,从而控制其跟踪自己。
若此时又进来一个玩家B,由于玩家A和跟随物都有独立的网络ID,因此玩家B可以直接看到玩家A和跟随物。
但是在玩家B眼里,跟随物将无法跟随玩家A。
这是因为玩家A的客户端中的跟随物,玩家A对它的网络ID的`引用`是正确设置的,
但是玩家B的客户端中的跟随物,这个引用是空指针

下面用代码实现一下这个问题:

脚本挂载在玩家预制体上,
用来在按下空格键后,召唤出跟随自己的武器(我这里实际上就是一个胶囊)。
注意下面代码和流程中,让服务器创建一个prefab是需要先注册预制体、并且创建玩实例后调用NetworkServer.Spawn(tmp);开启网络数据同步的
而且由于服务器和客户端内存引用地址的差异,需要进行同步的GameObject必须改成NetworkIdentity

//PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
using Cinemachine;
public class PlayerController : NetworkBehaviour
{Rigidbody rb;CinemachineVirtualCamera cv;public GameObject prefabWeapon;[SyncVar]bool isWeaponHolded = false;[SyncVar]private NetworkIdentity weaponSpawned;[Command]void GetWeaponHold(){if(isWeaponHolded)return;isWeaponHolded = true;GameObject tmp = GameObject.Instantiate(prefabWeapon);NetworkServer.Spawn(tmp);weaponSpawned = tmp.GetComponent<NetworkIdentity>();}private void Start() {rb = GetComponent<Rigidbody>();if(isLocalPlayer){cv = GameObject.FindGameObjectWithTag("VCAM").GetComponent<CinemachineVirtualCamera>();cv.Follow = this.transform;cv.LookAt = this.transform;}}private void Update() {if(Input.GetKeyDown(KeyCode.Space)){GetWeaponHold();}float moveDirX = Input.GetAxis("Horizontal");float moveDirY = Input.GetAxis("Vertical");rb.velocity = new Vector3(moveDirX * 3f, moveDirY * 3f, 0);if(isWeaponHolded){weaponSpawned.transform.position = new Vector3(transform.position.x, transform.position.y+2, transform.position.z);}}
}

最后尤其注意一点,需要对预制体进行注册,否则网络管理器不会去识别预制体并将之与各个客户端同步

玩家预制体正常的引用武器预制体:

运行之:
本机可以正常看到跟随的武器。

但是新加入的玩家只能看到武器和老玩家,无法跟随。
而且在疯狂报错空指针,原因刚才解释过了,就是引用未成功设置——尽管这个引用是一个SyncVar
(未来版本的Mirror可能会修复这个Issue,2022/2/28未修复)

当然让武器的transform直接进行服务器与客户端之间的同步可以容易解决这个问题,但是效率低。

问题解决

这里直接转一下FirstGearGames这位大佬的解决方案,
其核心思想就是通过客户端已有的网络对象列表,与对应的netID,重新设定对象引用。

更多这位大佬的解决方案可以订阅他的patreon频道获取。

代码

using Mirror;#pragma warning disable CS0618, CS0672[System.Serializable]public class NewNetworkIdentityReference{/// <summary>/// NetworkId for the NetworkIdentity this was initialized for./// </summary>public uint NetworkId { get; private set; }/// <summary>/// NetworkIdentity this referencer is holding a value for./// </summary>public NetworkIdentity Value{get{//No networkId, therefor is null.if (NetworkId == 0)return null;//If cache isn't set then try to set it now.if (_networkIdentityCached == null)_networkIdentityCached = NetworkIdentity.spawned[NetworkId];return _networkIdentityCached;}}/// <summary>/// Cached NetworkIdentity value. Used to prevent excessive dictionary iterations./// </summary>private NetworkIdentity _networkIdentityCached = null;/// <summary>/// /// </summary>public NewNetworkIdentityReference() { }/// <summary>/// Initializes with a NetworkIdentity./// </summary>/// <param name="networkIdentity"></param>public NewNetworkIdentityReference(NetworkIdentity networkIdentity){if (networkIdentity == null)return;NetworkId = networkIdentity.netId;_networkIdentityCached = networkIdentity;}/// <summary>/// Initializes with a NetworkId./// </summary>/// <param name="networkId"></param>public NewNetworkIdentityReference(uint networkId){NetworkId = networkId;}}public static class NetworkIdentityReferenceReaderWriter{public static void WriteNetworkIdentityReference(this NetworkWriter writer, NewNetworkIdentityReference nir){//Null NetworkIdentityReference or no NetworkIdentity value.if (nir == null || nir.Value == null)writer.WriteUInt(0);//Value exist, write netId.elsewriter.WriteUInt(nir.Value.netId);}public static NewNetworkIdentityReference ReadNetworkIdentityReference(this NetworkReader reader){return new NewNetworkIdentityReference(reader.ReadUInt());}public static void WriteUInt(this NetworkWriter writer, uint value){writer.WriteByte((byte)value);writer.WriteByte((byte)(value >> 8));writer.WriteByte((byte)(value >> 16));writer.WriteByte((byte)(value >> 24));}public static uint ReadUInt(this NetworkReader reader){uint value = 0;value |= reader.ReadByte();value |= (uint)(reader.ReadByte() << 8);value |= (uint)(reader.ReadByte() << 16);value |= (uint)(reader.ReadByte() << 24);return value;}}

使用方式

将刚刚失败的代码中,做出如下修改:

//引用类型的修改
[SyncVar]
private NetworkIdentity weaponSpawned;
=>
[SyncVar]
private NewNetworkIdentityReference weaponSpawned;
//引用对象设定的修改
weaponSpawned = tmp.GetComponent<NetworkIdentity>();
=>
weaponSpawned = new NewNetworkIdentityReference(tmp.GetComponent<NetworkIdentity>());
//解包使用 多一个Value获取到正确的NetworkIdentity
weaponSpawned.transform.position = new Vector3(transform.position.x, transform.position.y+2, transform.position.z);
=>
weaponSpawned.Value.transform.position = new Vector3(transform.position.x, transform.position.y+2, transform.position.z);

测试

可以发现能够成功跟随了。

UnityMirror学习笔记(4):创建自定义prefab,并解决网络ID引用数据不同步的问题相关推荐

  1. JavaScript学习笔记:创建自定义对象

    文章目录 一.利用构造函数模式创建自定义对象 二.采用原型模式创建自定义对象 三.采用混合模式创建自定义对象 四.采用动态原型模式创建自定义对象 一.利用构造函数模式创建自定义对象 使用构造函数可以创 ...

  2. PhalAPI学习笔记 ——— 第一章自定义HelloWorld接口

    PhalAPI学习笔记 --- 第一章自定义HelloWorld接口 前言 自定义接口 项目实例 结果 分布解析 结束语 前言 公司业务需要转学PHP,而PHP中一个功能强大且生态链完整的PHP接口框 ...

  3. Django:学习笔记(2)——创建第一个应用

    Django:学习笔记(2)--创建第一个应用 创建应用 在 Django 中,每一个应用都是一个 Python 包,并且遵循着相同的约定.Django 自带一个工具,可以帮你生成应用的基础目录结构, ...

  4. spring学习笔记02-spring-bean创建的细节问题

    spring学习笔记02-spring-bean创建的细节问题 三种创建Bean对象的方式 Bean的作用范围 Bean的生命周期 <?xml version="1.0" e ...

  5. Mr.J-- jQuery学习笔记(十九)--自定义动画实现图标特效

    之前有写过自定义动画Mr.J-- jQuery学习笔记(十八)--自定义动画 这次实现一个小demo 图标特效 页面渲染 <!DOCTYPE html> <html lang=&qu ...

  6. JavaScript学习笔记:创建、添加与删除节点

    JavaScript学习笔记:创建.添加与删除节点 文章目录 JavaScript学习笔记:创建.添加与删除节点 一.DOM对象节点类型 二.创建节点 1.创建元素节点 2.创建文本节点 3.创建属性 ...

  7. Python学习笔记:创建分数类

    Python学习笔记:创建分数类 1.编写创建分数类.py # 创建分数类from math import gcd# 定义分数类 class Fraction: def __init__(self, ...

  8. Java学习笔记:创建线程的两种方法

    Java学习笔记:创建线程的两种方法 一.预备工作 1.创建Maven项目ThreadDemo 2.在pom.xml里添加依赖 二.继承Thread类创建子线程

  9. oracle 创建角色 权限设置,[学习笔记] Oracle创建用户、分配权限、设置角色,

    [学习笔记] Oracle创建用户.分配权限.设置角色, 创建用户 create user student --用户名 identified by "123456" --密码 de ...

最新文章

  1. 码农技术炒股之路——抓取股票基本信息、实时交易信息、主力动向信息
  2. ipvsadm的几个参数输出的说明
  3. Ubuntu下安装Docker
  4. TestNG方法測试及注意要点 代码及配置具体解释(解决testng方法不运行问题)
  5. 数据库新秀 postgresql vs mongo 性能PK
  6. 浅谈Spring5 响应式编程
  7. [vue] 实际工作中,你总结的vue最佳实践有哪些?
  8. (pytorch-深度学习)批量归一化
  9. 使用Qt生成第一个窗口程序
  10. 机器学习深度学习知识点总结
  11. 4.redis设计与实现--跳跃表
  12. 手把手教你Android手机与BLE终端通信--连接,发送和接收数据
  13. linux下将多个文件去除文件头合并_使用 PDF Mix Tool 执行常见的 PDF 编辑任务 | Linux 中国...
  14. 软件设计原则(七) 迪米特法则
  15. java 模拟百度翻译
  16. 一行代码统计文本中指定字符串出现的次数
  17. 一清机、二清机、跳码,你知道这些POS机猫腻的原理吗?
  18. 2023年四川农业大学农村发展专硕经验贴
  19. 道阻且长,行则将至;行而不辍,未来可期。
  20. [UVM]UVM TLM1.0 Interface归纳总结 --- 图解UVM TLM1.0 Interface

热门文章

  1. 【深度学习】全连接网络
  2. 【react】antd
  3. 在centos环境中简单搭建邮件服务器
  4. UART和USART的区别(UART vs USART)
  5. 解忧杂货铺(五):用了无法离开的网站资源
  6. Unity开发使用DOTween插件实现ui组件慢慢消失和慢慢出现
  7. TCP/IP协议简介(五) 之 应用层
  8. ❤️【python入门项目】将学妹的照片转换为铅笔素描 ❤️
  9. 基于jsp+mysql+ssm汽车配件管理系统-计算机毕业设计
  10. 爬取猫眼top100并存入csv文件中