Unity【Multiplayer 多人在线】服务端、客户端通用架构的使用指南

news/2024/7/7 19:33:06

文章目录

  • 🚩 Import
  • 🚀 protogen使用方法
  • 🪐 客户端接口
  • 🌈 服务端接口
  • 🧭 数据处理
  • 🎨 Example


🚩 Import

  • 下载SKFramework框架,导入到Unity中;

SKFramework

  • 在框架Package Manager中搜索并下载导入Socket模块;
    Package Manager
  • Package包中包含Server服务端内容以及protogen工具,将其解压到工程外;

Server和protogen

🚀 protogen使用方法

  • 编写的.proto文件放入proto文件夹中;

proto文件

  • 打开run.bat文件,编辑编译指令;

编译指令

  • 运行run.bat文件,生成后的.cs脚本在cs文件夹中,将其放入到Unity中即可;

编译成功

注:.proto文件编译为.cs脚本后,该脚本一般不轻易改动。

  • 如果有大量的.proto文件需要编译,编辑编译指令可能会比较繁琐,因此可以使用自定义的工具Protogen Helper来自动创建run.bat文件。

Protogen Helper

代码如下:

using System.IO;
using UnityEngine;
using UnityEditor;
using System.Text;
using System.Diagnostics;

namespace Mutiplayer
{
    /// <summary>
    /// Proto通信协议类编译工具
    /// </summary>
    public class ProtogenHelper : EditorWindow
    {
        [MenuItem("Multiplayer/Protogen Helper")]
        public static void Open()
        {
            var window = GetWindow<ProtogenHelper>("Protogen Helper");
            window.maxSize = new Vector2(1000f, 60f);
            window.minSize = new Vector2(200f, 60f);
            window.Show();
        }

        //根路径
        private string rootPath;
        private const string prefsKey = "Protogen.exe Path";

        private void OnEnable()
        {
            rootPath = EditorPrefs.HasKey(prefsKey) ? EditorPrefs.GetString(prefsKey) : string.Empty;
        }

        private void OnGUI()
        {
            GUILayout.Label("protogen.exe所在路径:");
            GUILayout.BeginHorizontal();
            {
                string path = GUILayout.TextField(rootPath);
                if (path != rootPath)
                {
                    rootPath = path;
                    EditorPrefs.SetString(prefsKey, rootPath);
                }
                if (GUILayout.Button("Browse", GUILayout.Width(55f)))
                {
                    path = EditorUtility.OpenFolderPanel("选择路径", rootPath, null);
                    if (path != rootPath)
                    {
                        rootPath = path;
                        EditorPrefs.SetString(prefsKey, rootPath);
                    }
                }
            }
            GUILayout.EndHorizontal();

            if (GUILayout.Button("Create .bat"))
            {
                string protoPath = rootPath + "/proto";
                if (!Directory.Exists(protoPath))
                {
                    UnityEngine.Debug.Log($"<color=red>文件夹不存在</color> {protoPath}");
                    return;
                }
                string csPath = rootPath + "/cs";
                //如果cs文件夹不存在则创建
                if (!Directory.Exists(csPath))
                {
                    Directory.CreateDirectory(csPath);
                }
                DirectoryInfo di = new DirectoryInfo(protoPath);
                //获取所有.proto文件信息
                FileInfo[] protos = di.GetFiles("*.proto");
                //使用StringBuilder拼接字符串
                StringBuilder sb = new StringBuilder();
                //遍历
                for (int i = 0; i < protos.Length; i++)
                {
                    string proto = protos[i].Name;
                    //拼接编译指令
                    sb.Append(rootPath + @"/protogen.exe -i:proto\" + proto + @" -o:cs\" + proto.Replace(".proto", ".cs") + "\r\n");
                }
                sb.Append("pause");

                //生成".bat文件"
                string batPath = $"{rootPath}/run.bat";
                File.WriteAllText(batPath, sb.ToString());
                //打开该文件夹
                Process.Start(rootPath);
            }
        }
    }
}

🪐 客户端接口

  • Connect: 连接服务端;
/// <summary>
/// 连接服务端
/// </summary>
/// <param name="ip">服务器IP地址</param>
/// <param name="port">端口</param>
public void Connect(string ip, int port)
  • Send:发送数据;
/// <summary>
/// 发送数据
/// </summary>
/// <param name="proto">协议</param>
public void Send(IExtensible proto)
  • Close:关闭与服务端的连接;
/// <summary>
/// 关闭连接
/// </summary>
public void Close()

🌈 服务端接口

  • 向单个客户端发送数据;
/// <summary>
/// 向客户端发送协议(单点发送)
/// </summary>
/// <param name="client">客户端</param>
/// <param name="proto">协议</param>
public static void Send(Client client, IExtensible proto)
  • 向所有客户端发送数据;
/// <summary>
/// 向所有客户端发送协议(广播)
/// </summary>
/// <param name="proto">协议</param>
public static void Send(IExtensible proto)
  • 向指定客户端之外的所有客户端发送数据;
/// <summary>
/// 向指定客户端之外的所有客户端发送协议
/// </summary>
/// <param name="proto">协议</param>
/// <param name="except">不需要发送的客户端</param>
public static void Send(IExtensible proto, Client except)
  • 关闭指定客户端的连接;
/// <summary>
/// 关闭客户端连接
/// </summary>
/// <param name="client">客户端</param>
public static void Close(Client client)

🧭 数据处理

根据解析出的协议名来调用相应的处理方法:

数据处理

以上是服务端对ProtoTest类型协议的处理示例,服务端通过Send将该消息转发给所有客户端。

🎨 Example

using UnityEngine;
using SK.Framework.Sockets;
using System.Collections.Generic;

