Unity 使用Netcode实现用户登录和登出

Unity之NetCode for GameObjets 基本使用

  • 说明
    • 思路
    • 相关API
    • 代码实现
    • Tips

说明

  最近项目需要联机,项目方案选用Unity提供的NetCode for GameObjets(以下简称NGO),踩了不少坑,本文不介绍基础使用,围绕双端(主机+客户端)登录大厅展开介绍,这里记录总结一下。

思路

了解到功能需求以后,我有两个疑问:

  1. 当我某一个客户端上线如何将自身的信息同步给其它在线用户?
  2. 建立连接后,消息状态是如何同步的?
  3. 所有玩家的信息(比如玩家身上一个脚本标识)如何维护起来?

带着疑问继续往下走

  首先开启主机/服务器/客户端非常简单,只需要对应调用StartHost(),StartClient(),StartServer()即可。
在每一个客户端创建了一个Dictionary<ulong, PlayerInfo>()用于保存在线的玩家信息ulong是每个客户端ClientID,PlayerInfo是相关玩家信息。
  当某个玩家上线后,会本地add一下,并调用RPC方法,告诉其他玩家,我来了
  我本地存了一个JSON,每次客户端上线后,调用一个ServerRPC,将本地客户端的消息同步给其它客户端,主机端监听客户端的连接情况,每当有新客户端加入,调用一个ClientRPC,将信息同步给客户端。
  离线也是如此

相关API

ServerRPC
  RPC 是一个标准的软件行业概念。它们是对不在同一可执行文件中的对象调用方法的一种方式。
在这里插入图片描述
  客户端可以在 NetworkObject 上调用服务器 RPC。RPC 被放置在本地队列中,然后发送到服务器,在那里它在同一 NetworkObject 的服务器版本上执行。
  从客户端调用 RPC 时,SDK 会记录该 RPC 的对象、组件、方法和任何参数,并通过网络发送该信息。服务器或分布式颁发机构服务接收该信息,查找指定对象,查找指定方法,并使用收到的参数在指定对象上调用该方法。


ClientRPC
在这里插入图片描述  服务器可以在 NetworkObject 上调用客户端 RPC。RPC 被放置在本地队列中,然后发送到选定的客户端(默认情况下,此选择是所有客户端)。当客户端收到 RPC 时,RPC 将在同一 NetworkObject 的客户端版本上执行。


