unity3d:GameFramework+xLua+Protobuf+lua-protobuf,生成.cs,.pb工具流

news2024/11/28 13:38:08

概述

1.区分lua,cs用的proto
2.proto生成cs,使用protogen.exe,通过csharp.xslt修改生成cs样式
3.proto生成lua加载.pb二进制文件,并生成.pb列表文件,用于初始化加载
4.协议id生成cs,lua中枚举

区分cs,lua用proto

cs中序列化使用基于CSPacketBase,SCPacketBase的子类
lua中序列化使用lua-protobuf,需要提前把pb二进制文件加载
cs,lua中不通用协议类型,即某个协议类型只能在cs或者lua的一侧使用
使用两个文件夹区分,cs用的.proto放CS,lua用.proto放Lua文件夹下,在生成工具中分别处理

协议id生成cs,lua中

在NetMsgID.txt中填写所有lua,cs用的协议id(不区分lua用,还是cs用),例如

CSLogin = 100,
SCLogin = 101,
CSPlayerInfo = 102,
SCPlayerInfo = 103,
CSSelectCharacter = 104,
SCSelectCharacter = 105,

生成到对应的模板中,NetMsgIDTmpCS.cs

namespace Network
{
/// <summary>
/// 网络协议ID
/// </summary>
public enum NetMsgID
{
__Content__
}

}

NetMsgIDTmpLua.lua

--网络协议ID
NetMsgID = 
{
__Content__
}

替换__Content__,再复制到工程目录中

static void GenMsgID()
{
    string sMsgContent = File.ReadAllText(m_msgIDFullName);
    string sMsgCS = File.ReadAllText(m_msgIDTmpCSFullName);
    string sMsgLua = File.ReadAllText(m_msgIDTmpLuaFullName);
    sMsgCS = sMsgCS.Replace("__Content__", sMsgContent); 
    File.WriteAllText(m_msgIDCSFullName,sMsgCS);

    sMsgLua = sMsgLua.Replace("__Content__", sMsgContent);
    sMsgLua = sMsgLua.Replace("//", "--");
    File.WriteAllText(m_msgIDLuaFullName, sMsgLua);
}

proto生成cs

使用protogen.exe把.proto生成.cs文件

.net控制台遍历文件夹生成cs

protogen.exe单独使用如下,运行命令行,cd到protogen.exe的盘符,再cd 到protogen.exe的根目录下
把Person.proto放到protogen.exe的同级目录下。

protogen -i:Person.proto -o:Person.cs 

编写.net控制台程序执行
启动cmd并cd到protogen.exe根目录

