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

Unity之NetCode for GameObjets 基本使用

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

说明

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

思路

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

  1. 当我某一个客户端上线如何将自身的信息同步给其它在线用户?
  2. 建立连接后,消息状态是如何同步的?

带着疑问继续往下走

首先开启主机/服务器/客户端非常简单,只需要对应调用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
{
    //客户端id
    public ulong id;
    //网络标识id
    public 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);
    }

    //延时等待 获取NetworkObjectId
    IEnumerator 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;
            else
                AddPlayer(info);
            
        }
    }

    [ServerRpc(RequireOwnership =false)]
    void UpdatePlayerInfoServerRpc(PlayerInfo info)
    {
        if (IsServer)
        {
            if (allPlayerInfos.ContainsKey(info.id))
                allPlayerInfos[info.id] = info;
            else
                AddPlayer(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组件


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【单机游戏】红色警戒游戏介绍和玩法

平地一声惊雷&#xff0c;金将军居然发射了洲际导弹&#xff0c;虽然我们不能亲自体验&#xff0c;但是我们可以自己在游戏中体验一把&#xff0c;今天就介绍一个很多80 90都玩过的即时战略游戏-红色警戒 https://pan.quark.cn/s/7aca45fa3dd7 红色警戒&#xff08;Red Alert …

【Python各个击破】matplotlib

导入 import matplotlib.pyplot as plt import numpy as np用法 # 根据x,y数组作图 fig, ax plt.subplots() ax.plot([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,…

JavaEE初阶---网络原理之TCP篇(二)

文章目录 1.断开连接--四次挥手1.1 TCP状态1.2四次挥手的过程1.3time_wait等待1.4三次四次的总结 2.前段时间总结3.滑动窗口---传输效率机制3.1原理分析3.2丢包的处理3.3快速重传 4.流量控制---接收方安全机制4.1流量控制思路4.2剩余空间大小4.3探测包的机制 5.拥塞控制---考虑…

ssm022房屋租售网站的设计与实现+jsp(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;房屋租售网站的设计与实现 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为…

Knife4j配置 ▎使用 ▎教程 ▎实例

knife4j简介 支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。 提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试.参数和格式都…

CSS flex布局- 最后一个元素占满剩余可用高度转载

效果图 技术要点 height父元素必须有一个设定的高度flex-grow: 1 flex 盒子模型内的该元素将会占据父容器中剩余的空间F12检查最后一行的元素&#xff0c;高度就已经改变了&#xff1b;

虚拟环境设置成kernel来解决一些jupyter报错问题

1. 下面提到的问题应该是不同环境&#xff08;base、虚拟环境&#xff09;的区别&#xff0c;而不是python版本的区别。 2. 这个方法起到了比较好的效果&#xff0c;但是底层的逻辑还没太明白&#xff0c;有时间继续研究下。 3. 最终的结果好像是pycharm、anaconda用的python…

(五)Web前端开发进阶2——AJAX

目录 1.Ajax概述 2.Axios库 3.认识URL 4.Axios常用请求方法 5.HTTP协议——请求报文/响应报文 6.前后端分离开发 7.Element组件库 1.Ajax概述 AJAX 是异步的 JavaScript和XML(Asynchronous JavaScript And XML)。简单点说&#xff0c;就是使用XMLHttpRequest 对象与服务…

揭秘PyInstaller:Python应用打包的瑞士军刀

文章目录 **揭秘PyInstaller&#xff1a;Python应用打包的瑞士军刀**1. 背景介绍&#xff1a;为何选择PyInstaller&#xff1f;2. PyInstaller究竟是什么&#xff1f;3. 如何安装PyInstaller&#xff1f;4. PyInstaller的简单使用方法4.1 打包单个Python脚本4.2 生成单个可执行…

Spring Boot 创建项目详细介绍

上篇文章简单介绍了 Spring Boot&#xff08;Spring Boot 详细简介&#xff01;&#xff09;&#xff0c;还没看到的读者&#xff0c;建议看看。 下面&#xff0c;介绍一下如何创建一个 Spring Boot 项目&#xff0c;以及自动生成的目录文件作用。 Maven 构建项目 访问 http…

机器学习——解释性AI(Explainable AI)

机器学习——解释性AI&#xff08;Explainable AI&#xff09; 解释性AI&#xff08;Explainable AI&#xff09;——让机器学习模型更加透明与可信什么是解释性AI&#xff1f;解释性AI的常见方法示例代码&#xff1a;使用SHAP解释随机森林模型示例代码&#xff1a;使用LIME解释…

开源一款基于 JAVA 的仓库管理系统,支持三方物流和厂内物流,包含 PDA 和 WEB 端的源码

大家好&#xff0c;我是一颗甜苞谷&#xff0c;今天分享一款基于 JAVA 的仓库管理系统,支持三方物流和厂内物流,包含 PDA 和 WEB 端的源码。 前言 在当前的物流仓储行业&#xff0c;企业面临着信息化升级的迫切需求&#xff0c;但往往受限于高昂的软件采购和维护成本。现有的…

Tomcat servlet response关于中文乱码的经验

前言 最近修改老项目项目&#xff0c;使用zuul网关返回的中文内容乱码了&#xff0c;如果使用GBK或者GB2312编码确正常显示&#xff0c;稍微实验了一下&#xff0c;发现里面很多细节&#xff0c;毕竟Springboot对我们做了很多事情&#xff0c;而且当我们使用不同的模式会出现很…

【原创分享】详述中间件的前世今生

中间件是一种软件组件&#xff0c;位于应用程序和操作系统之间&#xff0c;通过提供统一的接口和功能来简化开发和管理应用程序、提高应用程序的可靠性和性能。 中间件的前世可以追溯到20世纪80年代的分布式系统和网络技术的发展。在那个时候&#xff0c;随着计算机网络的普及…

JAVA力扣每日一题:P198. 打家劫舍

本题来自&#xff1a;力扣-每日一题 力扣 (LeetCode) 全球极客挚爱的技术成长平台https://leetcode.cn/ 题解&#xff1a; class Solution {public int rob(int[] nums) {int len nums.length;if(len 0)return 0;if(len 1)return nums[0];int first nums[0];int second …

Nuxt.js 应用中的 components:dirs 事件钩子详解

title: Nuxt.js 应用中的 components:dirs 事件钩子详解 date: 2024/10/31 updated: 2024/10/31 author: cmdragon excerpt: components:dirs 是 Nuxt.js 中的一个生命周期钩子,用于在 app:resolve 期间扩展自动导入组件的目录。通过这个钩子,开发者可以动态地添加新的组…

IDEA 好用的插件分享

IDEA 好用的插件分享 一、常用篇1. CamelCase&#xff08;大小写格式转换&#xff09;2. Translation &#xff08;翻译插件&#xff09;3. GitToolBox &#xff08;git工具箱&#xff09;4. CodeGlance Pro&#xff08;代码缩略图&#xff09;5. fittencode&#xff08;代码补…

蓝牙资讯|苹果AirPods Pro 2推出听力测试、助听器和听力保护等功能

苹果推送iOS 18.1 系统版本更新&#xff0c;AirPods Pro 2 用户也在 iOS 18.1 中获得了强大的新功能。 运行固件 7B19 的 AirPods Pro 2 用户&#xff0c;搭配 iOS 18.1 系统的 iPhone&#xff0c;将获得三项强大的听力健康功能&#xff1a;听力测试、助听器和听力保护。 听力…

计算机毕业设计Python+大模型股票预测系统 股票推荐系统 股票可视化 股票数据分析 量化交易系统 股票爬虫 股票K线图 大数据毕业设计 AI

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; Python大模型股票预测系统 …

旺季来临,沃尔玛下了血本和亚马逊竞争,将会员年费减半至49美元

沃尔玛于10月28日宣布&#xff0c;在假日季到来之前推出Walmart Plus会员服务&#xff0c;以50%的折扣缩小与竞争对手亚马逊Prime订阅服务之间的差距。 为了吸引正在应对高通胀的消费者&#xff0c;今年沃尔玛和其他美国品牌方提前推出促销活动&#xff0c;并增加更多优惠和折…