 public class Example : MonoBehaviour
{
    private Vector2 scroll;
    private List<string> messages = new List<string>();
    private string content;
    private NetworkManager NetworkManager;

    private void OnGUI()
    {
        GUI.enabled = !NetworkManager.IsConnected;
        if (GUILayout.Button("Connect", GUILayout.Width(200f), GUILayout.Height(50f)))
        {
            NetworkManager.Connect("127.0.0.1", 8801);
        }
        GUI.enabled = NetworkManager.IsConnected;
        if (GUILayout.Button("Disconnect", GUILayout.Width(200f), GUILayout.Height(50f)))
        {
            NetworkManager.Close();
        }

        GUILayout.BeginVertical("Box", GUILayout.Height(200f), GUILayout.Width(300f));
        scroll = GUILayout.BeginScrollView(scroll);
        {
            for (int i = 0; i < messages.Count; i++)
            {
                GUILayout.Label(messages[i]);
            }
        }
        GUILayout.EndScrollView();
        GUILayout.EndVertical();

        GUILayout.BeginHorizontal();
        content = GUILayout.TextField(content, GUILayout.Height(50f));
        if (GUILayout.Button("Send", GUILayout.Width(50f), GUILayout.Height(50f)))
        {
            if (!string.IsNullOrEmpty(content))
            {
                ProtoBuf.IExtensible proto = new proto.ProtoTest.ProtoTest() { content = content };
                NetworkManager.Send(proto);
                content = string.Empty;
            }
        }
        GUILayout.EndHorizontal();
    }
    private void Start()
    {
        NetworkManager = GetComponent<NetworkManager>();
    }

    public void OnProtoTestEvent(proto.ProtoTest.ProtoTest protoTest)
    {
        messages.Add(protoTest.content);
    }
}

Example


http://lihuaxi.xjx100.cn/news/327512.html

相关文章

Java高效率复习-MySQL下篇[MySQL]

前言 本文章的语言描述会比上篇多一些 数据库的创建修改与删除 标识符命名规则 数据库名、表名不得超过30个字符&#xff0c;变量限制为29个必须只能包含A-Z&#xff0c;a-z&#xff0c;0-9&#xff0c;_等63个字符数据库名、表名、字段名等对象名中间不要包含空格同一个My…

超透镜与超表面全息

超透镜和超表面因其操纵电磁场的独特特性而在科学上声名鹊起&#xff0c;如今它们的制造已经变得可行。但它们的设计难度远远超过了传统镜片&#xff0c;因为必须考虑到纳米级构件的特性。 VirtualLab Fusion的优势  统一的平台&#xff1a;具有将纳米级构建模块和大尺…

CUDA入门和网络加速学习(一)

0. 简介 最近作者希望系统性的去学习一下CUDA加速的相关知识&#xff0c;正好看到深蓝学院有这一门课程。所以这里作者以此课程来作为主线来进行记录分享&#xff0c;方便能给CUDA网络加速学习的萌新们去提供一定的帮助。 1. GPU与CPU区别 处理器指标一般主要分为两大类&…

彻底搞懂MySql的B+Tree

1.什么是索引 官方定义&#xff1a;一种能为mysql提高查询效率的数据结构&#xff0c;索引是为了加速对表中数据行的检索而创建的一种分散存储的数据结构。好比如&#xff0c;一本书&#xff0c;你想找到自己想看的章节内容&#xff0c;直接查询目录就行。这里的目录就类似索引…

[附源码]计算机毕业设计勤工助学管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

(十四)笔记.net学习之RabbitMQ工作模式

RabbitMQ在.net中简单使用一、简单模式1.生产者2.消费者二、工作队列模式1.工作队列模式介绍2.生产者发送消息3.消费能力三、发布/订阅模式1.介绍2.生产者3.消费者四、Routing路由模式1.介绍2.生产着3.消费者五、topic 主题模式1.介绍2. 生产者3.消费者一、简单模式 1.生产者 …

手游平台开发怎么做?

根据相关的数据&#xff0c;目前手游市场的发展速度有所减缓&#xff0c;但依然有很大的发展空间。随着5 G时代的来临&#xff0c;再加上大家手里都有了手机&#xff0c;玩家数量也在不断增加&#xff0c;手游市场的发展前景也会越来越好&#xff0c;可以预见&#xff0c;这将会…

SD NAND 的 SDIO在STM32上的应用详解(中篇)

四.SDIO功能框图(重点) SDIO包含2个部分&#xff1a; ● SDIO适配器模块&#xff1a;实现所有MMC/SD/SD I/O卡的相关功能&#xff0c;如时钟的产生、命令和数据的传送。 ● AHB总线接口&#xff1a;操作SDIO适配器模块中的寄存器(由STM32控制SDIO外设)&#xff0c;并产生中断和…