using (Process p = new Process())
{
    //启动cmd,并设置好相关属性。
    p.StartInfo.FileName = "cmd.exe";
    p.StartInfo.UseShellExecute = false;    //是否使用操作系统shell启动
    p.StartInfo.RedirectStandardInput = true;//接受来自调用程序的输入信息
    p.StartInfo.RedirectStandardOutput = true;//由调用程序获取输出信息
    p.StartInfo.RedirectStandardError = true;//重定向标准错误输出
    p.StartInfo.CreateNoWindow = true;//不显示程序窗口
    p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
    p.Start();

    //cmd要到protogen.exe的根目录
    string[] arrDiskName = Directory.GetCurrentDirectory().Split(':');
    string diskName = arrDiskName[0] + ":";
    p.StandardInput.WriteLine(diskName);

    string cdPath = "cd " + m_protoRootPath;
    p.StandardInput.WriteLine(cdPath);

遍历proto/CS目录下proto文件生成到工程目录

//遍历所有cs文件下proto
var files = new DirectoryInfo(m_protoPathCS).GetFiles("*.proto", SearchOption.AllDirectories);
foreach (var f in files)
{
    string fileName = f.Name.Replace(".proto", "");
    //生成cs
    {
        string sIn = " -i:proto/CS/" + f.Name;
        string sOut = " -o:" + m_csPath  + fileName + ".cs";
        string sCmd = protogenPath + sIn + sOut;
        p.StandardInput.WriteLine(sCmd);
    }
}

csharp.xslt修改输出cs样式

XSL 指扩展样式表语言(EXtensible Stylesheet Language), 它是一个 XML 文档的样式表语言。
XSLT 指 XSL 转换
通过 XSLT,您可以向输出文件添加元素和属性,或从输出文件移除元素和属性。

在csharp.xslt中载入自定义.xslt

  <xsl:import href="custom.xslt"/>

修改基类名

csharp.xslt中增加自定义函数getBaseClassName

public partial class <xsl:call-template name="pascal"/> <xsl:call-template name="getBaseClassName"> </xsl:call-template> 

custom.xslt中实现getBaseClassName

 <!-- 定义获取基类名方法 -->
  <xsl:template name="getBaseClassName">
 	 <xsl:variable name = "className" ><xsl:call-template name="pascal"/></xsl:variable>
     <xsl:choose >
     	<xsl:when test="starts-with($className,'SC')"> : SCPacketBase</xsl:when>
     	<xsl:when test="starts-with($className,'CS')"> : CSPacketBase</xsl:when>
     	<xsl:otherwise> 
		</xsl:otherwise>
     </xsl:choose>
  </xsl:template>

如果SC开头的类,增加基类为SCPacketBase,服务器给客户端包
如果SC开头的类,增加基类为CSPacketBase,客户端给服务器包

增加Clear函数

csharp.xslt中增加自定义函数methodClear

<xsl:call-template name = "methodClear" ></xsl:call-template>

custom.xslt中实现methodClear

<!-- 是否显示Clear方法模版 -->
  <xsl:template name="methodClear">
 	 <xsl:variable name = "className" ><xsl:call-template name="pascal"/></xsl:variable>
     <xsl:choose>
     	<xsl:when test="starts-with($className,'SC') or starts-with($className,'CS')"> 
	//网络协议Id
	public override int Id 
	{ 
		get {return (int)Network.NetMsgID.<xsl:value-of select="$className"/>;} 
	} 

	//回到引用池,变量设置初始化。如果是引用型成员变量也要回到引用池
	public override void Clear()
	{
//<xsl:value-of select="$className"/>Clear
	}
     	</xsl:when>
     	<xsl:otherwise>
	//回到引用池,变量设置初始化。如果是引用型成员变量也要回到引用池
	public override void Clear()
	{
//<xsl:value-of select="$className"/>Clear
	}		
		</xsl:otherwise>
     </xsl:choose>
  </xsl:template>

协议类CS,SC开头类,子结构类都是基于引用池,需要实现Clear(),作用是回到引用池时,需要把变量置为初始值,这里先写入注释//className,等待cs进入unity工程时,通过正则再进一步处理
SC,CS协议类需要实现协议ID,这里对应NetMsgID.txt一一对应,例如协议类名为CSLogin,那么NetMsgID.txt有条内容为CSLogin = 100

正则表达式填充Clear中类成员设置默认值

上一步生成的Clear内容为

	public override void Clear()
	{
//CSLgoin
	}

需要对上一步生成Clear函数内填充内容,把类中成员设置为默认值,例如CSLogin填充为

	public override void Clear()
	{
		_account = "" ;
		_password = "" ;

	}

用正则匹配文本规则添加
1.遍历所有packet.cs文件
2.一个packet.cs文件中匹配类名@"public partial class (\w+) : ",一个可能包含1至N个Class。例如包含ClassA,ClassB
3.提取文件中类名开始到Clear结尾时一个类的部分,例如ClassA

    public static string GetClassContent(string fileContent,string className)
    {
        string classContent = "";
        string pattern = "(?s)public partial class " + className + "(.*?)" + className + "Clear";//(?s):这是一个正则表达式的选项,称为“单行”模式(single-line mode),它使 . 匹配所有字符,包括换行符。
        Debug.Log(pattern);
        // // 创建正则表达式对象,使用 RegexOptions.Multiline 选项
        Regex regex = new Regex(pattern, RegexOptions.Multiline);

        // 执行匹配
        Match match = regex.Match(fileContent);

        if (match.Success)
        {
            classContent = match.Groups[1].Value;
            Debug.Log(match.Groups[1].Value);
        }

        return classContent;
    }

4.ClassA中获取private变量,进行取的类型,默认值进行替换

    public static string GetClassMemberClear(string classContent)
    {
        StringBuilder sbClear = new StringBuilder();
        string pattern = "private (.*?);";
        Regex regex = new Regex(pattern);

        // 执行匹配
        MatchCollection matches = regex.Matches(classContent);

        // 遍历所有匹配项
        foreach (Match match in matches)
        {
            // 检查是否有成功的匹配
            if (match.Success)
            {
                // 提取匹配的类名(捕获组1)
                string sMember = match.Groups[1].Value;
                Debug.Log("找到成员:" + sMember);  //string _account = ""
                
                string[] arrMember = sMember.Split(' ');
                string sType = arrMember[0];
                string sVariant = arrMember[1];
                //非引用池类型
                string clear = "";
                for (int i = 1; i < arrMember.Length; i++)
                {
                    clear += arrMember[i];
                    clear += " ";
                }

                clear = clear + ";" + "\n";
                sbClear.Append("		" + clear);
            }
        }
        string sClear = sbClear.ToString();
        Debug.Log($"Clear中{sClear}");
        return sClear;
        
    }

注意,SC,CS类已经是引用池类,类中间不能再嵌套引用池为类成员

可以使用unity监听导入资源,对上一步产生的CS类再加工处理

    private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    {
        if (m_isEnable == false)
        {
            //防止一边导入,一边处理又一边导入了
            return;
        }
        m_isEnable = false;
        for (int i = 0; i < importedAssets.Length; i++)
        {
            string path = importedAssets[i];
            if (path.StartsWith("Assets/GameMain/Scripts/Network/Protobuf/ProtoCS"))
            {
                //对生成cs类,处理clear函数
                PacketGenClear.GenClear2ProtoCS(path);
            }
        }
        m_isEnable = true;
    }

Protoc生成pb二进制文件

使用protoc.exe把.proto生成.pb二进制文件,用于lua中加载
基本使用

protoc -o addressbook.pb addressbook.proto

遍历文件夹生成.pd到工程目录

var luaProtoFiles = new DirectoryInfo(m_protoPathLua).GetFiles("*.proto", SearchOption.AllDirectories);
//遍历所有luaproto
foreach (var f in luaProtoFiles)
{
    string fileName = f.Name.Replace(".proto", "");
    //生成pb
    {
        string sIn = " proto/Lua/" + f.Name;
        string spbFullName = m_pbPath + fileName + ".pb";
        string sOut = " -o " + spbFullName;
        string sCmd = protocPath + sOut + sIn;
        p.StandardInput.WriteLine(sCmd);
    }
    m_sbProtoList.AppendLine(fileName);
}

生成pd列表文件

File.WriteAllText(m_protoListFullName, m_sbProtoList.ToString());

执行效果

在这里插入图片描述

生成cs类

[global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"CSLogin")]
public partial class CSLogin : CSPacketBase
{
public CSLogin() {}


private string _account = "";
[global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"account", DataFormat = global::ProtoBuf.DataFormat.Default)]
[global::System.ComponentModel.DefaultValue("")]
public string account
{
  get { return _account; }
  set { _account = value; }
}

