unity插件Excel转换Proto插件-ExcelToProtobufferTool
unity插件Excel转换Proto插件-ExcelToProtobufferTool
- **ExcelToProtobufTool 插件文档**
- **1. 插件概述**
- **2. 默认配置类:`DefaultIProtoPathConfig`**
- **属性说明**
- **3. 自定义配置类**
- **定义规则**
- **示例代码**
- **4. 使用方式**
- **4.1 默认路径**
- **4.2 自定义路径**
- **4.3 Excel 配置规则**
- **5. 注意事项**
- **6. 总结**
- Excel数值配置填充规则
- **Excel 文档配置规则说明(更新版)**
- **1. 概述**
- **2. 工作表页签命名规则**
- **3. 枚举表(_Enum 后缀)**
- **文件结构**
- **示例**
- **转换为 Excel 表格**
- **生成 Proto 文件**
- **生成 C# 脚本**
- **4. 类定义表(_Class 后缀)**
- **文件结构**
- **示例**
- **转换为 Excel 表格**
- **生成 Proto 文件**
- **生成 C# 脚本**
- **5. 列表类表**
- **文件结构**
- **示例**
- **转换为 Excel 表格**
- **生成 Proto 文件**
- **生成 C# 脚本**
- **6. 第一行词典 Key 的规则补充说明**
- **1. 规则说明**
- **2. 示例:地图 Map 配置**
- **3. 定义嵌套字典的 Key 和 Value**
- **4. 生成的数据结构**
- **5. 生成逻辑**
- **6. 总结**
- **7. 生成的解析脚本**
- **8. 注意事项**
- **7. 字典类型的规则补充说明**
- **1. Key 的类型限制**
- **字典字段的定义规则**
- **情况 1:`:` 前面是非 `:` 的字符**
- **情况 2:`:` 前面是 `::`**
- **通用规则**
- **示例详解**
- **示例 1:Value 为基础类型**
- **示例 2:Value 为枚举类型**
- **示例 3:Value 为自定义类型**
- **示例 4:自定义类型中嵌套列表类型**
- **示例 5:自定义类型中嵌套字典类型**
- **定义**
- 1. **`EffectItem` 类型的定义**
- 2. **`ItemPrices` 类型的定义**
- **数据**
- **最终生成的数据结构**
- **示例代码(C#)**
- **输出结果**
- **说明**
- **总结**
- **自定义类数据填充注意事项**
- **1. 数据填充必须与自定义类的成员个数对应且成员总数相同**
- **2. 数据填充顺序与 Proto 编码顺序一致**
- **3. 数据填充需要转义字符**
- **总结**
ExcelToProtobufTool 插件文档
1. 插件概述
ExcelToProtobufTool 是一个 Unity 插件,用于将 Excel 配置文件转换为 Protobuf 数据格式,并生成对应的 C# 脚本或 DLL 文件。通过配置类 DefaultIProtoPathConfig
,开发者可以自定义 Excel 文件路径、Protobuf 数据生成路径、DLL 生成路径等。
不支持.Net Standard,需要将功成切换到.Net Framework.
2. 默认配置类:DefaultIProtoPathConfig
属性说明
属性名称 | 类型 | 说明 |
---|---|---|
PackagesRootPathName | string | 包根目录名称,默认值为 "Packages" 。 |
PackagesFullName | string | 完整的包路径,组合了根目录名称和包名。 |
PackagesPath | string | 包路径的完整路径,通过 DirectoryInfo 获取。 |
IsDebug | bool | 是否启用调试模式,默认值为 true 。 |
IsUsedDLL | bool | 是否使用生成的 DLL 文件。true 为打包成 DLL,false 为生成 C# 脚本。 |
ExcelPath | string | Excel 文件路径,默认指向 Config/Excel/Game 。 |
GenerateProtoPath | string | Protobuf 文件路径,默认指向 Config/ProtoFiles 。 |
GenerateProtoDataPath | string | Protobuf 生成的 Data 文件路径,默认指向 Res/ProtoData 。 |
GenerateProtoCsRootPath | string | 生成的 Protobuf C# 脚本路径,默认指向 Assets/Scripts/ProtoCSharp 。 |
ProtoDllName | string | Protobuf 脚本生成的 DLL 文件名,默认值为 CompanyName.ProtoBuffData 。 |
GenerateCsCachePath | string | 脚本或 DLL 的缓存路径,默认指向 Library/ProtoCache 。 |
GenerateProtoDllPath | string | Protobuf 生成的 DLL 路径,默认指向 Assets/Plugins/ProtoBuffData 。 |
ProtocPath | string | Protobuf 文件解析工具(protoc )的路径,根据平台动态调整。 |
GoogleProtobufPath | string | Google Protobuf 库的 DLL 路径,默认指向插件内置的 Google.Protobuf.dll 。 |
3. 自定义配置类
定义规则
-
继承
DefaultIProtoPathConfig
:- 自定义配置类必须继承自
DefaultIProtoPathConfig
。
- 自定义配置类必须继承自
-
添加
[ExecuteInEditMode]
特性:- 确保配置在 Unity 编辑模式下生效。
-
实现静态构造函数:
- 在静态构造函数中注册自定义配置到
ProtoPathConfig.CurProtoPathConfig
。
- 在静态构造函数中注册自定义配置到
-
重写需要自定义的属性:
- 根据项目需求,重写以下常用属性:
ExcelPath
:Excel 文件路径。GenerateProtoDataPath
:Protobuf 数据比特流文件路径。IsUsedDLL
:是否使用生成的 DLL 文件。GenerateProtoDllPath
:Protobuf 生成的 DLL 路径。GenerateProtoCsRootPath
:Protobuf 生成的 C# 脚本路径。
- 根据项目需求,重写以下常用属性:
示例代码
using UnityEngine;
using HuaXianQu.ProtoBuffEx.Runtime;// 添加 [ExecuteInEditMode] 特性,使脚本在编辑模式下运行
[ExecuteInEditMode]
public class CustomProtoPathConfig : DefaultIProtoPathConfig
{// 静态构造函数,用于注册自定义配置static CustomProtoPathConfig(){// 自动注册自定义配置ProtoPathConfig.CurProtoPathConfig = new CustomProtoPathConfig();}// 自定义 Excel 文件路径public override string ExcelPath => $"{Application.dataPath}/../../Config/Excel/CustomGame";// 自定义 Protobuf 数据比特流文件路径public override string GenerateProtoDataPath => $"{Application.dataPath}/Res/CustomProtoData";// 启用 DLL 模式public override bool IsUsedDLL => true;// 自定义 Protobuf 生成的 DLL 路径public override string GenerateProtoDllPath => "Assets/Plugins/CustomProtoBuffData";// 自定义 Protobuf 生成的 C# 脚本路径public override string GenerateProtoCsRootPath => "Assets/Scripts/CustomProtoCSharp";
}
4. 使用方式
4.1 默认路径
如果不自定义配置类,插件将使用默认路径:
- Excel 文件路径:
$"{Application.dataPath}/../../Config/Excel/Game"
。 - Protobuf 数据路径:
$"{Application.dataPath}/Res/ProtoData"
。 - C# 脚本路径:
"Assets/Scripts/ProtoCSharp"
。
4.2 自定义路径
如果需要自定义路径,请按照以下步骤操作:
-
创建自定义配置类:
- 按照上述规则创建自定义配置类,并重写需要自定义的属性。
-
将自定义配置类放置在项目中:
- 将
CustomProtoPathConfig
类放置在项目的任意脚本文件夹中(如Assets/Scripts
)。
- 将
-
插件自动使用配置:
- 插件会自动调用静态构造函数,注册并使用自定义配置。
4.3 Excel 配置规则
具体请查看 Excel数值配置填充规则章节
-
Excel 文件格式:
-
使用 CSV 文件格式存储数据,方便生成 Excel 文件。
-
示例数据:
, 1,2,3,4,5,6,7,8,9 地图ID,地图名称,区域类型,怪物分布,背景音乐,地图等级,位置,大小,描述 int,string,string,string,string,int,string,string,string MapID,Name,RegionType,MonsterDistribution,BackgroundMusic,MapLevel,Location,Size,Description 1,新手村,安全区,"恶魔,魔王,沙虫,林,狼",轻松的背景音乐,1,"(0,0)",100x100,新手玩家的起点 2,黑暗森林,危险区,"哥布林、狼,沙虫",紧张的音乐,5,"(100,50)",200x150,充满危险的森林 3,地下城,副本,"骷髅战士,沙虫",神秘的背景音乐,10,"(-300,200)",150x100,隐藏着宝藏的地下城 4,主城,安全区,"恶魔,魔王,沙虫,林,狼",欢快的背景音乐,1,"(-500,500)",300x300,玩家聚集的主城 5,雪山,危险区,"雪狼,雪人,沙虫",寒冷的背景音乐,15,"(800,1000)",250x200,寒冷的雪山区域 6,沙漠,危险区,"沙虫,蝎子,毒蛇",炎热的背景音乐,20,"(1200,1500)",300x250,炎热的沙漠区域 7,沼泽,危险区,"沼泽怪,毒蛇",阴森的背景音乐,25,"(200,800)",180x150,危险的沼泽区域 8,火山,危险区,"火焰巨人,熔岩兽",炽热的背景音乐,30,"(1500,2000)",350x300,炽热的火山区域 9,精灵森林,安全区,"恶魔,魔王,沙虫,蝎子",自然的背景音乐,1,"(700,700)",200x200,精灵族的栖息地 10,地狱,副本,"恶魔,魔王,沙虫",恐怖的背景音乐,35,"(2500,3000)",400x350,魔王统治的地狱区域
-
-
创建 Excel 文件:
-
在
ExcelPath
指定的路径下创建 CSV 文件(如Map.csv
)。
-
将上述数据复制到 CSV 文件中。
-
注意:保存格式必须为 UTF-8 with BOM,步骤如下:
-
打开 CSV 文件。
-
选择
文件 -> 另存为
。 -
在保存对话框中,选择编码格式为 UTF-8 with BOM。
选择编码格式为 UTF-8 with BOM
-
保存文件。
-
-
-
转换为 Excel 文件:
- 使用 Excel 打开 CSV 文件,保存为
.xlsx
格式。
- 使用 Excel 打开 CSV 文件,保存为
-
生成 Protobuf 文件:
- 在 Unity 中,导航到菜单栏
Tools -> ExcelToCsharp
。 - 等待进度完成,插件将在配置目录中生成对应的 Protobuf 文件、C# 脚本或 DLL 文件。
- 成功后会弹出提示窗口:
- 在 Unity 中,导航到菜单栏
-
查看生成的数据:
- 生成的 Protobuf 数据将以 JSON 形式显示在 Unity 的属性面板中。
- 生成的 Protobuf 数据将以 JSON 形式显示在 Unity 的属性面板中。
5. 注意事项
-
路径配置:
- 确保自定义路径(如
ExcelPath
、GenerateProtoDataPath
等)在项目中存在且有效。
- 确保自定义路径(如
-
避免重复注册:
- 如果项目中存在多个自定义配置类,确保只有一个配置类被注册到
ProtoPathConfig.CurProtoPathConfig
,避免冲突。
- 如果项目中存在多个自定义配置类,确保只有一个配置类被注册到
-
编辑模式测试:
- 由于
[ExecuteInEditMode]
特性的存在,可以在 Unity 编辑器中直接测试配置是否生效,无需进入运行模式。
- 由于
测试实例
打开PackageManager导入实例
如下图
打开ProtoBuffDataTest.unity场景
主要测试代码ExcelWin.cs
运行结果
值得注意说明的是ExcelWin使用的Map.cs和MapItem使用的是案例里面的脚本。不是生成的脚本。原因是为了使测试用例运行正常。如果使用新编译的脚本或者dll请删除测试用例的脚本并修改HuaXianQu.ProtoBuffEx.Tests.Sample.asmdef
6. 总结
通过创建自定义配置类并重写相关属性,可以轻松配置 ExcelToProtobufTool 插件的行为。只需将自定义配置类放置在项目中,插件会自动使用自定义配置。结合 Excel 配置规则,开发者可以快速将 Excel 数据转换为 Protobuf 格式,并生成对应的 C# 脚本或 DLL 文件。
Excel数值配置填充规则
Excel 文档配置规则说明(更新版)
1. 概述
本文档详细说明了如何定义 枚举、类 和 列表类 的字段和数据,并生成对应的 Proto 文件 和 C# 脚本。通过遵循这些规则,您可以快速定义配置文件并生成代码。
2. 工作表页签命名规则
- 枚举表:以
_Enum
为后缀。- 示例:
ItemType_Enum
、EffectType_Enum
。
- 示例:
- 类定义表:以
_Class
为后缀。- 示例:
EffectItem_Class
、BackpackItem_Class
。
- 示例:
- 列表类表:直接使用大驼峰命名法,无需后缀。
- 示例:
Backpack
、Character
。
- 示例:
3. 枚举表(_Enum 后缀)
文件结构
- 第一列:枚举名称,使用大驼峰命名法(PascalCase)。
- 其他列:枚举值定义,格式为
枚举值名称:枚举值
。- 第一个枚举值必须为
0
。 - 枚举值名称使用大驼峰命名法(PascalCase)。
- 枚举值为整数,从
0
开始递增。
- 第一个枚举值必须为
示例
ItemType,Consumable:0,Equipment:1,QuestItem:2,Currency:3
EffectType,Heal:0,Buff:1,Poison:2
转换为 Excel 表格
ItemType | Consumable:0 | Equipment:1 | QuestItem:2 | Currency:3 |
---|---|---|---|---|
EffectType | Heal:0 | Buff:1 | Poison:2 |
生成 Proto 文件
enum ItemType {Consumable = 0;Equipment = 1;QuestItem = 2;Currency = 3;
}enum EffectType {Heal = 0;Buff = 1;Poison = 2;
}
生成 C# 脚本
public enum ItemType
{Consumable = 0, // 消耗品Equipment = 1, // 装备QuestItem = 2, // 任务物品Currency = 3 // 货币
}public enum EffectType
{Heal = 0, // 治疗Buff = 1, // 增益Poison = 2 // 中毒
}
4. 类定义表(_Class 后缀)
文件结构
- 第一列:类名,使用大驼峰命名法(PascalCase)。
- 其他列:成员定义,格式为
成员字段类型:成员字段名称:Proto文件字段编码
。- 支持基础类型、枚举类型、自定义类型、列表类型和字典类型。
示例
EffectItem,string:Name:1,int:Level:2,int:ID:3
BackpackItem,int:ItemID:1,string:ItemName:2,ItemType:ItemType:3,SellInfo:Sellable:4
SellInfo,bool:IsSellable:1,string:CurrencyType:2,PriceRange:Range:3
PriceRange,double:MinPrice:1,double:MaxPrice:2
ItemPrices,int:ItemID:1,"Dictionary<string,EffectItem>:Prices:2"
转换为 Excel 表格
EffectItem | string:Name:1 | int:Level:2 | int:ID:3 |
---|---|---|---|
BackpackItem | int:ItemID:1 | string:ItemName:2 | ItemType:ItemType:3 |
SellInfo | bool:IsSellable:1 | string:CurrencyType:2 | PriceRange:Range:3 |
PriceRange | double:MinPrice:1 | double:MaxPrice:2 | |
ItemPrices | int:ItemID:1 | Dictionary<string,EffectItem>:Prices:2 |
生成 Proto 文件
message EffectItem {string Name = 1;int32 Level = 2;int32 ID = 3;
}
message BackpackItem {int32 ItemID = 1;string ItemName = 2;ItemType ItemType = 3;SellInfo Sellable = 4;
}
message SellInfo {bool IsSellable = 1;string CurrencyType = 2;PriceRange Range = 3;
}message PriceRange {double MinPrice = 1;double MaxPrice = 2;
}
message ItemPrices {int32 ItemID = 1;map<string,EffectItem> Prices = 2;
}
生成 C# 脚本
public class EffectItem
{public string Name { get; set; } // Proto 编码: 1public int Level { get; set; } // Proto 编码: 2public int ID { get; set; } // Proto 编码: 3
}
public class BackpackItem
{public int ItemID { get; set; } // Proto 编码: 1public string ItemName { get; set; } // Proto 编码: 2public ItemType ItemType { get; set; } // Proto 编码: 3public SellInfo Sellable { get; set; } // Proto 编码: 4
}
public class SellInfo
{public bool IsSellable { get; set; } // Proto 编码: 1public string CurrencyType { get; set; } // Proto 编码: 2public PriceRange Range { get; set; } // Proto 编码: 3
}public class PriceRange
{public double MinPrice { get; set; } // Proto 编码: 1public double MaxPrice { get; set; } // Proto 编码: 2
}public class ItemPrices
{public int ItemID { get; set; } // Proto 编码: 1public MapField<string,EffectItem> Prices { get; set; } // Proto 编码: 2
}
5. 列表类表
文件结构
- 第一行:字段的附加属性(类似 C# 的特性),用
{}
括起来,包含多个键值对。- 示例:
{key;EffectName:1,lan:Public,DefaultValue:0}
。
- 示例:
- 第二行:字段的编码值,从
1
开始递增,表示生成 Proto 文件时变量对应的编码值。- 示例:
1,2,3,4,5,6,7,8,9,10,11,12,13
。
- 示例:
- 第三行:字段的功能描述,用中文简要说明每一列的作用。
- 示例:
唯一ID,物品名称,物品类型,最大堆叠数,是否可交易,是否可销毁,出售信息,出售价格列表,图标资源路径,描述,关联道具列表,效果列表,属性加成
。
- 示例:
- 第四行:字段类型,用于转换成 C# 类型。
- 支持类型:
- 基础类型:
int
、uint
、long
、ulong
、double
、float
、bool
、string
。 - 枚举类型:如
ItemType
。 - 自定义类型:如
EffectItem
。 - 列表类型:如
List<EffectItem>
。 - 字典类型:如
Dictionary<string, EffectItem>
。
- 基础类型:
- 支持类型:
- 第五行:字段属性名称(大驼峰命名),方便代码调用。
- 示例:
ItemID,ItemName,ItemType,MaxStack,Tradable,Destructible,Sellable,SellPrices,IconPath,Description,LinkedItemIDs,Effects,AttributeBonus
。
- 示例:
- 第六行及以后:具体数据,按照字段类型和属性名称逐行填写。
示例
"{key;EffectName:1,lan:Public,DefaultValue:0}",
1,2,3,4,5,6,7,8,9,10,11,12,13,14
Unique ID,Item Name,Item Type,Max Stack Size,Tradable,Destructible,Sell Info,Sell Price List,Icon Path,Description,Linked Item IDs,Effects,Attribute Bonuses,Package Info
int,string,ItemType,int,bool,bool,SellInfo,List<PriceInfo>,string,string,List<int>,"Dictionary<string, EffectItem>","Dictionary<string, double>","Dictionary<int, ItemPrices>"
ItemID,ItemName,ItemType,MaxStack,Tradable,Destructible,Sellable,SellPrices,IconPath,Description,LinkedItemIDs,Effects,AttributeBonus,Info
1,Small Healing Potion,Consumable,99,TRUE,TRUE,"{TRUE,Gold,{10.0,20.0}}","{{Gold,10.0},{Silver,20.0},{Copper,30.0}}",icons/potion_small.png,Restores a small amount of health,"{1,2,3}","Name:{{Heal,1,50},{Buff,2,10},{Poison,3,30}}","::{Consumable:1.5,Equipment:0.5,QuestItem:2.0}","::{1:{10,Name:{{Fireball,5,9},{Thunderstorm,5,9}}}, 2:{20,Name:{{Blink,5,9},{BlackHole,5,9}}}}"
2,Large Healing Potion,Consumable,99,TRUE,TRUE,"{TRUE,Diamond,{50.0,100.0}}","{{Diamond,50.0},{Gold,100.0},{Silver,150.0}}",icons/potion_large.png,Restores a large amount of health,"{4,5}","Name:{{Heal,1,100},{Buff,2,20},{Poison,3,60}}","::{Consumable:2.0,Equipment:1.0,QuestItem:3.0}","::{1:{10,Name:{{Fireball,5,9},{Thunderstorm,5,9}}}, 2:{20,Name:{{Blink,5,9},{BlackHole,5,9}}}}"
3,Iron Sword,Equipment,1,TRUE,TRUE,"{FALSE,Gold,{200.0,400.0}}","{{Gold,200.0},{Silver,400.0},{Copper,600.0}}",icons/sword_iron.png,A common iron sword,{},"Name:{{Buff,2,15},{Poison,3,45},{Heal,1,75}}","::{Equipment:3.0,Consumable:1.0,QuestItem:0.5}","::{1:{10,Name:{{Fireball,5,9},{Thunderstorm,5,9}}}, 2:{20,Name:{{Blink,5,9},{BlackHole,5,11}}}}"
转换为 Excel 表格
{key;EffectName:1,lan:Public,DefaultValue:0} | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | |
唯一ID | 物品名称 | 物品类型 | 最大堆叠数 | 是否可交易 | 是否可销毁 | 出售信息 | 出售价格列表 | 图标路径 | 描述 | 关联道具列表 | 效果 | 属性加成 | 背包信息 |
int | string | ItemType | int | bool | bool | SellInfo | List | string | string | List | Dictionary<string, EffectItem> | Dictionary<ItemType, double> | Dictionary<string,ItemPrices> |
ItemID | ItemName | ItemType | MaxStack | Tradable | Destructible | Sellable | SellPrices | IconPath | Description | LinkedItemIDs | Effects | AttributeBonus | Info |
1 | 小型治疗药水 | Consumable | 99 | TRUE | TRUE | {TRUE,Gold,{10.0,20.0}} | {{Gold,10.0},{Silver,20.0},{Copper,30.0}} | icons/potion_small.png | 回复少量生命值 | {1,2,3} | Name:{{Heal,1,50},{Buff,2,10},{Poison,3,30}} | ::{Consumable:1.5,Equipment:0.5,QuestItem:2.0} | ::{1:{10,Name:{{Fireball,5,9},{Thunderstorm,5,9}}}, 2:{20,Name:{{Blink,5,9},{BlackHole,5,9}}}} |
2 | 大型治疗药水 | Consumable | 99 | TRUE | TRUE | {TRUE,Diamond,{50.0,100.0}} | {{Diamond,50.0},{Gold,100.0},{Silver,150.0}} | icons/potion_large.png | 回复大量生命值 | {4,5} | Name:{{Heal,1,100},{Buff,2,20},{Poison,3,60}} | ::{Consumable:2.0,Equipment:1.0,QuestItem:3.0} | ::{1:{10,Name:{{Fireball,5,9},{Thunderstorm,5,9}}}, 2:{20,Name:{{Blink,5,9},{BlackHole,5,9}}}} |
3 | 铁剑 | Equipment | 1 | TRUE | TRUE | {FALSE,Gold,{200.0,400.0}} | {{Gold,200.0},{Silver,400.0},{Copper,600.0}} | icons/sword_iron.png | 一把普通的铁剑 | {} | Name:{{Buff,2,15},{Poison,3,45},{Heal,1,75}} | ::{Equipment:3.0,Consumable:1.0,QuestItem:0.5} | ::{1:{10,Name:{{Fireball,5,9},{Thunderstorm,5,9}}}, 2:{20,Name:{{Blink,5,9},{BlackHole,5,9}}}} |
生成 Proto 文件
message EffectItem {string Name = 1;int32 Level = 2;int32 ID = 3;
}
message BackpackItem {int32 ItemID = 1;string ItemName = 2;ItemType ItemType = 3;SellInfo Sellable = 4;
}
message SellInfo {bool IsSellable = 1;string CurrencyType = 2;PriceRange Range = 3;
}message PriceRange {double MinPrice = 1;double MaxPrice = 2;
}
message ItemPrices {int32 ItemID = 1;map<string,EffectItem> Prices = 2;
}message BackpackItem {int32 ItemID = 1;string ItemName = 2;ItemType ItemType = 3;int32 MaxStack = 4;bool Tradable = 5;bool Destructible = 6;SellInfo Sellable = 7;repeated PriceInfo SellPrices = 8;string IconPath = 9;string Description = 10;repeated int32 LinkedItemIDs = 11;map<string, EffectItem> Effects = 12;map<ItemType, double> AttributeBonus = 13;map<ItemType, ItemPrices> Info = 13;
}
生成 C# 脚本
public class EffectItem
{public string Name { get; set; } // Proto 编码: 1public int Level { get; set; } // Proto 编码: 2public int ID { get; set; } // Proto 编码: 3
}
public class BackpackItem
{public int ItemID { get; set; } // Proto 编码: 1public string ItemName { get; set; } // Proto 编码: 2public ItemType ItemType { get; set; } // Proto 编码: 3public SellInfo Sellable { get; set; } // Proto 编码: 4
}
public class SellInfo
{public bool IsSellable { get; set; } // Proto 编码: 1public string CurrencyType { get; set; } // Proto 编码: 2public PriceRange Range { get; set; } // Proto 编码: 3
}public class PriceRange
{public double MinPrice { get; set; } // Proto 编码: 1public double MaxPrice { get; set; } // Proto 编码: 2
}public class ItemPrices
{public int ItemID { get; set; } // Proto 编码: 1public MapField<string,EffectItem> Prices { get; set; } // Proto 编码: 2
}public class BackpackItem
{public int ItemID { get; set; }public string ItemName { get; set; }public ItemType ItemType { get; set; }public int MaxStack { get; set; }public bool Tradable { get; set; }public bool Destructible { get; set; }public SellInfo Sellable { get; set; } // 嵌套类public List<PriceInfo> SellPrices { get; set; } // 列表类(嵌套类)public string IconPath { get; set; }public string Description { get; set; }public List<int> LinkedItemIDs { get; set; }public MapField<string, EffectItem> Effects { get; set; }public MapField<ItemType, double> AttributeBonus { get; set; }
}
6. 第一行词典 Key 的规则补充说明
1. 规则说明
- 数据填充格式:
- 第一行使用
{key;EffectDict:编码}
定义嵌套字典的 Key。 - 编码:决定 Key 的排序优先级(从 1 开始递增,数字越小优先级越高)。
- 示例:
{key;EffectDict:1}
表示该字段作为第一层 Key,{key;EffectDict:2}
表示该字段作为第二层 Key,依此类推。
- 第一行使用
2. 示例:地图 Map 配置
以下是一个地图配置的示例,定义了嵌套字典的 Key 和 Value。
{key;EffectDict:1},,"{key;EffectDict:2,key;EffectInfoDict:1}",{key;EffectDict:3},,{key;EffectInfoDict:2},,,
1,2,3,4,5,6,7,8,9
MapID,MapName,RegionType,MonsterDistribution,BackgroundMusic,MapLevel,Location,Size,Description
int,string,string,string,string,int,string,string,string
MapID,Name,RegionType,MonsterDistribution,BackgroundMusic,MapLevel,Location,Size,Description
1,Starter Village,Safe Zone,"Demon, Devil King, Sandworm, Forest, Wolf",Relaxing BGM,1,"(0,0)",100x100,Starting point for new players
2,Dark Forest,Danger Zone,"Goblin, Wolf, Sandworm",Intense BGM,5,"(100,50)",200x150,A dangerous forest
3,Dungeon,Instance,"Skeleton Warrior, Sandworm",Mysterious BGM,10,"(-300,200)",150x100,A dungeon filled with treasures
4,Main City,Safe Zone,"Demon, Devil King, Sandworm, Forest, Wolf",Cheerful BGM,1,"(-500,500)",300x300,The main city where players gather
5,Snowy Mountain,Danger Zone,"Snow Wolf, Yeti, Sandworm",Chilly BGM,15,"(-800,1000)",250x200,A cold snowy mountain area
6,Desert,Danger Zone,"Sandworm, Scorpion, Poisonous Snake",Hot BGM,20,"(-1200,1500)",300x250,A hot desert area
7,Swamp,Danger Zone,"Swamp Monster, Poisonous Snake",Eerie BGM,25,"(-200,800)",180x150,A dangerous swamp area
8,Volcano,Danger Zone,"Fire Giant, Lava Beast",Scorching BGM,30,"(-1500,2000)",350x300,A scorching volcano area
9,Elven Forest,Safe Zone,"Demon, Devil King, Sandworm, Scorpion",Natural BGM,1,"(-700,700)",200x200,The habitat of the elves
10,Hell,Instance,"Demon, Devil King, Sandworm",Terrifying BGM,35,"(-2500,3000)",400x350,The realm ruled by the Devil King
对应对的excel
3. 定义嵌套字典的 Key 和 Value
- Key:
- 由
{key;词典名称:编码}
定义的字段组成。 - 示例中:
MapID
作为第一层 Key({key;EffectDict:1}
)。RegionType
作为第二层 Key({key;EffectDict:2}
)。MonsterDistribution
作为第三层 Key({key;EffectDict:3}
)。
- 由
- Value:
- 最后一个字段是嵌套字典的 Value,即
MapItem
类型的数据。
- 最后一个字段是嵌套字典的 Value,即
4. 生成的数据结构
上面共定义两个词典EffectDict和EffectInfoDict
最终生成的数据结构为:
Dictionary<int, Dictionary<string, Dictionary<string, MapItem>>>EffectDict;
Dictionary<string, Dictionary<int, MapItem>>EffectInfoDict;
- EffectDict的Key:
- 第一层:
MapID
(int
类型)。 - 第二层:
RegionType
(string
类型)。 - 第三层:
MonsterDistribution
(string
类型)。
- 第一层:
- EffectDict的Value:
MapItem
类型的数据。
- EffectInfoDict的Key:
- 第一层:
RegionType
(string
类型)。 - 第二层:
MapLevel
(string
类型)。
- 第一层:
- EffectInfoDict的Value:
MapItem
类型的数据。
这两个词典有一个相同的key是RegionType。就是说一个字段可以作为多个词典的key
5. 生成逻辑
解析脚本的核心逻辑如下:
- 读取 CSV 数据:
- 跳过表头,从数据行开始解析。
- 解析每一行数据:
- 根据字段类型和属性名称,提取嵌套字典的 Key 和 Value。
- 构建嵌套字典:
- 使用
MapID
作为第一层 Key。 - 使用
RegionType
作为第二层 Key。 - 使用
MonsterDistribution
作为第三层 Key。 - 将
MapItem
数据作为 Value。
- 使用
6. 总结
-
{key;词典名称:编码}
:- 用于定义嵌套字典的每一层 Key。
- 编码决定 Key 的排序优先级(从 1 开始递增,数字越小优先级越高)。
-
嵌套字典结构:
- 多个字段共同组成嵌套字典的 Key。
- 最后一个字段是嵌套字典的 Value。
- 示例中生成的结构为
Dictionary<int, Dictionary<string, Dictionary<string, MapItem>>>
。
-
解析脚本:
- 通过逐行解析 CSV 数据,构建嵌套字典。
- 支持异步编程,使用
ConcurrentDictionary
。
在使用词典时先调用实现IProtoInit的Init方法,目的用于性能优化,预加载,如下示例代码:
if (messageData is IProtoInit protoInit){protoInit.Init();}
在插件演示示例的ProtoDataCenter脚本完整的示例代码
public T _Get<T>(Action<T> callFun) where T : class, IMessage, new(){Type type = typeof(T);T messageData = (T)protoDataDict.GetOrAdd(type, _ =>{T messageData = Activator.CreateInstance(typeof(T)) as T;if (GetBytes(type.Name, out byte[] protoData)){messageData.MergeFrom(protoData);if (messageData is IProtoInit protoInit){protoInit.Init();}}return messageData;});callFun?.Invoke(messageData);return messageData;}
如果不调用接口也能正确使用词典。因为在词典属性里将会自定初始化并调用这个方法,如:
private ConcurrentDictionary<int, ConcurrentDictionary<string, ConcurrentDictionary<string, MapItem>>>_EffectDictMap = null;ConcurrentDictionary<int, ConcurrentDictionary<string, ConcurrentDictionary<string, MapItem>>> EffectDictMap{get{if (_EffectDictMap == null){_EffectDictMap =new ConcurrentDictionary<int,ConcurrentDictionary<string, ConcurrentDictionary<string, MapItem>>>();Init();}return _EffectDictMap;}set => _EffectDictMap = value;}
7. 生成的解析脚本
以下是根据上述配置生成的解析脚本:
using System.Collections;
using System.Collections.Concurrent;
using Google.Protobuf;
using HuaXianQu.ProtoBuffEx.Runtime.ProtoInterface;public partial class Map : IProtoInit
{private ConcurrentDictionary<int, ConcurrentDictionary<string, ConcurrentDictionary<string, MapItem>>>_EffectDictMap = null;ConcurrentDictionary<int, ConcurrentDictionary<string, ConcurrentDictionary<string, MapItem>>> EffectDictMap{get{if (_EffectDictMap == null){_EffectDictMap =new ConcurrentDictionary<int,ConcurrentDictionary<string, ConcurrentDictionary<string, MapItem>>>();Init();}return _EffectDictMap;}set => _EffectDictMap = value;}private ConcurrentDictionary<string, ConcurrentDictionary<int, MapItem>> _EffectInfoDictMap = null;ConcurrentDictionary<string, ConcurrentDictionary<int, MapItem>> EffectInfoDictMap{get{if (_EffectInfoDictMap == null){_EffectInfoDictMap = new ConcurrentDictionary<string, ConcurrentDictionary<int, MapItem>>();Init();}return _EffectInfoDictMap;}set => _EffectInfoDictMap = value;}public void Init(){if (_EffectDictMap == null){_EffectDictMap =new ConcurrentDictionary<int, ConcurrentDictionary<string, ConcurrentDictionary<string, MapItem>>>();}if (_EffectInfoDictMap == null){_EffectInfoDictMap = new ConcurrentDictionary<string, ConcurrentDictionary<int, MapItem>>();}for (int i = 0; i < DataList.Count; i++){var item = DataList[i];InitEffectDict(item);InitEffectInfoDict(item);}}private void InitEffectDict(MapItem item){var MapIDMap = EffectDictMap.GetOrAdd(item.MapID,key => new ConcurrentDictionary<string, ConcurrentDictionary<string, MapItem>>());var RegionTypeMap = MapIDMap.GetOrAdd(item.RegionType, key => new ConcurrentDictionary<string, MapItem>());RegionTypeMap.TryAdd(item.MonsterDistribution, item);}private void InitEffectInfoDict(MapItem item){var RegionTypeMap =EffectInfoDictMap.GetOrAdd(item.RegionType, key => new ConcurrentDictionary<int, MapItem>());RegionTypeMap.TryAdd(item.MapLevel, item);}public bool GetEffectDictMap<T>(int MapID, out T value) where T : IDictionary{value = default(T);if (EffectDictMap.TryGetValue(MapID, out var MapIDMap)){value = (T)(IDictionary)MapIDMap;return true;}return false;}public bool GetEffectDictMap<T>(int MapID, string RegionType, out T value) where T : IDictionary{value = default(T);if (EffectDictMap.TryGetValue(MapID, out var MapIDMap)){if (MapIDMap.TryGetValue(RegionType, out var RegionTypeMap)){value = (T)(IDictionary)RegionTypeMap;return true;}}return false;}public bool GetEffectDictMap<T>(int MapID, string RegionType, string MonsterDistribution, out T value)where T : IMessage{value = default(T);if (EffectDictMap.TryGetValue(MapID, out var MapIDMap)){if (MapIDMap.TryGetValue(RegionType, out var RegionTypeMap)){if (RegionTypeMap.TryGetValue(MonsterDistribution, out var MonsterDistributionMap)){value = (T)(IMessage)MonsterDistributionMap;return true;}}}return false;}public bool GetEffectInfoDictMap<T>(string RegionType, out T value) where T : IDictionary{value = default(T);if (EffectInfoDictMap.TryGetValue(RegionType, out var RegionTypeMap)){value = (T)(IDictionary)RegionTypeMap;return true;}return false;}public bool GetEffectInfoDictMap<T>(string RegionType, int MapLevel, out T value) where T : IMessage{value = default(T);if (EffectInfoDictMap.TryGetValue(RegionType, out var RegionTypeMap)){if (RegionTypeMap.TryGetValue(MapLevel, out var MapLevelMap)){value = (T)(IMessage)MapLevelMap;return true;}}return false;}
}
8. 注意事项
key;EffectDict:编码
:EffectDict
是标记相同词典的名称。- 一个字段可以有多个
key;词典名称:编码
,但词典名称不能相同。
- 初始化:
- 在初始化
Map
数据时,需要调用Init
方法实现IProtoInit
接口。
- 在初始化
- 异步支持:
- 使用
ConcurrentDictionary
支持异步编程。
- 使用
通过以上规则和示例,您可以灵活定义复杂的嵌套字典结构,并生成对应的代码和配置文件。
7. 字典类型的规则补充说明
在原有的字典类型规则基础上,进一步明确 Key 的类型限制,并补充相关示例。
1. Key 的类型限制
- Key 只能为基础类型:
- 支持的基础类型包括:
int
、uint
、long
、ulong
、double
、float
、bool
、string
。 - 不支持枚举类型、自定义类型、列表类型或字典类型作为 Key。
- 支持的基础类型包括:
- Value 可以是任意类型:
- 支持基础类型、枚举类型、自定义类型、列表类型或字典类型。
字典字段的定义规则
字典字段的定义规则分为两种情况,具体取决于 :
前面的字符:
情况 1::
前面是非 :
的字符
- 含义:
:
前面的字符表示 类成员名称,用于定义 Key。 - 规则:
- Key:由类成员名称决定,通常是类中的某个字段。
- Value:是类本身的数据。
- 格式:
类成员名称:{类数据}
。 - 示例:
- 定义类:
Effects,string:EffectName:1,EffectItem:EffectData:2
- 数据:
EffectName:{{Heal,1,50},{Buff,2,10},{Poison,3,30}}
- 解释:
EffectName
是Effects
类的一个成员字段,作为 Key。{Heal,1,50}
、{Buff,2,10}
、{Poison,3,30}
是Effects
类的数据,作为 Value。- 最终生成
Dictionary<string, Effects>
类型。
- 定义类:
情况 2::
前面是 ::
- 含义:
::
表示 Key 是 基础类型,直接使用值作为 Key。 - 规则:
- Key:基础类型的值(如
int
、string
等)。 - Value:可以是 基础类型、枚举类型 或 自定义类型。
- 不支持直接使用列表类型或字典类型作为 Value。
- 如果需要在 Value 中使用列表或字典类型,可以通过 自定义类型 嵌套实现。
- 格式:
- 如果 Value 是 基础类型,直接填写值。
- 如果 Value 是 枚举类型,直接填写 枚举值 或 枚举值名称。
- 如果 Value 是 自定义类型,使用
{类数据}
格式。
- 示例:
- 定义类:
SellInfo,bool:IsSellable:1,string:CurrencyType:2,PriceRange:Range:3
- 数据:
::{1:{TRUE,Gold,{10.0,20.0}},2:{FALSE,Silver,{15.0,25.0}}}
- 解释:
1
和2
是 Key 值(int
类型)。{TRUE,Gold,{10.0,20.0}}
和{FALSE,Silver,{15.0,25.0}}
是SellInfo
类的数据,作为 Value。- 最终生成
Dictionary<int, SellInfo>
类型。
- 定义类:
- Key:基础类型的值(如
通用规则
情况 | Key 定义 | Value 定义 | 格式 | 示例 |
---|---|---|---|---|
: 前面是非 : 的字符 | 类成员名称(如 EffectName ) | 类数据 | 类成员名称:{类数据} | EffectName:{{Heal,1,50},{Buff,2,10},{Poison,3,30}} |
: 前面是 :: | 基础类型的值(如 1 、"key1" ) | 基础类型、枚举类型、自定义类型 | ::{Key值:Value数据} | ::{1:10, 2:20} 或 ::{1:Heal, 2:Buff} 或 ::{1:{TRUE,Gold,{10.0,20.0}}} |
示例详解
示例 1:Value 为基础类型
- 定义:
ItemPrices,int:ItemID:1,double:Price:2
- 数据:
::{1:10.5, 2:20.0, 3:30.75}
- 解释:
1
、2
、3
是 Key 值(int
类型)。10.5
、20.0
、30.75
是 Value 值(double
类型)。- 最终生成
Dictionary<int, double>
类型。
示例 2:Value 为枚举类型
- 定义:
ItemEffects,int:ItemID:1,EffectType:Effect:2
- 数据:
或::{1:0, 2:1, 3:2}
::{1:Heal, 2:Buff, 3:Poison}
- 解释:
1
、2
、3
是 Key 值(int
类型)。0
(或Heal
)、1
(或Buff
)、2
(或Poison
)是 Value 值(EffectType
枚举类型)。- 最终生成
Dictionary<int, EffectType>
类型。
示例 3:Value 为自定义类型
- 定义:
SellInfo,bool:IsSellable:1,string:CurrencyType:2,PriceRange:Range:3
- 数据:
::{1:{TRUE,Gold,{10.0,20.0}}, 2:{FALSE,Silver,{15.0,25.0}}}
- 解释:
1
和2
是 Key 值(int
类型)。{TRUE,Gold,{10.0,20.0}}
和{FALSE,Silver,{15.0,25.0}}
是SellInfo
类的数据,作为 Value。- 最终生成
Dictionary<int, SellInfo>
类型。
示例 4:自定义类型中嵌套列表类型
- 定义:
ItemAttributes,int:ItemID:1,List<string>:Attributes:2
- 数据:
::{1:{300,{Attack,Defense}}, 2:{400,{Speed,Agility}}}
- 解释:
1
和2
是 Key 值(int
类型)。{300,{Attack,Defense}}
和{400,{Speed,Agility}}
是ItemAttributes
类的数据,作为 Value。1
和2
是ItemID
字段。{Attack,Defense}
和{Speed,Agility}
是Attributes
字段(List<string>
类型)。
- 最终生成
Dictionary<int, ItemAttributes>
类型。
示例 5:自定义类型中嵌套字典类型
- 定义:
ItemPrices,int:ItemID:1,Dictionary<string,double>:Prices:2
- 数据:
::{1:{10,::{Gold:10.0,Silver:20.0}}, 2:{20,::{Gold:15.0,Silver:25.0}}}
- 解释:
1
和2
是 Key 值(int
类型)。{10,::{Gold:10.0,Silver:20.0}}
和{20,::{Gold:15.0,Silver:25.0}}
是ItemPrices
类的数据,作为 Value。10
和20
是ItemID
字段。{Gold:10.0,Silver:20.0}
和{Gold:15.0,Silver:25.0}
是Prices
字段(Dictionary<string, double>
类型)。
- 最终生成
Dictionary<int, ItemPrices>
类型。
这个示例展示了如何在自定义类型中嵌套字典类型,并通过 CSV 格式定义和存储数据。以下是详细的解释和结构化说明:
定义
1. EffectItem
类型的定义
EffectItem,string:Name:1,int:Level:2,int:ID:3
EffectItem
:自定义类型。- 字段:
Name
:string
类型,表示特效名称。Level
:int
类型,表示特效等级。ID
:int
类型,表示特效的唯一标识符。
2. ItemPrices
类型的定义
ItemPrices,int:ItemID:1,Dictionary<string,EffectItem>:Prices:2
ItemPrices
:自定义类型。- 字段:
ItemID
:int
类型,表示物品的唯一标识符。Prices
:Dictionary<string, EffectItem>
类型,表示以EffectItem
的Name
为键的字典。
数据
::{1:{10,Name:{{Fireball,5,9},{Thunderstorm,5,9}}}, 2:{20,Name:{{Blink,5,9},{BlackHole,5,9}}}
}
-
外层字典:
- 键值对为
1
和2
(int
类型)。 - 对应的值分别为
{10,Name:{{Fireball,5,9},{Thunderstorm,5,9}}}
和{20,Name:{{Blink,5,9},{BlackHole,5,9}}}
(ItemPrices
类型)。
- 键值对为
-
ItemPrices
结构:ItemID
:10
和20
(int
类型)。Prices
:Dictionary<string, EffectItem>
类型。- 键为
Name
(string
类型)。 - 值为
EffectItem
类型的数据,例如{Fireball,5,9}
和{Thunderstorm,5,9}
。
- 键为
-
EffectItem
结构:Fireball,5,9
表示Name="Fireball"
,Level=5
,ID=9
。Thunderstorm,5,9
表示Name="Thunderstorm"
,Level=5
,ID=9
。
最终生成的数据结构
Dictionary<int, ItemPrices>
- Key:
1
和2
(int
类型)。 - Value:
ItemPrices
类型,包含:ItemID
:10
和20
(int
类型)。Prices
:Dictionary<string, EffectItem>
类型,包含:- Key:
Name
(string
类型)。 - Value:
EffectItem
类型,例如{Fireball,5,9}
和{Thunderstorm,5,9}
。
- Key:
示例代码(C#)
以下是用 C# 表示的等效数据结构:
using System;
using System.Collections.Generic;
using UnityEngine;public class EffectItem
{public string Name { get; set; }public int Level { get; set; }public int ID { get; set; }
}public class ItemPrices
{public int ItemID { get; set; }public Dictionary<string, EffectItem> Prices { get; set; }
}public class ProtoTest:MonoBehaviour
{void Start(){// 创建 EffectItem 实例var fireball = new EffectItem { Name = "Fireball", Level = 5, ID = 9 };var thunderstorm = new EffectItem { Name = "Thunderstorm", Level = 5, ID = 9 };var blink = new EffectItem { Name = "Blink", Level = 5, ID = 9 };var blackHole = new EffectItem { Name = "BlackHole", Level = 5, ID = 9 };// 创建 ItemPrices 实例var item1 = new ItemPrices{ItemID = 10,Prices = new Dictionary<string, EffectItem>{{ fireball.Name, fireball },{ thunderstorm.Name, thunderstorm }}};var item2 = new ItemPrices{ItemID = 20,Prices = new Dictionary<string, EffectItem>{{ blink.Name, blink },{ blackHole.Name, blackHole }}};// 创建外层字典var itemPricesDict = new Dictionary<int, ItemPrices>{{ 1, item1 },{ 2, item2 }};// 输出结果foreach (var kvp in itemPricesDict){Debug.Log(($"Key: {kvp.Key}");Debug.Log(($"ItemID: {kvp.Value.ItemID}");foreach (var price in kvp.Value.Prices){Debug.Log(($" Price Key: {price.Key}");Debug.Log(($" EffectItem: {price.Value.Name}, Level={price.Value.Level}, ID={price.Value.ID}");}}}
}
输出结果
Key: 1
ItemID: 10Price Key: FireballEffectItem: Fireball, Level=5, ID=9Price Key: ThunderstormEffectItem: Thunderstorm, Level=5, ID=9
Key: 2
ItemID: 20Price Key: BlinkEffectItem: Blink, Level=5, ID=9Price Key: BlackHoleEffectItem: BlackHole, Level=5, ID=9
说明
- 该数据结构是一个嵌套字典,外层字典的键为
int
,值为ItemPrices
类型。 ItemPrices
包含一个int
类型的ItemID
和一个Dictionary<string, EffectItem>
类型的Prices
。EffectItem
是一个自定义类型,包含Name
、Level
和ID
字段。- 这种结构适合用于存储复杂的游戏数据,例如物品价格及其关联的特效信息。
总结
- 字典字段的格式:
{Key1:Value1,Key2:Value2,...}
。- Key 和 Value 之间用
:
分隔,多个键值对之间用,
分隔。
- Key 的类型:
- Key 只能是基础类型(如
int
、string
等)。
- Key 只能是基础类型(如
- Value 的类型:
- Value 可以是基础类型、枚举类型或自定义类型。
- 如果 Value 是自定义类型,使用
{类数据}
格式。
自定义类数据填充注意事项
1. 数据填充必须与自定义类的成员个数对应且成员总数相同
- 数据填充时,必须确保每个字段都有对应的值,即使为空也需要用空字符占位。
- 示例:
public class EffectItem {public string Name { get; set; }public string Prices { get; set; }public int Level { get; set; }public int ID { get; set; } }
- 数据填充为:
{Fireball,,1,2}
Name
为Fireball
,Prices
为空,Level
为1
,ID
为2
。
- 数据填充为:
2. 数据填充顺序与 Proto 编码顺序一致
-
Proto 文件中的字段编码决定了数据填充的顺序。
-
示例:
- 定义类:
EffectItem,string:Name:1,int:Level:2,int:ID:3
- 生成的 Proto 文件:
message EffectItem {string Name = 1;int32 Level = 2;int32 ID = 3; }
- 数据填充为:
{Fireball,56,100}
Name
为Fireball
,Level
为56
,ID
为100
。
- 定义类:
-
如果修改字段编码顺序:
- 定义类:
EffectItem,string:Name:2,int:Level:1,int:ID:3
- 生成的 Proto 文件:
message EffectItem {int32 Level = 1;string Name = 2;int32 ID = 3; }
- 数据填充为:
{56,Fireball,100}
Level
为56
,Name
为Fireball
,ID
为100
。
- 定义类:
-
注意事项:
- 数据填充必须按照字段编码顺序进行,否则会导致数据错乱。
- 建议字段编码从小到大使用,以避免混淆。
3. 数据填充需要转义字符
- 当数据中包含特殊字符(如英文逗号
,
、大括号{}
)时,需要在前面添加\
进行转义。 - 示例:
- 数据中包含逗号:
{Fireball\, the Great,56,100}
Name
为Fireball, the Great
,Level
为56
,ID
为100
。
- 数据中包含大括号:
{Fireball\{Special\},56,100}
Name
为Fireball{Special}
,Level
为56
,ID
为100
。
- 数据中包含逗号:
总结
- 数据填充必须与类成员个数一致,空值用空字符占位。
- 数据填充顺序必须与 Proto 编码顺序一致,否则会导致数据错乱。
- 特殊字符需要转义,使用
\
进行标记。
通过遵循以上规则,可以确保数据填充的准确性和一致性,避免因数据错乱导致的转换错误。
相关文章:

unity插件Excel转换Proto插件-ExcelToProtobufferTool
unity插件Excel转换Proto插件-ExcelToProtobufferTool **ExcelToProtobufTool 插件文档****1. 插件概述****2. 默认配置类:DefaultIProtoPathConfig****属性说明** **3. 自定义配置类****定义规则****示例代码** **4. 使用方式****4.1 默认路径****4.2 自定义路径**…...

C#中的语句
C#提供了各式各样的语句,大多数是由C和C发展而来,当然,在C#中做了相应修改。语句和表达式一样,都是C#程序的基本组成部分,在本文我们来一起学习C#语句。 1.语句 语句是构造所有C#程序的过程构造块。在语句中可以声明…...

《罗宾逊-旅途VR》Build2108907官方学习版
《罗宾逊-旅途VR》官方版 https://pan.xunlei.com/s/VODiY5gn_fNxKREdVRdwVboCA1?pwdsh3f# 从第一人称的角度进行探索,玩家将遇到一系列恐龙和生物,这些恐龙和生物会对它们在泰森三世生态系统中的存在做出反应。强调与周围环境的互动,鼓励玩…...
常用的跨域方案有哪些?
在前端开发中,跨域(Cross-Origin)是一个常见问题,通常是由于浏览器的同源策略(Same-Origin Policy)限制导致的。为了解决跨域问题,前端开发者可以采用多种方案。 1. CORS(跨域资源共…...

JDBC实验测试
一、语言和环境 实现语言:Java。 环境要求:IDEA2023.3、JDK 17 、MySQL8.0、Navicat 16 for MySQL。 二、技术要求 该系统采用 SWING 技术配合 JDBC 使用 JAVA 编程语言完成桌面应用开发。 三、功能要求 某电商公司为了方便客服查看用户的订单信…...

ChatGPT 摘要,以 ESS 作为你的私有数据存储
作者:来自 Elastic Ryan_Earle 本教程介绍如何设置 Elasticsearch 网络爬虫,将网站索引到 Elasticsearch 中,然后利用 ChatGPT 使用我们的私人数据来总结对其提出的问题。 Python 脚本的 Github Repo:https://github.com/Gunner…...

每日一题洛谷P2669 [NOIP2015 普及组] 金币c++
#include<iostream> using namespace std; int main() {int k;cin >> k;int sum 0;int n 1;while (k > 0) {sum n * n;k - n;n;}sum k * (n - 1);cout << sum << endl;return 0; }...

【C语言系列】深入理解指针(2)
一、数组名的理解 上一篇文章中我们写过一个这样的代码: int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[0];这里使用&arr[0] 的方式拿到了数组第⼀个元素的地址,但是其实数组名本来就是地址,而且是数组首元素的地址ÿ…...
与 Spring Boot 的无缝集成:ShardingSphere 快速集成实践
ShardingSphere 是一个轻量级的开源分布式数据库中间件,它支持分库分表、分布式事务、读写分离等功能。它能够与各种应用框架进行集成,其中与 Spring Boot 的集成非常流行,因为它能够帮助开发者在 Spring Boot 项目中快速实现高性能的分布式数…...
【QT】窗口/界面置于最前端显示,且激活该窗口
目录 0.环境 1.问题描述 2.具体实现 0.环境 windows11 qt 1.问题描述 我有一个窗口QMainWindow(也适用于QWidget或QDialog),想让其在显示的时候置于最前面,且激活成为当前活动窗口 2.具体实现 mainWindow->show();mainWind…...

DOL-288 多功能电子计时器说明书
新买一个计时器,它的用法不太直观,所以把说明书留在这里,以便以后查询。 DOL-288 多功能电子计时器说明书 1.功能说明: 正计时功能,计时上限为23小时59分59秒倒计时功能,计时上限为23小时59分59秒&#…...

14 常用的负载均衡算法
基于nginx的代理 1. 轮询算法 例如我们在nginx服务器中代理了3台服务器,再每次客户端发起请求的时候按照顺序请求挨次的发送到代理的三台服务器上。该算法比较适合每台服务器性能差不多的场景,如果部分服务器性能比较差,可能会造成性能好的…...
方法建议ChatGPT提示词分享
方法建议 ChatGPT能够根据您的具体需求提供针对性的建议,帮助您选择最合适的研究方法。通过清晰的提示,ChatGPT可以精准地为您提供最契合的研究方案。此外,它还能协助您将这些方法灵活地应用于新的研究环境,提出创新的技术解决方案…...
如何提高自动化测试覆盖率和效率
用ChatGPT做软件测试 在现代软件开发中,自动化测试已经成为保证软件质量的重要手段。然而,在实践中,自动化测试的覆盖率和效率常常受到限制,导致潜在缺陷未能及时发现或测试资源浪费。因此,提升自动化测试的覆盖率和效…...

Django学习笔记(安装和环境配置)-01
Django学习笔记(安装和环境配置)-01 一、创建python环境 1、可以通过安装Anaconda来创建一个python环境 # 创建一个虚拟python环境 conda create -n django python3.8 # 切换激活到创建的环境中 activate django2、安装django # 进入虚拟环境中安装django框架 pip install …...

【PHP】部署和发布PHP网站到IIS服务器
欢迎来到《小5讲堂》 这是《PHP》系列文章,每篇文章将以博主理解的角度展开讲解。 温馨提示:博主能力有限,理解水平有限,若有不对之处望指正! 目录 前言安装PHP 稳定版本线程安全版解压使用 PHP配置 配置文件扩展文件…...

渗透测试之SSRF漏洞原理 危害 产生的原因 探测手法 防御手法 绕过手法 限制的手段
目录 SSRF说明: SSRF攻击流程 原理: 危害: SSRF产生的原因 ssrf漏洞利用{危害} 探测手法是否存在SSRF漏洞 如何找ssrf漏洞位置 分享连接地址 google hack url关键字 PHP语言中可能出现的ssrf漏洞函数 file_get_contents sockopen() curl_exec() SSRF…...

微信小程序-base64加解密
思路:先创建一个base64.js的文件,这个文件可以作为专门加解密的文件模块,需要时就引用;创建好后,引用base64.js里的加解密函数。 注意:引用模块一定要引用正确的路径,否则会报错。 base64.js:…...
Linux shell 批量验证端口连通性
脚本 #!/bin/bash # #database check #set -o nounset LOCALIPifconfig | grep inet | head -1 | awk {print $2} | sed s/addr\:// IPLIST192.168.1.99 192.168.1.98 192.168.1.97 PORTLIST81 82 83 84 85 86 check_nc(){ for CHECK_IP in $IPLIST dofor CHECK_PORT in $PORT…...

2025-1-21 Newstar CTF web week1 wp
文章目录 week1headach3会赢吗智械危机 week1 headach3 根据提示,在页面的请求头里找到flag flag{You_Ar3_R3Ally_A_9ooD_d0ctor} 会赢吗 打开控制台,拿到第一部分flag 将地址栏改为提示,去到下一关 控制台调用函数,得到flag …...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...