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



    🚩 Import

    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
    {
        /// 
        /// Proto通信协议类编译工具
        /// 
        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($"文件夹不存在 {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);
                }
            }
        }
    }
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    🪐 客户端接口

    • Connect: 连接服务端;
    /// 
    /// 连接服务端
    /// 
    /// 服务器IP地址
    /// 端口
    public void Connect(string ip, int port)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • Send:发送数据;
    /// 
    /// 发送数据
    /// 
    /// 协议
    public void Send(IExtensible proto)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • Close:关闭与服务端的连接;
    /// 
    /// 关闭连接
    /// 
    public void Close()
    
    • 1
    • 2
    • 3
    • 4

    🌈 服务端接口

    • 向单个客户端发送数据;
    /// 
    /// 向客户端发送协议(单点发送)
    /// 
    /// 客户端
    /// 协议
    public static void Send(Client client, IExtensible proto)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 向所有客户端发送数据;
    /// 
    /// 向所有客户端发送协议(广播)
    /// 
    /// 协议
    public static void Send(IExtensible proto)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 向指定客户端之外的所有客户端发送数据;
    /// 
    /// 向指定客户端之外的所有客户端发送协议
    /// 
    /// 协议
    /// 不需要发送的客户端
    public static void Send(IExtensible proto, Client except)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 关闭指定客户端的连接;
    /// 
    /// 关闭客户端连接
    /// 
    /// 客户端
    public static void Close(Client client)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    🧭 数据处理

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

    数据处理

    以上是服务端对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);
        }
    }
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    Example

    Unity Multiplayer 多人在线示例工程:https://github.com/136512892/Unity-Multiplayer

  • 相关阅读:
    【零基础学Python】Day11 Python条件控制
    Docker安装禅道
    [word] word带圈数字20以上 #笔记#笔记
    一条 sql 了解 MYSQL 的架构设计
    c++学习27qt(三)Qt自定义控件封装,事件处理,绘画和文件读写
    使用免费软件将数据从机械硬盘克隆到固态硬盘!
    C++11 新增特性
    网络基本概念
    java毕业设计——基于java+Socket+sqlserver的网络通信系统设计与实现(毕业论文+程序源码)——网络通信系统
    Java设计模式面试题和答案
  • 原文地址:https://blog.csdn.net/qq_42139931/article/details/128205617