private string _password = "";
[global::ProtoBuf.ProtoMember(2, IsRequired = false, Name=@"password", DataFormat = global::ProtoBuf.DataFormat.Default)]
[global::System.ComponentModel.DefaultValue("")]
public string password
{
  get { return _password; }
  set { _password = value; }
} 
	//网络协议Id
	public override int Id 
	{ 
		get {return (int)Network.NetMsgID.CSLogin;} 
	} 

	//回到引用池,变量设置初始化。如果是引用型成员变量也要回到引用池
	public override void Clear()
	{
		_account = "" ;
		_password = "" ;

	}
 	
}

生成CS,pd流程图
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java Web学习笔记20——Ajax-Axios

Axios&#xff1a; 介绍&#xff1a;Axios对原生的Ajax进行封装&#xff0c;简化书写&#xff0c;快速开发。 官网&#xff1a;https://www.axios-http.cn Axios 入门&#xff1a; {}是Js的对象。 get的请求参数是在URL后面&#xff1f;和相关参数值。 post的请求参数是在请…

【线性代数】向量空间,子空间

向量空间 设V为n维向量的集合&#xff0c;如果V非空&#xff0c;且集合V对于向量的加法以及数乘两种运算封闭&#xff0c;那么就称集合V为向量空间 x&#xff0c;y是n维列向量。 x 向量组等价说明可以互相线性表示 向量组等价则生成的向量空间是一样的 子空间 例题18是三位向…