代码实现

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using QFramework;
using Unity.Netcode;//用户信息类 同步消息
public struct PlayerInfo : INetworkSerializable
{//客户端idpublic ulong id;//网络标识idpublic ulong networkID;public int typeID;public PlayerInfo(ulong id,ulong networkID,int typeID){this.id = id;this.networkID = networkID; this.typeID = typeID;   }public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter{serializer.SerializeValue(ref id);serializer.SerializeValue(ref networkID);serializer.SerializeValue(ref typeID);}
}public class UI_Desk_Ctrl :NetworkController
{[Header("角色1"),SerializeField] UI_DeskUserItemCtrl win_deskUserItemCtrl;[Header("角色2"), SerializeField] UI_DeskUserItemCtrl lif_deskUserItemCtrl;IInitModel_DeckRescue deckRescue_InitModel;//玩家列表Dictionary<ulong, PlayerInfo> allPlayerInfos;void Awake(){deckRescue_InitModel = this.GetModel<IInitModel_DeckRescue>();allPlayerInfos=new Dictionary<ulong, PlayerInfo>();}public override void OnNetworkSpawn(){base.OnNetworkSpawn();if (this.IsServer){NetworkManager.OnClientConnectedCallback += OnClientConn;NetworkManager.OnClientDisconnectCallback += OnClientDis;}else{NetworkManager.OnClientDisconnectCallback += OnClientDisInClient;}deckRescue_InitModel.CurUserType.RegisterWithInitValue(type => {WaitPlayerInit((int)type).ToAction().Start(this);}).UnRegisterWhenGameObjectDestroyed(this);}void OnClientDisInClient(ulong obj){RemovePlayer(obj);}//当客户端连接时  服务端执行void OnClientConn(ulong obj){//服务端更新客户端的玩家foreach (var item in allPlayerInfos){UpdatePlayerInfoClientRpc(item.Value);}}//当客户端断开连接void OnClientDis(ulong obj){RemovePlayer(obj);}//延时等待 获取NetworkObjectIdIEnumerator WaitPlayerInit(int typeID){while (NetworkManager.LocalClient.PlayerObject == null){yield return null;  }if (!this.IsServer){UpdatePlayerInfoServerRpc(new PlayerInfo(NetworkManager.LocalClientId, NetworkManager.LocalClient.PlayerObject.NetworkObjectId, typeID));}AddPlayer(new PlayerInfo(NetworkManager.LocalClientId, NetworkManager.LocalClient.PlayerObject.NetworkObjectId, typeID));}[ClientRpc]void UpdatePlayerInfoClientRpc(PlayerInfo info){if (!this.IsServer){if (allPlayerInfos.ContainsKey(info.id))allPlayerInfos[info.id] = info;elseAddPlayer(info);}}[ServerRpc(RequireOwnership =false)]void UpdatePlayerInfoServerRpc(PlayerInfo info){if (IsServer){if (allPlayerInfos.ContainsKey(info.id))allPlayerInfos[info.id] = info;elseAddPlayer(info);}}//添加玩家void AddPlayer(PlayerInfo info){if (!allPlayerInfos.ContainsKey(info.id)){Debug.Log("服务端添加客户端的 clientID:  " + info.id);allPlayerInfos.Add(info.id, info);var netwoObj = NetworkManager.Singleton.SpawnManager.SpawnedObjects[info.networkID];UserType type = (UserType)info.typeID;switch (type){case UserType.None:break;case UserType.Winchman:win_deskUserItemCtrl.UpdateData("角色1", "上线", true);break;case UserType.Lifeguard:lif_deskUserItemCtrl.UpdateData("角色2", "上线", true);break;}}else{Debug.Log("玩家已经存在  存在id:" + info.id);}}//移除玩家void RemovePlayer(ulong clientID){if (allPlayerInfos.ContainsKey(clientID)){Debug.Log("服务端接收到客户端退出:"+clientID  +"  netwoid:"+ allPlayerInfos[clientID].networkID);UserType type = (UserType)allPlayerInfos[clientID].typeID;switch (type){case UserType.None:break;case UserType.Winchman:win_deskUserItemCtrl.UpdateData("绞车手", "下线", false);break;case UserType.Lifeguard:lif_deskUserItemCtrl.UpdateData("救生员", "下线", false);break;}allPlayerInfos.Remove(clientID);}}public override void OnNetworkDespawn(){base.OnNetworkDespawn();if (this.IsServer){Debug.Log("服务端关闭:"+NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject().OwnerClientId);RemovePlayer(NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject().OwnerClientId);allPlayerInfos = new Dictionary<ulong, PlayerInfo>();NetworkManager.Shutdown();Debug.Log("服务器关闭");}}
}

Tips

NetworkObjectIdClientId 在 Unity Netcode 中是两个不同的概念,它们用于不同的目的:

  • ClientId:
    ClientId 是用于标识每个连接的客户端的唯一标识符。
    每个客户端连接到服务器时都会分配一个唯一的 ClientId,在整个会话期间保持不变。
    主要用于管理客户端连接、客户端之间的通信,以及区分各个连接的客户端。

  • NetworkObjectId:
    NetworkObjectId 是用于标识每个网络对象的唯一标识符。
    每个被网络管理的对象(例如玩家角色、物品等)都有一个 NetworkObject 组件,该组件自动生成一个 NetworkObjectId,用于唯一标识这个对象。
    NetworkObjectId 是在所有客户端和服务器之间同步的,主要用于查找和管理网络中生成的 GameObject 实例。

    相关


