Unity【Multiplayer 多人在线】服务端、客户端通用架构的使用指南
2023-02-18 16:43:24 时间
? Import
•下载SKFramework[1]框架,导入到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
References
[1]
SKFramework: https://github.com/136512892/SKFramework
相关文章
- Java 相同类型强制转换异常
- 和付费网盘说再见,跟着本文自己起个网盘(Java 开源项目)
- 有了这个开源 Java 项目,开发出炫酷的小游戏好像不难?
- 超级好用的 Java 数据可视化库:Tablesaw
- 采用开源Zabbix+500块硬件平替5万块动环检测系统,实现UPS、温湿度、烟雾等数据采集、存储、告警、大屏展示
- 驱动开发:WinDBG 常用调试命令总结
- 中小企业快速合规,快速部署开源堡垒机TELEPORT
- 驱动开发:监控进程与线程对象操作
- 客快物流大数据项目(九十六):ClickHouse的VersionedCollapsingMergeTree深入了解
- 软件测试|selenium屏幕操作事件TouchActions
- FPS游戏:视场角矩阵的特点
- Path Finder for Mac(强大的文件管理工具)v2149中文激活版
- 零售行业R公司对接亚马逊Amazon Device EDI项目案例
- FPS 游戏:快速寻找基址的方法
- 7min到40s:SpringBoot启动优化实践
- 客快物流大数据项目(九十九):Clickhouse中update/delete的使用
- 手把手教你使用CNN进行交通标志识别(已开源)
- 软件测试|selenium三种等待方式
- FPS游戏:实现人物定点瞬移
- 人力资源行业数据特点解析