172.二叉树:左叶子之和(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr, right(nullptr) {}* Tree…

Zemax中FFT PSF和惠更斯PSF的区别?

在Zemax“分析”选项卡中&#xff0c;有PSF&#xff08;“点扩散函数”&#xff09;图&#xff0c;主要包括如下两种计算方式&#xff1a; 1. FFT PSF&#xff0c;快速傅里叶变换&#xff08;fast fourier transform&#xff0c;FFT&#xff09; 该方法可以看做是以下点扩散函…

React 为什么组件渲染了两次,原因为何,如何解决? React.StrictMode

文章目录 Intro官网解释解决 Intro 我在用 react 写一个 demo &#xff0c;当我在某个自定义组件的 return 语句之前加上一句log之后&#xff0c;发现&#xff1a;每次页面重新渲染&#xff0c;该行日志都打印了两次&#xff01; 慌&#xff01;难道我的自定义组件哪里写得有问…

开源多平台AI音乐生成器本地安装结合cpolar内网穿透实现远程访问

文章目录 前言1. 本地部署2. 使用方法介绍3. 内网穿透工具下载安装4. 配置公网地址5. 配置固定公网地址 前言 本文主要介绍如何在Windows系统电脑上快速本地部署一个文字生成音乐的AI创作工具MusicGPT&#xff0c;并结合cpolar内网穿透工具实现随时随地远程访问使用。 MusicG…

2024年6月8日 (周六) 叶子游戏新闻

万能嗅探: 实测 网页打开 某视频号、某音、某红薯、某站&#xff0c;可以做到无水印的视频和封面下载功能哦&#xff0c;具体玩法大家自行发挥吧。 《丝之歌》粉丝又要失望&#xff1a;大概率不会亮相Xbox发布会即将于后天举行的 Xbox 发布会预计将会有许多令人兴奋的消息。早些…

AI大模型时代,帆软引领对话式业务分析变革

大数据产业创新服务媒体 ——聚焦数据 改变商业 试想一下&#xff0c;假如用户完全不用懂技术&#xff0c;也不需要懂什么数据分析技巧&#xff0c;就可以随心所欲的进行数据分析&#xff0c;该多好。现在&#xff0c;有一个工具可以实现这个设想&#xff0c;那就是基于大模型…

【第13章】SpringBoot实战篇之项目部署

文章目录 前言一、准备1. 引入插件2. 打包3. 启动4. 后台启动 二、跳过测试模块三、外置配置文件1.引入插件2.忽略配置文件3. 外置配置文件 总结 前言 项目部署需要把项目部署到Linux服务器上&#xff0c;SpringBoot项目通过Maven打包即可快速生成可运行Jar包程序。 一、准备 …

SAP PP学习笔记18 - MTO(Make-to-Order):按订单生产(受注生産) 的策略 20,50,74

前面几章讲了 MTS&#xff08;Make-to-Stock&#xff09;按库存生产的策略&#xff08;10&#xff0c;11&#xff0c;30&#xff0c;40&#xff0c;70&#xff09;。 SAP PP学习笔记14 - MTS&#xff08;Make-to-Stock) 按库存生产&#xff08;策略10&#xff09;&#xff0c;…

Anaconda3 下载安装卸载

1、下载 官网链接&#xff1a;Download Now | Anaconda Step1&#xff1a;进入官网 Anaconda | The Operating System for AI Step2&#xff1a;进入下载页面&#xff0c;选择要Anaconda软件安装包 2、安装 Step1: 点击 Anaconda3-2024.02-1-Windows-x86_64.exe 安装包进行安…

基于学习模型的可学习小波变换方法(Pytorch)

首先以图像编码为例进行说明。 图像编码是一个复杂的系统&#xff0c;通常包含多个模块&#xff0c;其中变换模块具有重要作用。小波变换在图像编码领域得到了广泛的应用&#xff0c;例如著名的JPEG 2000就是一种小波图像编码方法。然而&#xff0c;现阶段的小波图像编码方法与…

武汉理工大学嵌入式系统应用之临时抱佛脚复习

其实大学很多课程的期末冲刺复习非常简单&#xff0c;就是在大脑中构建一个redis数据库就行了&#xff0c;缓存下一大堆键值对&#xff0c;然后考试的时候输出&#xff0c;很没意思。 嵌入式系统的定义 以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软件硬件可裁剪…

VSCode调试揭秘:Live Server助力完美测试Cookie与Session,远超“Open in Browser“!

文章目录 一、项目场景&#xff1a;二、问题描述1. open in browser&#xff1a;2. open with live server 三、原因分析&#xff1a;先了解一下open in browser和open with live server的区别两者的优缺点open in browseropen with live server 四、解决方案&#xff1a;总结 …

开发网站,如何给上传图片的服务器目录授权

开发网站&#xff0c;上传图像时提示”上传图片失败&#xff0c;Impossible to create the root directory /var/www/html/xxxxx/public/uploads/avatar/20240608.“ 在Ubuntu上&#xff0c;你可以通过调整文件夹权限来解决这个问题。首先&#xff0c;确保Web服务器&#xff08…

173.二叉树:找树左下角的值(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr, right(nullptr) {}* Tree…

数据集(图片)求均值和标准差

数据集&#xff08;图片&#xff09;求均值和标准差 一、环境配置 运行一下命令完成环境配置 pip install opencv-python numpy二、源代码 import os import cv2 import numpy as npdef compute_mean_std(Img_folders):all_file []for images_path in Img_folders:all_file…

攻防世界---misc---What-is-this

1、下载附件&#xff0c;是一个.gz的文件夹&#xff0c;是linux系统的压缩包后缀 2、在kali中解压&#xff0c;解压之后得到两张图片 3、想把图片拖在物理机中分析&#xff0c;但是拖不了&#xff0c;所以将.gz文件在物理机中改为.zip&#xff0c;解压之后看到了一个没有后缀的…

Java从入门到放弃

线程池的主要作用 线程池的设计主要是为了管理线程&#xff0c;为了让用户不需要再关系线程的创建和销毁&#xff0c;只需要使用线程池中的线程即可。 同时线程池的出现也为性能的提升做出了很多贡献&#xff1a; 降低了资源的消耗&#xff1a;不会频繁的创建、销毁线程&…

Windows 10 找不到Microsoft Edge 浏览器

下载链接 了解 Microsoft Edge 手动下载浏览器 问题说明 一般来说&#xff0c;windows10系统应该是自带浏览器edge的&#xff0c;但有的电脑就是没有找到edge浏览器&#xff0c;可能系统是精简过的&#xff0c;可能是被卸载了。如下&#xff0c;控制面板确实没找到程序。 ​ …