身为一个码农,写代码没有提示是最难受最影响效率的吧,偏偏lua就是这样的。目前,大多数的Unity游戏开发者都已经开始使用IntelliJ IDEA来写lua代码,很重要的一个原因就是IDEA中的EmmyLua插件(EmmyLua插件下载地址:https://emmylua.github.io/),使用这个插件可以极大的提高我们的开发效率。

我们自己写的业务逻辑添加emmylua注解很简单,但是如何在项目中生成带注解的proto提高我们的开发效率呢?这时我们就需要一个像导表工具一样的根据proto导出emmylua注解文件的工具。

写工具的时候我想到了两种使用方式:

1、用lua代码来写,然后通过bat来执行lua脚本

2、用C#来实现,写到项目统一的工具类里,方便其他人维护跟移植项目

基于我们项目的需求,我选择了方法2来实现。首先放上效果图:

测试用的proto:

import "commons.proto";
package com.gy.server.packet;option java_package = "com.gy.server.packet";
option java_outer_classname = "PbActivity";// -------------------------------测试注释-------------------------------//战斗状态
enum BattleStatus {NO_START = 1;                            //未开启BATTLE = 2;                              //战斗状态,允许玩家自由争夺位置SETTLEMENT = 3;                          //结算状态,做一系列结算操作,不允许玩家战斗RESET = 4;                               //重置状态SLEEP = 5;                               //休眠状态,只有控制器能唤醒CHANGE = 6;                              //切换状态
}//这是一段ActivityInfo 前置测试注释1
message ActivityInfo {//这是一段ActivityInfo 前置测试注释2optional ActivityModule module = 1;optional ActivityData data = 2;optional int64 startTime = 3; //活动开启时间optional int64 endTime = 4; //活动结束时间//这是一个注释optional BattleStatus battleStatus = 5; //枚举测试}//这是一段ActivityInfo 后置测试注释// ***************************测试注释***************************message ActivityModule {//这是一段测试注释optional int32 type = 1;//类型optional int32 activityId = 2;optional TaskActivityModule task = 10;
}message ActivityData {optional TaskActivityData tasks = 8;                                //任务活动
}//任务活动
message TaskActivityModule {repeated int32 receivedIds = 1; //已领取的奖励ID集合repeated TaskActivity tasks = 2; //任务进度repeated int32 receivedTaskIds = 3; //已接取任务ID集合message TaskActivity {optional int32 id = 1; //条目IDoptional int64 curProgress = 2; //当前进度optional int64 totalProgress = 3; //总进度}
}//任务活动数据
message TaskActivityData {repeated TaskActivityItem items = 1;message TaskActivityItem {optional int32 id = 1;optional int32 goalId = 2; //目标IDrepeated PbReward rewards = 3; //常规奖励optional int32 vipLimit = 4; //vip等级限制optional string jump = 5; //跳转数据optional bool isSelectReward = 6; //是否选择奖励optional int32 resetFrequency = 7;      //重置频率,1每日,2每周,3每月}
}

生成的注解文件:

--  -------------------------------测试注释-------------------------------
-- 战斗状态
---@class BattleStatus : nil
BattleStatus= {
NO_START = "NO_START"; --未开启
BATTLE = "BATTLE"; --战斗状态,允许玩家自由争夺位置
SETTLEMENT = "SETTLEMENT"; --结算状态,做一系列结算操作,不允许玩家战斗
RESET = "RESET"; --重置状态
SLEEP = "SLEEP"; --休眠状态,只有控制器能唤醒
CHANGE = "CHANGE"; --切换状态
}-- 这是一段ActivityInfo 前置测试注释1
-- 这是一段ActivityInfo 前置测试注释2
---@class ActivityInfo : nil
---@field public module ActivityModule
---@field public data ActivityData
---@field public startTime number@-- 活动开启时间
---@field public endTime number@-- 活动结束时间
-- 这是一个注释
---@field public battleStatus BattleStatus@-- 枚举测试local ActivityInfo = {}--  ***************************测试注释***************************
---@class ActivityModule : nil
-- 这是一段测试注释
---@field public type number@-- 类型
---@field public activityId number
---@field public task TaskActivityModule
local ActivityModule = {}---@class ActivityData : nil
---@field public tasks TaskActivityData@-- 任务活动
local ActivityData = {}-- 任务活动
---@class TaskActivityModule_TaskActivity : nil
---@field public id number@-- 条目ID
---@field public curProgress number@-- 当前进度
---@field public totalProgress number@-- 总进度
local TaskActivityModule_TaskActivity = {}---@class TaskActivityModule : nil
---@field public receivedIds number[]@-- 已领取的奖励ID集合
---@field public tasks TaskActivityModule_TaskActivity[]@-- 任务进度
---@field public receivedTaskIds number[]@-- 已接取任务ID集合local TaskActivityModule = {}-- 任务活动数据
---@class TaskActivityData_TaskActivityItem : nil
---@field public id number
---@field public goalId number@-- 目标ID
---@field public rewards PbReward[]@-- 常规奖励
---@field public vipLimit number@-- vip等级限制
---@field public jump string@-- 跳转数据
---@field public isSelectReward boolean@-- 是否选择奖励
---@field public resetFrequency number@-- 重置频率,1每日,2每周,3每月
local TaskActivityData_TaskActivityItem = {}---@class TaskActivityData : nil
---@field public items TaskActivityData_TaskActivityItem[]local TaskActivityData = {}

话不多说,直接把源码贴出来,该源码经过反复验证、测试,完全适用于各类复杂的PB结构:

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;public class PbUtils
{public static string PB_PROTOBUFF_FOLDER_PATH_KEY = "PB_PROTOBUFF_FOLDER_PATH_KEY";private static bool isCurEnum = false;private static List<string> curClsName = new List<string>();[MenuItem("Assets/PB生成lua %&i", false, 200)]public static void UpdatePBFromSVN(){string savedPath = EditorUserSettings.GetConfigValue(PB_PROTOBUFF_FOLDER_PATH_KEY);if (string.IsNullOrEmpty(savedPath))SetProtoBufPath();GenPbAPI();}//记录当前"程序文件/protobuf"的地址private static void SetProtoBufPath(){//protobuf文件夹的存放路径,根据需求来更改string defaultPbPath = Application.dataPath + "../../../../../程序文件/protobuf";string path = "";//如果默认地址存在,则自动保存默认地址为protobuf地址if (FileUtils.ExistsDirectory(defaultPbPath)){path = defaultPbPath;}else{path = EditorUtility.OpenFolderPanel("protobuf文件夹所在的本地目录", defaultPbPath, "");}if (!string.IsNullOrEmpty(path)){EditorUserSettings.SetConfigValue(PB_PROTOBUFF_FOLDER_PATH_KEY, path);Debug.Log("protobuf Path: " + path);}}static void GenPbAPI(){string[] paths = FileUtils.GetFiles(EditorUserSettings.GetConfigValue(PB_PROTOBUFF_FOLDER_PATH_KEY ), "*.proto");string emPbDirecPath = Application.dataPath + "..\\..\\EmmyluaAPI\\Pb";if (!FileUtils.ExistsDirectory(emPbDirecPath)){FileUtils.CreateDirectory(emPbDirecPath);}        List<string> list = new List<string>();List<string> finnalLines = new List<string>();for (var i = 0; i < paths.Length; i++){isCurEnum = false;curClsName.Clear();list.Clear();finnalLines.Clear();//当前的proto文件string[] lines = File.ReadAllLines(paths[i]);SplitLineList2Normal(lines , ref finnalLines);for (var j = 0; j < finnalLines.Count; j++){string finalLineStr = ExportPbToLuaStr(finnalLines[j]);if (!finalLineStr.Equals(string.Empty)){list.Add(finalLineStr);}}FileInfo info = new FileInfo(paths[i]);string fileName = info.Name.Replace(".proto", ".lua");string path = emPbDirecPath + "\\" + fileName;//是否只有主干项目才生成PB的注解
//            if (getIsMain)
//            {File.WriteAllLines(path, list.ToArray());
//            }}}static void SplitLineList2Normal(string[] lineList , ref List<string> finnalLines){string curContentTemp = String.Empty;Dictionary<int ,List<string>> cacheLinesDic = new Dictionary<int, List<string>>();Dictionary<int ,List<string>> finalLinesDic = new Dictionary<int, List<string>>();Dictionary<string ,string> replaceRuleDic = new Dictionary<string ,string>();string curClsName = string.Empty;int curClsIndex = 0;int totalClsCount = 0;for (int index = 0; index < lineList.Length; index++){curContentTemp = lineList[index];curContentTemp = curContentTemp.Replace("\t", " ");if (curContentTemp.Contains("{")){curClsIndex = curClsIndex + 1;//是否要替换内部类的名字string clsName = string.Empty;int msgTitleIndex = 0;if (curContentTemp.IndexOf("message") >= 0){msgTitleIndex = curContentTemp.IndexOf("message");clsName = curContentTemp.Substring(msgTitleIndex + 7, curContentTemp.IndexOf("{") - msgTitleIndex - 7 ).Trim();}else if(curContentTemp.IndexOf("enum") >= 0){msgTitleIndex = curContentTemp.IndexOf("enum");clsName = curContentTemp.Substring(msgTitleIndex + 4, curContentTemp.IndexOf("{") - msgTitleIndex - 4 ).Trim();}if (curClsIndex == 1){//缓存当前message或者enum的名字curClsName = clsName;replaceRuleDic.Clear();cacheLinesDic.Clear();finalLinesDic.Clear();}else{if (!string.IsNullOrEmpty(curClsName)){string nameNew = curClsName + "_" + clsName;curContentTemp = curContentTemp.Replace(clsName , nameNew);   //缓存替换规则if (replaceRuleDic.ContainsKey(clsName))replaceRuleDic[clsName] = nameNew;elsereplaceRuleDic.Add(clsName , nameNew);//当前类的缓存里是否有需要替换的类名foreach (KeyValuePair<int ,List<string>> item in cacheLinesDic){for (int itemIndex = 0; itemIndex < item.Value.Count; itemIndex++){string tempValueStr = item.Value[itemIndex];string[] valueStrList = tempValueStr.Split(' ');bool isContains = false;string ruleKey = string.Empty;string ruleValue= string.Empty;foreach (KeyValuePair<string, string> itemRule in replaceRuleDic){ruleKey = itemRule.Key;ruleValue = itemRule.Value;for (int ruleIndex = 0; ruleIndex < valueStrList.Length; ruleIndex++){if (valueStrList[ruleIndex].CompareTo(ruleKey) == 0){valueStrList[ruleIndex] = ruleValue;isContains = true;break;}}if (isContains)break;}//拼接回来字符串tempValueStr = string.Empty;for (int strIndex = 0; strIndex < valueStrList.Length; strIndex++){tempValueStr += valueStrList[strIndex] + " ";}item.Value[itemIndex] = tempValueStr;}}foreach (KeyValuePair<int ,List<string>> item in finalLinesDic){for (int itemIndex = 0; itemIndex < item.Value.Count; itemIndex++){string tempValueStr = item.Value[itemIndex];string[] valueStrList = tempValueStr.Split(' ');bool isContains = false;string ruleKey = string.Empty;string ruleValue= string.Empty;foreach (KeyValuePair<string, string> itemRule in replaceRuleDic){ruleKey = itemRule.Key;ruleValue = itemRule.Value;for (int ruleIndex = 0; ruleIndex < valueStrList.Length; ruleIndex++){if (valueStrList[ruleIndex].CompareTo(ruleKey) == 0){valueStrList[ruleIndex] = ruleValue;isContains = true;break;}}if (isContains)break;}//拼接回来字符串tempValueStr = string.Empty;for (int strIndex = 0; strIndex < valueStrList.Length; strIndex++){tempValueStr += valueStrList[strIndex] + " ";}item.Value[itemIndex] = tempValueStr;}}}}List<string> temp = new List<string>();temp.Add(curContentTemp);cacheLinesDic.Add(curClsIndex , temp);}else if (curContentTemp.Contains("}")){cacheLinesDic[curClsIndex].Add(curContentTemp);totalClsCount += 1;finalLinesDic[totalClsCount] = cacheLinesDic[curClsIndex];cacheLinesDic.Remove(curClsIndex);curClsIndex = curClsIndex - 1;if (curClsIndex == 0){foreach (KeyValuePair<int, List<string>> item in finalLinesDic){List<string> temp = item.Value;for (int cacheIndex = 0; cacheIndex < temp.Count ; cacheIndex++){finnalLines.Add(temp[cacheIndex]);                    }item.Value.Clear();}finalLinesDic.Clear();totalClsCount = 0;}}else{if (curClsIndex == 0){finnalLines.Add(curContentTemp);                    }else{//是否要替换类名string[] valueStrList = curContentTemp.Split(' ');bool isContains = false;string ruleKey = string.Empty;string ruleValue= string.Empty;foreach (KeyValuePair<string, string> itemRule in replaceRuleDic){ruleKey = itemRule.Key;ruleValue = itemRule.Value;for (int ruleIndex = 0; ruleIndex < valueStrList.Length; ruleIndex++){if (valueStrList[ruleIndex].CompareTo(ruleKey) == 0){valueStrList[ruleIndex] = ruleValue;isContains = true;break;}}if (isContains)break;}//拼接回来字符串curContentTemp = string.Empty;for (int strIndex = 0; strIndex < valueStrList.Length; strIndex++){curContentTemp += valueStrList[strIndex] + " ";}cacheLinesDic[curClsIndex].Add(curContentTemp);}}}}static string ExportPbToLuaStr(string str){if (string.IsNullOrEmpty(str))return string.Empty;if (str.Contains("import")|| str.Contains("package com.gy.server.packet;")|| str.Contains("option java_package")|| str.Contains("option java_outer_classname ")){return string.Empty;}if (str.Contains("{")){if (str.Trim().IndexOf("//") == 0){str = str.Trim().Replace("//", "-- ");return str;}//开始组建结构//是message还是enumbool isMsg = str.Contains("message");bool isEnum = str.Contains("enum");if (isMsg || isEnum){string curNote = String.Empty;if (str.Contains("//")){//截取服务器添加的注解curNote = str.Substring(str.IndexOf("//") + 2);str = str.Substring(0, str.IndexOf("//")); //向后截取没}string clsName = string.Empty;int msgTitleIndex = 0;if (isMsg){msgTitleIndex = str.IndexOf("message");clsName = str.Substring(msgTitleIndex + 7, str.IndexOf("{") - msgTitleIndex - 7 ).Trim();}else{msgTitleIndex = str.IndexOf("enum");clsName = str.Substring(msgTitleIndex + 4, str.IndexOf("{") - msgTitleIndex - 4 ).Trim();}if (!string.IsNullOrEmpty(curNote)){str ="-- " + curNote+"\n" + str;}if (isMsg){str = str.Replace("message", "---@class").Trim();str = str.Replace("{", ": nil").Trim();isCurEnum = false;}else{string newEnumName = clsName;if (curClsName.Count > 0)newEnumName = curClsName[0] + newEnumName;      str = "---@class " + newEnumName + " : nil\n" + newEnumName +"= {";isCurEnum = true;}curClsName.Add(clsName);}}else if (str.Contains("}")){if (str.Trim().IndexOf("//") == 0){str = str.Trim().Replace("//", "-- ");return str;}//组建结构结束if (!isCurEnum){string nameTemp = curClsName[curClsName.Count - 1];str = "local " + nameTemp + " = {}";}curClsName.RemoveAt(curClsName.Count - 1);if (str.Contains("//"))str = str.Replace("//", "-- ").Trim();str += "\n";isCurEnum = false;}else{if (isCurEnum){PbEnumContent2LuaStr(ref str);}else{PbMsgContent2LuaStr(ref str);}}return str;}//message内容生成注解static void PbMsgContent2LuaStr(ref string str){int indexTemp = str.Trim().IndexOf("//");string curNote = string.Empty;if (indexTemp >= 0){curNote =  str.Trim().Substring(indexTemp + 2);if (indexTemp == 0){str = "-- " + curNote;return;}}string[] arrSplit = str.Split(' ');int index = 0;int trueIndex = 0;bool isArr = false;for (var i = 0; i < arrSplit.Length; i++){string sp = arrSplit[i];index = index + 1;
//                    if (sp == "\t\trequired"
//                        || sp == "\t\toptional"
//                    )if (sp.Contains("required")|| sp.Contains("optional")){trueIndex = index;break;}else if (sp.Contains("repeated")){trueIndex = index;isArr = true;break;}}string fieldType = arrSplit[trueIndex];if (fieldType == "int32"|| fieldType == "int64"|| fieldType == "float"|| fieldType == "double"|| fieldType == "uint32"|| fieldType == "uint64"|| fieldType == "sint64"|| fieldType == "fixed32"|| fieldType == "fixed64"|| fieldType == "sfixde32"|| fieldType == "sfixde64"){fieldType = "number";}else if (fieldType == "bool"){fieldType = "boolean";}else if (fieldType == "bytes"){fieldType = "string";}if (isArr){fieldType += "[]";}string field = null;for (var i = trueIndex + 1; i < arrSplit.Length; i++){if (arrSplit[i] != string.Empty){field = arrSplit[i];break;}}if (!string.IsNullOrEmpty(field) && field != " "){str = string.Format("---@field public {0} {1}", field, fieldType);if (!string.IsNullOrEmpty(curNote)){str += "@-- " + curNote;}}}//枚举内容生成注解static void PbEnumContent2LuaStr(ref string str){if (string.IsNullOrEmpty(str.Trim())){return;}if (str.Trim().IndexOf("//") == 0){str = str.Trim().Replace("//", "-- ");return;}string curEnumName = str.Substring(0 ,str.IndexOf("=")).Trim();int indexTemp = str.IndexOf("//");if (indexTemp > 0){string curNote =  str.Substring(indexTemp + 2);str = curEnumName + " = \"" +curEnumName + "\"; --" + curNote;}else if( indexTemp == 0){str = str.Replace("//", "-- ").Trim();}else{str = curEnumName + " = \"" +curEnumName + "\";";}}}

把该脚本放到unity的Editor下,点击即可。

发现网上没有什么太多的ProtoBuf生成EmmyLua注解文件的代码,现有的几个也都是bug频出,兼容很差,故将我写的这一套代码贴出来了。

有需要的小伙伴可以自取哦。码字不易,记得给我点赞哟,你们的赞就是我坚持写博客的动力~

ProtoBuf生成EmmyLua注解API提示文件(支持复杂的嵌套结构)相关推荐

  1. 上期所API头文件一、ThostFtdcUserApiStruct.h---API结构体的定义及工作流程(源代码6.3.19版)

    结构体的定义及工作流程 一.API工作流程 1.1.MdApi 1.2.TraderApi 二. ThostFtdcUserApiStruct结构体的定义 三.源代码 一.API工作流程 1.1.Md ...

  2. 恢复账套提示文件上的媒体簇结构不正确_用友U8软件用友固定资产(账套数据结构不正确)...

    内存不能为"read"内存不能为"read" :329 近日不少网友都遇到了该内存不能为"read"的错误提示.希望以下文章能对大家有所帮助 ...

  3. 恢复账套提示文件上的媒体簇结构不正确_2012年初级会计电算化试题二及答案...

    2012年初级会计电算化试题二 一.单选题 1.设置(),不属于科目设置的内容. A.数量核算 B.外币核算 C.三栏式账户 D.多栏式账户 2.报表子系统生成的对内会计报表是() A.资产负债表 B ...

  4. 恢复账套提示文件上的媒体簇结构不正确_供应链3测试题及答案

    一.单择题 1.采购系统中不能提供的业务有: A.普通采购 B.委托代销 C.受托代销 D.直运业务 2.受托代销业务商品的所有权属于: A.委托方 B.受托方 3.某企业的销售业务员已经在系统中设置 ...

  5. dita文档_使用DITADoclet和DITA API专业化生成DITA Java™API参考文档

    dita文档 2009年12月11日修订说明:在" 目标"和" 安装org.dita.dost插件 "标题下添加了两个指向可下载资源的链接. 2014年3月7日 ...

  6. java 生成并覆盖文件,基于mybatis-plus生成不被覆盖的文件并支持swagger注解

    情况是这样的: 原本mybatis-plus的框架的模板是不支持swagger的注解的,需要手动写. 自己折腾了1个多小时,建立在mybatis-plus的基础上进行修改.可以选择生成文件时,不覆盖某 ...

  7. Android P MTK 文件管理器打开三方应用生成的文件,提示不支持文件格式。

    问题:在文件管理器打开三方应用生成的文件,提示不支持文件格式. 分析:先看下系统自带的应用生成的文件是否能正常使用.如:相机拍张照片,在filemanager里面打开,显示正常.log如下 06-03 ...

  8. 一款零注解API接口文档生成工具

    smart-doc是一款同时支持JAVA REST API和Apache Dubbo RPC接口文档生成的工具,基于接口源码来分析生成接口文档,不采用任何注解侵入到业务代码中.只需要按照java-do ...

  9. 想说爱你不容易 | 使用最小 WEB API 实现文件上传(Swagger 支持)

    前言 上回,我们使用最小 WEB API 实现文件上传功能(<想说爱你不容易 | 使用最小 WEB API 实现文件上传>),虽然客户端访问是正常的,但是当打开 Swagger 页面时,发 ...

  10. Jasper Report 6.8 根据后台数据生成动态报表(JRXML文件实现)(三)JRXML文件生成过程(支持json,bean,map list数据源)

    1.生成头信息及页面 protected Element createPageXmlFileRoot( ) {DftRptMaster dftRptMaster = rptInfo.getDftRpt ...

最新文章

  1. python散点图拟合曲线-python 绘制拟合曲线并加指定点标识的实现
  2. CVPR2019接收结果公布了,但CVPR 2018的那些论文都怎么样了?
  3. 自动化测试--实现一套完全解耦的测试框架(三)
  4. 微信小程序组件slider
  5. 金山实习周记(2)——沟通
  6. codeblocks12.11汉化方法(汉化包的使用)
  7. html页面跳转及回退的几种方式
  8. 微信SVG使用指南 01
  9. android scroller,深入理解Android中Scroller的滚动原理
  10. Easy Data Transform for mac (Excel和CSV编程文件转换工具) v1.11.1激活版
  11. C语言 八进制数转换为四进制
  12. 油溶性球形金纳米颗粒,CAS7440-57-5
  13. vscode 代码出现波浪线
  14. 计算机网络硬件脆弱性,计算机网络的脆弱性包括哪些
  15. 盘点那些具有特色的写作软件
  16. math库的Python实现原理(ln(x)运算)
  17. C++ opencv视频文件摄像头使用
  18. java jsp中的日历表,jsp日历表格怎么做
  19. 5个超好用的屏幕划词翻译软件,选中文字就能翻译
  20. GitHub 发生重大改变!国内网友:Yellow居然不限制,瞧不起我?

热门文章

  1. 数字信号处理(4)- 自适应滤波器
  2. JQuery视频总结
  3. linux测试sata硬盘读写速度
  4. 北斗时间周和GPS时间周计算,JAVA为例
  5. Oracle 系列 统计信息详解(Statistic)
  6. 大师级中国风复古景区网站设计及html前端源码
  7. 最近京东抢茅台的很火啊,但是必须要京东plus会员。天猫超市抢茅台插件来咯
  8. c4d打开没反应_野分享:一大波C4D插件的测试以及分享
  9. 机器学习中各分类算法的优缺点比较
  10. C语言编程学习必备的一些网站,干货收藏!