在 Unity Netcode 中,要确保传递给 RPC 或 NetworkVariable 的数据类型是可序列化的,遵循以下规则来判断数据类型是否可以序列化:

  1. 内置可序列化类型
    以下类型可以直接在 ServerRpcClientRpc 中使用,因为 Netcode 已经支持它们的序列化:

    基本数据类型:int, float, double, bool, char
    整型数据:byte, sbyte, short, ushort, long, ulong
    结构体:Vector2, Vector3, Quaternion, Color, Color32
    字符串:string
    数组:所有基本数据类型和上述结构体类型的 一维数组,例如 int[], float[], string[], Vector3[]
    枚举:枚举类型可以直接用于 RPC 参数

  2. 实现了 INetworkSerializable 的类型
    如果类型没有被 Netcode 内置支持(比如自定义的复杂对象),需要通过实现 INetworkSerializable 接口来自定义序列化方式。Netcode 提供的 INetworkSerializable 接口定义了序列化和反序列化方法,使自定义类型可以通过网络传输。


  如果想获取到某个网络组件,可在同步的信息中保存NetcodeID,然后根据NetworkManager.Singleton.SpawnManager.SpawnedObjects获取对应NetcodeObj组件


如有错误,欢迎指正!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/464629.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

专业网页设计服务重要是什么

当在搜索引擎中键入“网页设计”&#xff0c;您将获得超过2000万个相关结果。在如此众多的网站制作公司中&#xff0c;应该如何做出明智的选择呢&#xff1f;首先&#xff0c;让我们深入探讨一下网页设计的重要性。网站设计需要综合各种不同领域的专业知识&#xff0c;它是一个…

【UART异步串口协议及verilog实现】

UART异步串口协议 1 UART1.1 数据格式1.2 波特率 2 UART的发送和接收模块2.1 uart发送模块2.2 uart的接收模块 【博客首发于微信公众号《 漫谈芯片与编程》】 本篇文章介绍常用的芯片外围低速协议&#xff1a;UART&#xff1b; 1 UART UART是异步串行传输协议&#xff0c;即…

彻底解决idea不识别java项目

需求背景 下载了一个java swing的项目,通过idea导入后,项目无法识别。打开java文件,也不会报错,也不编译。 无法识别效果图 可以看到左侧的菜单,项目是没有被识别。 打开java文件,可以看到没有识别,java的图标也没有出现。 解决方法 1、打开Project Structure 2、修改…

仿真APP助力汽车零部件厂商打造核心竞争力

汽车零部件是汽车工业的基石&#xff0c;是构成车辆的基础元素。一辆汽车通常由上万件零部件组成&#xff0c;包括发动机系统、传动系统、制动系统、电子控制系统等&#xff0c;它们共同确保了汽车的安全、可靠性及高效运行。 在汽车产业快速发展的今天&#xff0c;汽车零部件…

【Nginx】前端项目开启 Gzip 压缩大幅提高页面加载速度

背景 Gzip 是一种文件压缩算法&#xff0c;减少文件大小&#xff0c;节省带宽从而提减少网络传输时间&#xff0c;网站会更快更丝滑。 // nginx roothcss-ecs-1d22:/etc/nginx# nginx -v nginx version: nginx/1.24.0// node ndde v18.20.1// dependencies "vue": …

【Linux】从零开始使用多路转接IO --- epoll

当你偶尔发现语言变得无力时&#xff0c; 不妨安静下来&#xff0c; 让沉默替你发声。 --- 里则林 --- 从零开始认识多路转接 1 epoll的作用和定位2 epoll 的接口3 epoll工作原理4 实现epollserverV1 1 epoll的作用和定位 之前提过的多路转接方案select和poll 都有致命缺点…

CSS中常见的两列布局、三列布局、百分比和多行多列布局!

目录 一、两列布局 1、前言&#xff1a; 2. 两列布局的常见用法 两列布局的元素示例&#xff1a; 代码运行后如下&#xff1a; 二、三列布局 1.前言 2. 三列布局的常见用法 三列布局的元素示例&#xff1a; 代码运行后如下&#xff1a; 三、多行多列 1.前言 2&…

DCRNN解读(论文+代码)

