Unity 使用 Protobuf(Pb2)二進制數據全流程工具詳解

前言

在Unity游戲開發中,高效、快速、安全地讀取配置數據是一項重要需求。本文介紹一種完整的解決方案——使用Protobuf二進制格式(Pb2)存儲和讀取游戲數據,并詳細分享實現全流程的Unity工具。

一、技術流程概覽

實現Unity讀取Pb2二進制數據的流程如下:

  1. Excel設計數據表
  2. Excel轉Proto文件
  3. Proto文件轉C#類
  4. Excel數據序列化為Pb2二進制文件
  5. Unity中加載Pb2數據文件

二、具體實現步驟

1. Excel設計數據表

數據表應遵循特定的格式規范:

  • 第一行:字段注釋
  • 第二行:字段名(英文變量名)
  • 第三行:字段數據類型(例如int32、string)
  • 第四行及以下:數據內容

2. Excel轉Proto文件

使用自定義編輯器工具自動將Excel表轉換為.proto文件。

關鍵代碼ProtoGenerator.cs

private void GenerateProtoFile(){FileInfo fileInfo = new FileInfo(excelFilePath);if (!fileInfo.Exists){Debug.LogError("Excel 文件不存在: " + excelFilePath);return;}// 確保輸出目錄存在if (!Directory.Exists(outputFolder)){Directory.CreateDirectory(outputFolder);}// 定義 .proto 文件頭部信息string protoContent = "syntax = \"proto3\";\n";protoContent += "package GameDataProto;\n\n";using (ExcelPackage package = new ExcelPackage(fileInfo)){// 遍歷所有工作表,每個工作表生成一個 messageforeach (ExcelWorksheet worksheet in package.Workbook.Worksheets){string messageName = worksheet.Name;protoContent += $"message {messageName} {{\n";// 假定第一行為注釋,第二行為變量名,第三行為類型int colCount = worksheet.Dimension.Columns;int fieldIndex = 1;for (int col = 1; col <= colCount; col++){object commentObj = worksheet.Cells[1, col].Value;object variableNameObj = worksheet.Cells[2, col].Value;object typeObj = worksheet.Cells[3, col].Value;if (variableNameObj == null || typeObj == null)continue;string comment = commentObj != null ? commentObj.ToString().Trim() : "";string variableName = variableNameObj.ToString().Trim();string type = typeObj.ToString().Trim();if (!string.IsNullOrEmpty(comment)){protoContent += $"    // {comment}\n";}protoContent += $"    {type} {variableName} = {fieldIndex};\n";fieldIndex++;}protoContent += "}\n\n";}}string protoFilePath = Path.Combine(outputFolder, "GameDataProto.proto");Editor.EditorHelper.WriteAllText(protoFilePath, protoContent);Debug.Log($"生成 .proto 文件: {protoFilePath}");}
  • 通過Unity Editor菜單打開窗口,選擇Excel文件與輸出目錄,自動生成.proto文件。
3. Proto文件轉C#類

根據生成的.proto文件,自動生成對應的C#數據類。

關鍵代碼ProtoToCSharpGenerator.cs

  • 自動解析proto協議,生成繼承自DataInfo的數據類與繼承自BaseGameData的容器類GameData
[ProtoBuf.ProtoContract]public class DataInfo{[ProtoBuf.ProtoIgnore]public int id;}public class BaseGameData{/// <summary>/// 使用反射將加載到的表格數據存儲到當前 GameData 實例中。/// 例如,加載到的 List<CharacterInfo> 會賦值給屬性名為 CharacterInfo 的屬性,/// 要求屬性類型必須為 List<T>,T 與傳入數據類型一致。/// </summary>/// <typeparam name="T">表格數據的元素類型</typeparam>/// <param name="tableData">加載到的表格數據列表</param>public void SetTableData<T>(List<T> tableData){// 查找當前實例中類型為 List<T> 的公共屬性var property = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(p => p.PropertyType == typeof(List<T>));if (property != null){property.SetValue(this, tableData);Debug.Log($"成功將 {typeof(T).Name} 數據加載到 {this.GetType().Name} 中。");}else{Debug.LogError($"在 {this.GetType().Name} 中未找到類型為 List<{typeof(T).Name}> 的屬性。");}}public T GetDataByID<T>(int id) where T : DataInfo{var containerType = GetType();// 改為搜索公共字段,而非屬性var field = containerType.GetFields(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(f => f.FieldType == typeof(List<T>));if (field != null){var list = field.GetValue(this) as List<T>;if (list != null){return list.FirstOrDefault(item => item.id == id);}}return default(T);}}
 // 固定使用的命名空間string packageName = "Reacool.Core.DataTable";List<string> messageNames = new List<string>();string[] lines = Reacool.Editor.EditorHelper.ReadAllLines(protoFilePath);bool isMessage = false;bool isEnum = false;StringBuilder sb = new StringBuilder();// 文件頭注釋和 using 聲明sb.AppendLine("// 通過 .proto 文件自動生成的 C# 文件,請勿手動修改");sb.AppendLine("using System.Collections.Generic;");sb.AppendLine();// 開始生成代碼,強制命名空間為 Reacool.Core.DataTablesb.AppendLine($"namespace {packageName}");sb.AppendLine("{");// 解析 .proto 文件內容for (int i = 0; i < lines.Length; i++){string line = lines[i].Trim();// 忽略 package 聲明(固定命名空間)if (line.StartsWith("package")){continue;}else if (line.StartsWith("//")){sb.AppendLine("    " + line);}else if (line.StartsWith("message")){isMessage = true;var match = Regex.Match(line, @"message\s+(\w+)");if (match.Success){string messageName = match.Groups[1].Value;if (!messageNames.Contains(messageName))messageNames.Add(messageName);// 表格類繼承 DataInfosb.AppendLine("    [ProtoBuf.ProtoContract]");sb.AppendLine($"    public class {messageName} : DataInfo");sb.AppendLine("    {");// 在每個 message 里,自動插入 idProxy 屬性,替代基類 id 做序列化sb.AppendLine("        [ProtoBuf.ProtoMember(1)]");sb.AppendLine("        public int idProxy");sb.AppendLine("        {");sb.AppendLine("            get => base.id;");sb.AppendLine("            set => base.id = value;");sb.AppendLine("        }");}}else if (line.StartsWith("enum")){isEnum = true;var match = Regex.Match(line, @"enum\s+(\w+)");if (match.Success){string enumName = match.Groups[1].Value;sb.AppendLine("    public enum " + enumName);sb.AppendLine("    {");}}else if (line.StartsWith("}")){if (isMessage || isEnum){sb.AppendLine("    }");isMessage = false;isEnum = false;}}else if (string.IsNullOrEmpty(line)){sb.AppendLine();}else if (isMessage){// 解析 message 內字段,如 "repeated type name = id;"var fieldMatch = Regex.Match(line, @"(repeated\s+)?(\w+)\s+(\w+)\s*=\s*(\d+);");if (fieldMatch.Success){bool isArray = !string.IsNullOrEmpty(fieldMatch.Groups[1].Value);string fieldType = fieldMatch.Groups[2].Value;string fieldName = fieldMatch.Groups[3].Value;int fieldId = int.Parse(fieldMatch.Groups[4].Value);// 跳過 id 屬性,因為我們已經用 idProxy 代替if (fieldName.Equals("id", System.StringComparison.OrdinalIgnoreCase)){continue;}// 簡單轉換 Protobuf 基本類型為 C# 類型if (fieldType == "int32") fieldType = "int";else if (fieldType == "int64") fieldType = "long";sb.AppendLine($"        [ProtoBuf.ProtoMember({fieldId})]");sb.AppendLine($"        public {fieldType}{(isArray ? "[]" : "")} {fieldName};");}}else if (isEnum){sb.AppendLine("        " + line.Replace(';', ','));}
4. Excel數據序列化為Pb2二進制文件

使用工具將Excel表中數據自動序列化為Protobuf二進制格式。

關鍵代碼ProtobufBytesGenerator.cs

  • 自動讀取Excel文件,解析工作表,并序列化為Pb2格式,存儲成.bytes文件。
FileInfo excelFile = new FileInfo(excelFilePath);using (ExcelPackage package = new ExcelPackage(excelFile)){// 通過反射獲取 GameData 類型的所有公共實例字段Type gameDataType = typeof(T);FieldInfo[] fields = gameDataType.GetFields(BindingFlags.Public | BindingFlags.Instance);foreach (FieldInfo field in fields){// 僅處理 List<T> 類型的字段if (field.FieldType.IsGenericType &&field.FieldType.GetGenericTypeDefinition() == typeof(List<>)){Type elementType = field.FieldType.GetGenericArguments()[0];// 約定工作表名稱與元素類型名稱相同(如 CharacterInfo、AudioInfo 等)string sheetName = elementType.Name;var worksheet = package.Workbook.Worksheets[sheetName];if (worksheet == null){Debug.LogWarning("未找到工作表:" + sheetName + ",跳過。");continue;}// 調用通用解析方法 ParseSheet<T> 將工作表數據轉為 List<T>MethodInfo method = typeof(BinaryGenerator).GetMethod("ParseSheet", BindingFlags.NonPublic | BindingFlags.Static);MethodInfo genericMethod = method.MakeGenericMethod(elementType);object listObj = genericMethod.Invoke(null, new object[] { worksheet });// 輸出文件路徑:以工作表名稱命名的二進制文件(例如 CharacterInfo.bytes)string outputFilePath = Path.Combine(outputFolder, sheetName + ".bytes");using (FileStream fs = new FileStream(outputFilePath, FileMode.Create)){Serializer.Serialize(fs, listObj);}Debug.Log("生成二進制文件成功:" + outputFilePath);}}}
5. Unity中加載Pb2數據文件

通過DataTableManager讀取Pb2二進制文件并反序列化。

關鍵代碼DataTableManager.csBaseGameData.cs

  • 使用泛型反射動態加載二進制數據,存入BaseGameData對象中,統一管理。
 /// <summary>/// 保存二進制數據/// </summary>/// <param name="fileData"></param>/// <typeparam name="T"></typeparam>public void ProcessByteData<T,T2>(byte[] fileData)where T : DataInfo where T2 : BaseGameData{if (fileData == null || fileData.Length == 0){Debug.LogError("傳入的數據為空!");return;}// 根據類型名稱生成對應的字段名:例如 "CharacterInfo" -> "characterInfos"string typeName = typeof(T).Name;string fieldName = char.ToLowerInvariant(typeName[0]) + typeName.Substring(1) + "s";// 利用反射獲取全局 GameData 實例中對應的公共字段var field = typeof(T2).GetField(fieldName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);if (field == null){Debug.LogWarning($"GameData 中未找到對應字段:{fieldName}");return;}// 檢查該字段是否已經有數據加載,若有數據,則跳過加載var existingData = field.GetValue(this.GameData) as System.Collections.IList;if (existingData != null && existingData.Count > 0){Debug.Log($"{typeName} 數據已加載,跳過加載。");return;}// 反序列化傳入的 bytes 數據List<T> listObj = null;try{using (var ms = new System.IO.MemoryStream(fileData)){listObj = ProtoBuf.Serializer.Deserialize<List<T>>(ms);}}catch (System.Exception ex){Debug.LogError($"解析 {typeName} 數據失敗:{ex.Message}");return;}// 將解析后的數據存入 GameData 中對應的字段field.SetValue(this.GameData, listObj);Debug.Log($"成功加載 {typeName} 數據到 GameData.{fieldName}");}

三、關鍵代碼解析

  • Excel轉二進制數據:使用EPPlus解析Excel文件,序列化數據為二進制格式。
  • 數據反序列化:利用Protobuf庫,將二進制數據反序列化為C#對象。
  • 反射自動加載:利用C#反射特性,自動匹配數據字段和數據類型,簡化數據加載過程。

四、總結

本文提供了一整套基于Unity引擎的Protobuf(Pb2)數據管理流程,從Excel設計、數據轉換、代碼生成到數據加載,自動化程度高且擴展性強。通過本文分享的工具與方法,開發者可以高效地實現Unity項目的數據管理。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/73266.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/73266.shtml
英文地址,請注明出處:http://en.pswp.cn/web/73266.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL-----視圖與索引

目錄 視圖 1.視圖 2.操作 11.索引 1.定義 2.優缺點: 3.分類 4.索引的設計原則 5.索引的使用 作業 視圖 1.視圖 ?如果需要在原表中隱藏部分字段時&#xff0c;怎么辦&#xff1f; 視圖 &#x1f4d6;視圖: 是一個沒有存儲任何數據的表&#xff0c;可以對其CRUD視圖…

stm32-IIC

i^2c,iiCBus,集成電路總線&#xff0c;同步串行半雙工通信總線方式 sck:時鐘同步信號 SDA:發送數據 GND&#xff1a;接地 通信對象&#xff1a;芯片與芯片 主從應答方式&#xff1a; SDA&#xff1a;數據總線 SCL&#xff1a;時鐘總線 在硬件設計中&#xff1a; 上拉電阻&#…

`chromadb` 是什么

chromadb 是什么 chromadb 是一個開源的向量數據庫,它專門用于存儲、索引和查詢向量數據。在處理自然語言處理(NLP)、計算機視覺等領域的任務時,通常會將文本、圖像等數據轉換為向量表示,而 chromadb 可以高效地管理這些向量,幫助開發者快速找到與查詢向量最相似的向量數…

機器視覺工程師如何看機器視覺展會,有些機器視覺兄弟參加機器視覺展會,真的是參加了?重在參與?

作為機器視覺工程師,參加機器視覺展會不僅是了解行業前沿技術的窗口,也是拓展專業網絡、尋找解決方案的重要機會。以下是結合展會信息和工程師視角的綜合建議: 一、聚焦技術趨勢與創新應用 參與技術論壇與研討會 展會同期的技術論壇是獲取行業洞見的核心渠道。例如: 上海展…

Centos操作系統安裝及優化

Centos操作系統安裝及優化 零、環境概述 主機名 centos版本 cpu 內存 Vmware版本 ip地址 test CentOS Linux release 7.6.1810 (Core) 2C 2G 15.5.1 10.0.0.10 一、介質下載 1、7.6版本下載 CentOS7.6標準版下載鏈接: https://archive.kernel.org/centos-vault/7.6.1810/i…

Edge瀏覽器如何默認啟動某個工作區 / 為工作區添加快捷方式

Edge瀏覽器的工作區確實非常好用&#xff0c;可以多端同步標簽頁。但是打開Edge時默認是沒有在工作區的狀態&#xff0c;這個狀態下的標簽頁可能會丟失。所以我研究了一下&#xff0c;如何點擊快捷方式時自動啟動一個工作區&#xff0c;方法如下&#xff1a; 先找到WorkspaceCa…

mac上安裝nvm及nvm的基本語法使用!!

種一棵樹&#xff0c;最好是十年前&#xff0c;其次是現在&#xff01;想要改變&#xff0c;從此刻開始&#xff0c;一切都不晚&#xff01; 目錄 nvm是什么&#xff1f;前提條件&#xff1a;安裝homebrew如果系統已經有node版本&#xff1a;在mac上安裝nvm&#xff1a;用nvm安…

CPP中的numeric庫中的accumulate求和函數說明

導入 accumulate函數位于numeric庫中&#xff0c;作用是對數組或向量求和 // 設定初始值為0&#xff0c;從dp[0]到dp[n-1]進行累加 accumulate(dp,dpn,0);注意&#xff1a;這里的第二個參數是開區間&#xff0c;所以求和時不包含dp[n]這一位

Qt 高效讀寫JSON文件,玩轉QJsonDocument與QJsonObject

一、前言 JSON作為輕量級的數據交換格式&#xff0c;已成為開發者必備技能。Qt框架為JSON處理提供了完整的解決方案&#xff0c;通過QJsonDocument、QJsonObject和QJsonArray三大核心類&#xff0c;輕松實現數據的序列化與反序列化。 JSON vs INI 特性JSONINI數據結構支持嵌…

Kubernetes(k8s)-Pod親和性(Affinity)和反親和性(Anti-affinity)

作者介紹&#xff1a;簡歷上沒有一個精通的運維工程師。請點擊上方的藍色《運維小路》關注我&#xff0c;下面的思維導圖也是預計更新的內容和當前進度(不定時更新)。 我們上一章介紹了Docker基本情況&#xff0c;目前在規模較大的容器集群基本都是Kubernetes&#xff0c;但是K…

ESP32-C3物聯網方案,智能設備創新升級,無線交互控制通信應用

在物聯網技術迅猛發展的今天&#xff0c;各類智能設備如雨后春筍般涌現&#xff0c;深度融入我們生活與工作的各個角落&#xff0c;物聯網正以一種前所未有的速度改變著我們的世界。 想象一下&#xff0c;清晨&#xff0c;當第一縷陽光灑進房間&#xff0c;智能窗簾自動緩緩拉…

Python自動化測試 之 DrissionPage 的下載、安裝、基本使用詳解

Python自動化測試 之 DrissionPage 使用詳解 &#x1f3e1;前言&#xff1a;一、??DrissionPage的基本概述二、 &#x1f5fa;?環境安裝2.1 ???運行環境2.2 ???一鍵安裝 三、&#x1f5fa;?快速入門3.1 頁面類&#x1f6f0;?ChromiumPage&#x1f6eb; SessionPage&…

【操作系統安全】任務3:Linux 網絡安全實戰命令手冊

目錄 一、基礎網絡信息獲取 1. 網絡接口配置 2. 路由表管理 3. 服務端口監控 二、網絡監控與分析 1. 實時流量監控 2. 數據包捕獲 3. 網絡協議分析 三、滲透測試工具集 1. 端口掃描 2. 漏洞利用 3. 密碼破解 四、日志審計與分析 1. 系統日志處理 2. 入侵檢測 3…

社群經濟4.0時代:開源鏈動模式與AI技術驅動的電商生態重構

摘要&#xff1a;在Web3.0技術浪潮與私域流量紅利的雙重驅動下&#xff0c;電商行業正經歷從"流量收割"到"用戶深耕"的范式轉變。本文基于社群經濟理論框架&#xff0c;結合"開源鏈動21模式"、AI智能名片、S2B2C商城小程序源碼等創新工具&#x…

從技術架構和生態考慮,不是單純的配置優化,還有哪些方式可以提高spark的計算性能

從技術架構和生態系統層面提升Spark的計算性能&#xff0c;可采取以下核心策略&#xff1a; 一、計算模型重構與執行引擎升級 1. 彈性分布式數據集&#xff08;RDD&#xff09;的血統優化 通過RDD的Lineage&#xff08;血統&#xff09;機制實現容錯時&#xff0c;采用增量式…

AI對軟件工程(software engineering)的影響在哪些方面?

AI對軟件工程&#xff08;software engineering&#xff09;的影響是全方位且深遠的&#xff0c;它不僅改變了傳統開發流程&#xff0c;還重新定義了工程師的角色和軟件系統的構建方式。以下是AI影響軟件工程的核心維度&#xff1a; 一、開發流程的智能化重構 需求工程革命 ? …

數據庫取證分析

目錄 一.多表關聯 1.一對多聯結 2.子查詢 二.數據庫示例分析 1.多表關聯 三.選擇SQL分析的原因 四.數據庫概述 五.SQL語言 一.多表關聯 1.一對多聯結 2.子查詢 二.數據庫示例分析 1.多表關聯 三.選擇SQL分析的原因 四.數據庫概述 五.SQL語言 1.select 字段

Docker 部署 Graylog 日志管理系統

Docker 部署 Graylog 日志管理系統 前言一、準備工作二、Docker Compose 配置三、啟動 Graylog 服務四、訪問 Graylog Web 界面總結 前言 Graylog 是一個開源的日志管理平臺&#xff0c;專為實時日志收集、分析和可視化設計。它支持強大的搜索功能&#xff0c;并且與 Elastics…

Matlab2024a免費版下載教程

Matlab是一個高性能的數學計算與仿真軟件&#xff0c;廣泛應用于科學計算、數據分析、算法開發以及工程繪圖等多個領域。它提供了強大的矩陣運算能力、豐富的內置函數庫以及靈活的編程環境&#xff0c;使得用戶能夠高效地解決復雜的數學問題。本文&#xff0c;我將為大家詳細介…

網絡運維學習筆記(DeepSeek優化版) 022 HCIP-Datacom路由概念、BFD協議詳解與OSPF第一課

文章目錄 路由概念、BFD協議詳解與OSPF第一課一、路由協議優先級與選路原則1.1 路由協議優先級對照表1.2 路由選路核心原則 二、BFD&#xff08;Bidirectional Forwarding Detection&#xff0c;雙向轉發檢測&#xff09;的配置與應用2.1 雙向心跳探測&#xff08;雙端配置&…