UnityMirror学习笔记(4):创建自定义prefab,并解决网络ID引用数据不同步的问题
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引用数据不同步的问题相关推荐
- JavaScript学习笔记:创建自定义对象
文章目录 一.利用构造函数模式创建自定义对象 二.采用原型模式创建自定义对象 三.采用混合模式创建自定义对象 四.采用动态原型模式创建自定义对象 一.利用构造函数模式创建自定义对象 使用构造函数可以创 ...
- PhalAPI学习笔记 ——— 第一章自定义HelloWorld接口
PhalAPI学习笔记 --- 第一章自定义HelloWorld接口 前言 自定义接口 项目实例 结果 分布解析 结束语 前言 公司业务需要转学PHP,而PHP中一个功能强大且生态链完整的PHP接口框 ...
- Django:学习笔记(2)——创建第一个应用
Django:学习笔记(2)--创建第一个应用 创建应用 在 Django 中,每一个应用都是一个 Python 包,并且遵循着相同的约定.Django 自带一个工具,可以帮你生成应用的基础目录结构, ...
- spring学习笔记02-spring-bean创建的细节问题
spring学习笔记02-spring-bean创建的细节问题 三种创建Bean对象的方式 Bean的作用范围 Bean的生命周期 <?xml version="1.0" e ...
- Mr.J-- jQuery学习笔记(十九)--自定义动画实现图标特效
之前有写过自定义动画Mr.J-- jQuery学习笔记(十八)--自定义动画 这次实现一个小demo 图标特效 页面渲染 <!DOCTYPE html> <html lang=&qu ...
- JavaScript学习笔记:创建、添加与删除节点
JavaScript学习笔记:创建.添加与删除节点 文章目录 JavaScript学习笔记:创建.添加与删除节点 一.DOM对象节点类型 二.创建节点 1.创建元素节点 2.创建文本节点 3.创建属性 ...
- Python学习笔记:创建分数类
Python学习笔记:创建分数类 1.编写创建分数类.py # 创建分数类from math import gcd# 定义分数类 class Fraction: def __init__(self, ...
- Java学习笔记:创建线程的两种方法
Java学习笔记:创建线程的两种方法 一.预备工作 1.创建Maven项目ThreadDemo 2.在pom.xml里添加依赖 二.继承Thread类创建子线程
- oracle 创建角色 权限设置,[学习笔记] Oracle创建用户、分配权限、设置角色,
[学习笔记] Oracle创建用户.分配权限.设置角色, 创建用户 create user student --用户名 identified by "123456" --密码 de ...
最新文章
- 码农技术炒股之路——抓取股票基本信息、实时交易信息、主力动向信息
- ipvsadm的几个参数输出的说明
- Ubuntu下安装Docker
- TestNG方法測试及注意要点 代码及配置具体解释(解决testng方法不运行问题)
- 数据库新秀 postgresql vs mongo 性能PK
- 浅谈Spring5 响应式编程
- [vue] 实际工作中,你总结的vue最佳实践有哪些?
- (pytorch-深度学习)批量归一化
- 使用Qt生成第一个窗口程序
- 机器学习深度学习知识点总结
- 4.redis设计与实现--跳跃表
- 手把手教你Android手机与BLE终端通信--连接,发送和接收数据
- linux下将多个文件去除文件头合并_使用 PDF Mix Tool 执行常见的 PDF 编辑任务 | Linux 中国...
- 软件设计原则(七) 迪米特法则
- java 模拟百度翻译
- 一行代码统计文本中指定字符串出现的次数
- 一清机、二清机、跳码,你知道这些POS机猫腻的原理吗?
- 2023年四川农业大学农村发展专硕经验贴
- 道阻且长,行则将至;行而不辍,未来可期。
- [UVM]UVM TLM1.0 Interface归纳总结 --- 图解UVM TLM1.0 Interface