一、引言 作者首先提出&#xff1a;空间结构是非欧几里得且有方向性的&#xff0c;未来的交通速度受下游交通影响大于上游交通。虽然卷积神经网络&#xff08;CNN&#xff09;在部分研究中用于建模空间相关性&#xff0c;但其主要适用于欧几里得空间&#xff08;例如二维图像&a…

StandardThreadExecutor源码解读与使用(tomcat的线程池实现类)

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java源码解读-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 目录 1.前言 2.线程池基础知识回顾 2.1.线程池的组成 2.2.工作流程 2…

Unreal5从入门到精通之如何解决在VR项目在头显中卡顿的问题

前言 以前我们使用Unity开发VR,Unity提供了非常便利的插件和工具来做VR。但是由于Unity的渲染效果不如Unreal,现在我们改用Unreal来做VR了,所有的VR相关的配置和操作都要重新学习。 今天就来总结一下,我在开发VR过程中碰到的所有问题。 1.编辑器,以VR运行 默认运行方式…

centos7 kafka高可用集群安装及测试

前言 用三台虚拟机centos7 搭建高可用集群&#xff0c;及测试方法 高可用搭建的方法&#xff0c;参考&#xff1a;https://blog.csdn.net/u011197085/article/details/134070318 高可用搭建 1、安装配置zookeeper集群 下载zookeeper 注&#xff1a;zookeeper链接如果失效&a…

Redis(2):内存模型

一、Redis内存统计 工欲善其事必先利其器&#xff0c;在说明Redis内存之前首先说明如何统计Redis使用内存的情况。 在客户端通过redis-cli连接服务器后&#xff08;后面如无特殊说明&#xff0c;客户端一律使用redis-cli&#xff09;&#xff0c;通过info命令可以查看内存使用情…

C++笔试题之实现一个定时器

一.定时器&#xff08;timer&#xff09;的需求 1.执行定时任务的时&#xff0c;主线程不阻塞&#xff0c;所以timer必须至少持有一个线程用于执行定时任务 2.考虑到timer线程资源的合理利用&#xff0c;一个timer需要能够管理多个定时任务&#xff0c;所以timer要支持增删任务…

0.STM32F1移植到F0的各种经验总结

1.结构体的声明需放在函数的最前面 源代码&#xff1a; /*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructu…

在Microsoft Outlook日历中添加多个时区

在Microsoft Outlook日历中添加多个时区 1.单击Outlook中的文件选项卡&#xff0c;单击选项 2.左侧菜单中选择日历 3.向下滚动到时区部分&#xff0c;并标记当前时区&#xff0c;比如China 4.选中“显示第二个时区”框 5.选择第二个时区并给它一个标签&#xff0c;比如Germa…

为啥学习数据结构和算法

基础知识就像是一座大楼的地基&#xff0c;它决定了我们的技术高度。而要想快速做出点事情&#xff0c;前提条件一定是基础能力过硬&#xff0c;“内功”要到位。 想要通关大厂面试&#xff0c;千万别让数据结构和算法拖了后腿 我们学任何知识都是为了“用”的&#xff0c;是为…

爬虫学习4

from threading import Thread#创建任务 def func(name):for i in range(100):print(name,i)if __name__ __main__:#创建线程t1 Thread(targetfunc,args("1"))t2 Thread(targetfunc, args("2"))t1.start()t2.start()print("我是诛仙剑")from …

【Maven】——基础入门,插件安装、配置和简单使用,Maven如何设置国内源

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 引入&#xff1a; 一&#xff1a;Maven插件的安装 1&#xff1a;环境准备 2&#xff1a;创建项目 二…

Vue中使用echarts生成地图步骤详解

1.创建容器元素 <div class"map" id"map" style"width:1000px;height:1000px;"></div> 2.Vue项目引入world.js(我这里的演示是世界地图&#xff0c;不同地图对应js文件不一样) world.js文件包含&#xff1a; 地理坐标数据&#xff…

docker安装低版本的jenkins-2.346.3,在线安装对应版本插件失败的解决方法

提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、网上最多的默认解决方法1、jenkins界面配置清华源2、替换default.json文件 二、解决低版本Jenkins在线安装插件问题1.手动下载插件并导入2.低版本jenkins在…