当前位置:网站首页>【Unity】升级版·Excel数据解析,自动创建对应C#类,自动创建ScriptableObject生成类,自动序列化Asset文件

【Unity】升级版·Excel数据解析,自动创建对应C#类,自动创建ScriptableObject生成类,自动序列化Asset文件

2022-07-06 15:35:00 InfoQ

实现功能:

  • 自动创建继承ScriptableObject的C#数据类,每条Excel的数据,都有对应的字段的Get函数; 



  • 自动创建每个Excel的Asset生成类和生成函数,用于自动生成Asset文件
null
  • 用Asset生成类自动序列化Excel数据到Asset文件,可直接在项目运行时加载使用

实现原理:

Excel配置格式:

  • 第1行对应特殊标记(可以设置有效性,指定要创建的文件)
  • 第2行对应中文说明(作为第3行字段的注释)
  • 第3行对应字段名称(自动创建的字段名称)
  • 第4行对应字段类型(自动创建的字段类型,与字段名称一一对应)
  • 第5行及以后对应字段值(所有数据,以行为单位解析、保存数据)
  • 第一列固定字段为"id",是代码中索引每行数据的Key
Excel注释操作:
  • 字段名称行,每个字段单元格内容前加"//",可以注释该字段,不会解析生成到C#类;
  • 第一列的单元格内容前加"//",可以注释一行数据,不会保存到Asset文件中;
  • 注释可以用于添加说明行,或剔除指定无用数据。

生成的C#类格式:

行数据类,对应每一行数据:
[Serializable]
public class TestConfigExcelItem : ExcelItemBase
{
/// <summary>
/// 数据id
/// </summary>>
public int id;
/// <summary>
/// 字符串
/// </summary>>
public string testString;
/// <summary>
/// Int
/// </summary>>
public int testInt;
/// <summary>
/// Float
/// </summary>>
public float testFloat;
}

完整数据类,包含所有行的数据、初始化函数、Get函数:
public class TestConfigExcelData : ExcelDataBase<TestConfigExcelItem>
{
public TestConfigExcelItem[] items;

public Dictionary<int,TestConfigExcelItem> itemDic = new Dictionary<int,TestConfigExcelItem>();

public void Init()
{
itemDic.Clear();
if(items != null && items.Length > 0)
{
for(int i = 0; i < items.Length; i++)
{
itemDic.Add(items[i].id, items[i]);
}
}
}

public TestConfigExcelItem GetTestConfigExcelItem(int id)
{
if(itemDic.ContainsKey(id))
return itemDic[id];
else
return null;
}
#region --- Get Method ---

public string GetTestString(int id)
{
var item = GetTestConfigExcelItem(id);
if(item == null)
return default;
return item.testString;
}

// ··· ···

#endregion
}

目前支持的数据结构:

因为Unity不能序列化二维数组,这里改成一维数组+结构体的方式实现:
[Serializable]
public struct StringArr
{
 public string[] array;
}

//二维数组表示方式: StringArr[]

Asset数据文件:

在自动生成数据的C#类时,会同步生成Asset文件的创建类,用于自动创建Asset文件并序列化数据。

null

优点:

  • 数据修改后只需要重新一键生成即可
  • 每个Excel对应一个类,使用灵活,对Excel限制少
  • 自动创建C#类,不需要对每个Excel手动写代码,每条数据对应字段,不需要拆箱装修
  • 自动创建ScriptableObject的Asset文件,自动序列化数据,方便查看,可以手动修改调整,不需要每次改动都在Excel里操作
  • 在游戏内直接读取Asset的ScriptableObject子类,不需要额外操作,业务层直接调取数据字段

使用方法:

  • 按照标准格式配置Excel
  • 一键生成C#类、Asset文件
  • 项目运行时加载Asset资源,调用Init初始化,Get函数获取对应字段值即可


完整代码:

扩展Unity编辑器窗口:

using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
using System.Linq;

public class BuildExcelWindow : EditorWindow
{
 [MenuItem(&quot;MyTools/Excel Window&quot;,priority = 100)]
 public static void ShowReadExcelWindow()
 {
 BuildExcelWindow window = GetWindow<BuildExcelWindow>(true);
 window.Show();
 window.minSize = new Vector2(475,475);
 }

 //Excel读取路径,绝对路径,放在Assets同级路径
 private static string excelReadAbsolutePath;

 //自动生成C#类文件路径,绝对路径
 private static string scriptSaveAbsolutePath;
 private static string scriptSaveRelativePath;
 //自动生成Asset文件路径,相对路径
 private static string assetSaveRelativePath;

 private List<string> fileNameList = new List<string>();
 private List<string> filePathList = new List<string>();

 private void Awake()
 {
 titleContent.text = &quot;Excel配置表读取&quot;;

 excelReadAbsolutePath = Application.dataPath.Replace(&quot;Assets&quot;,&quot;Excel&quot;);
 scriptSaveAbsolutePath = Application.dataPath + CheckEditorPath(&quot;/Script/Excel/AutoCreateCSCode&quot;);
 scriptSaveRelativePath = CheckEditorPath(&quot;Assets/Script/Excel/AutoCreateCSCode&quot;);
 assetSaveRelativePath = CheckEditorPath(&quot;Assets/AssetData/Excel/AutoCreateAsset&quot;);
 }

 private void OnEnable()
 {
 RefreshExcelFile();
 }

 private void OnDisable()
 {
 fileNameList.Clear();
 filePathList.Clear();
 }

 private Vector2 scrollPosition = Vector2.zero;
 private void OnGUI()
 {
 GUILayout.Space(10);

 scrollPosition = GUILayout.BeginScrollView(scrollPosition,GUILayout.Width(position.width),GUILayout.Height(position.height));

 //展示路径
 GUILayout.BeginHorizontal(GUILayout.Height(20));
 if(GUILayout.Button(&quot;Excel读取路径&quot;,GUILayout.Width(100)))
 {
 EditorUtility.OpenWithDefaultApp(excelReadAbsolutePath);
 Debug.Log(excelReadAbsolutePath);
 }
 if(GUILayout.Button(&quot;Script保存路径&quot;,GUILayout.Width(100)))
 {
 SelectObject(scriptSaveRelativePath);
 }
 if(GUILayout.Button(&quot;Asset保存路径&quot;,GUILayout.Width(100)))
 {
 SelectObject(assetSaveRelativePath);
 }
 GUILayout.EndHorizontal();

 GUILayout.Space(5);

 //Excel列表

 GUILayout.Label(&quot;Excel列表:&quot;);
 for(int i = 0; i < fileNameList.Count; i++)
 {
 GUILayout.BeginHorizontal(&quot;Box&quot;,GUILayout.Height(40));

 GUILayout.Label($&quot;{i}:&quot;,&quot;Titlebar Foldout&quot;,GUILayout.Width(30),GUILayout.Height(35));
 GUILayout.Box(fileNameList[i],&quot;MeTransitionBlock&quot;,GUILayout.MinWidth(200),GUILayout.Height(35));
 GUILayout.Space(10);

 //生成CS代码
 if(GUILayout.Button(&quot;Create Script&quot;,GUILayout.Width(100),GUILayout.Height(30)))
 {
 ExcelDataReader.ReadOneExcelToCode(filePathList[i],scriptSaveAbsolutePath);
 }
 //生成Asset文件
 if(GUILayout.Button(&quot;Create Asset&quot;,GUILayout.Width(100),GUILayout.Height(30)))
 {
 ExcelDataReader.CreateOneExcelAsset(filePathList[i],assetSaveRelativePath);
 }

 GUILayout.EndHorizontal();
 GUILayout.Space(5);
 }
 GUILayout.Space(10);

 //一键处理所有Excel

 GUILayout.Label(&quot;一键操作:&quot;);
 GUILayout.BeginHorizontal(&quot;Box&quot;,GUILayout.Height(40));

 GUILayout.Label(&quot;all&quot;,&quot;Titlebar Foldout&quot;,GUILayout.Width(30),GUILayout.Height(35));
 GUILayout.Box(&quot;All Excel&quot;,&quot;MeTransitionBlock&quot;,GUILayout.MinWidth(200),GUILayout.Height(35));
 GUILayout.Space(10);

 if(GUILayout.Button(&quot;Create Script&quot;,GUILayout.Width(100),GUILayout.Height(30)))
 {
 ExcelDataReader.ReadAllExcelToCode(excelReadAbsolutePath,scriptSaveAbsolutePath);
 }
 if(GUILayout.Button(&quot;Create Asset&quot;,GUILayout.Width(100),GUILayout.Height(30)))
 {
 ExcelDataReader.CreateAllExcelAsset(excelReadAbsolutePath,assetSaveRelativePath);
 }
 GUILayout.EndHorizontal();

 //
 GUILayout.Space(20);
 //
 GUILayout.EndScrollView();
 }

 //读取指定路径下的Excel文件名
 private void RefreshExcelFile()
 {
 fileNameList.Clear();
 filePathList.Clear();

 if(!Directory.Exists(excelReadAbsolutePath))
 {
 Debug.LogError(&quot;无效路径:&quot; + excelReadAbsolutePath);
 return;
 }
 string[] excelFileFullPaths = Directory.GetFiles(excelReadAbsolutePath,&quot;*.xlsx&quot;);

 if(excelFileFullPaths == null || excelFileFullPaths.Length == 0)
 {
 Debug.LogError(excelReadAbsolutePath + &quot;路径下没有找到Excel文件&quot;);
 return;
 }

 filePathList.AddRange(excelFileFullPaths);
 for(int i = 0; i < filePathList.Count; i++)
 {
 fileNameList.Add(Path.GetFileName(filePathList[i]));
 }
 Debug.Log(&quot;找到Excel文件:&quot; + fileNameList.Count + &quot;个&quot;);
 }

 private void SelectObject(string targetPath)
 {
 Object targetObj = AssetDatabase.LoadAssetAtPath<Object>(targetPath);
 EditorGUIUtility.PingObject(targetObj);
 Selection.activeObject = targetObj;
 Debug.Log(targetPath);
 }

 private static string CheckEditorPath(string path)
 {
#if UNITY_EDITOR_WIN
 return path.Replace(&quot;/&quot;,&quot;\\&quot;);
#elif UNITY_EDITOR_OSX
 return path.Replace(&quot;\\&quot;,&quot;/&quot;);
#else
 return path;
#endif
 }
}

Excel数据读取类:&nbsp;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using Excel;
using System.Reflection;
using System;
using System.Linq;

public class ExcelDataReader
{
 //Excel第1行对应特殊标记
 private const int specialSignRow = 0;
 //Excel第2行对应中文说明
 private const int excelNodeRow = 1;
 //Excel第3行对应字段名称
 private const int excelNameRow = 2;
 //Excel第4行对应字段类型
 private const int excelTypeRow = 3;
 //Excel第5行及以后对应字段值
 private const int excelDataRow = 4;

 //标记注释行/列
 private const string annotationSign = &quot;//&quot;;

 #region --- Read Excel ---

 //创建Excel对应的C#类
 public static void ReadAllExcelToCode(string allExcelPath,string codeSavePath)
 {
 //读取所有Excel文件
 //指定目录中与指定的搜索模式和选项匹配的文件的完整名称(包含路径)的数组;如果未找到任何文件,则为空数组。
 string[] excelFileFullPaths = Directory.GetFiles(allExcelPath,&quot;*.xlsx&quot;);
 if(excelFileFullPaths == null || excelFileFullPaths.Length == 0)
 {
 Debug.Log(&quot;Excel file count == 0&quot;);
 return;
 }
 //遍历所有Excel,创建C#类
 for(int i = 0; i < excelFileFullPaths.Length; i++)
 {
 ReadOneExcelToCode(excelFileFullPaths[i],codeSavePath);
 }
 }

 //创建Excel对应的C#类
 public static void ReadOneExcelToCode(string excelFullPath,string codeSavePath)
 {
 //解析Excel获取中间数据
 ExcelMediumData excelMediumData = CreateClassCodeByExcelPath(excelFullPath);
 if(excelMediumData == null)
 {
 Debug.LogError($&quot;读取Excel失败 : {excelFullPath}&quot;);
 return;
 }
 if(!excelMediumData.isValid)
 {
 Debug.LogError($&quot;读取Excel失败,Excel标记失效 : {excelMediumData.excelName}&quot;);
 return;
 }

 if(!excelMediumData.isCreateCSharp && !excelMediumData.isCreateAssignment)
 {
 Debug.LogError($&quot;读取Excel失败,Excel不允许生成CSCode : {excelMediumData.excelName}&quot;);
 return;
 }

 //根据数据生成C#脚本
 string classCodeStr = ExcelCodeCreater.CreateCodeStrByExcelData(excelMediumData);
 if(string.IsNullOrEmpty(classCodeStr))
 {
 Debug.LogError($&quot;解析Excel失败 : {excelMediumData.excelName}&quot;);
 return;
 }

 //检查导出路径
 if(!Directory.Exists(codeSavePath))
 Directory.CreateDirectory(codeSavePath);
 //类名
 string codeFileName = excelMediumData.excelName + &quot;ExcelData&quot;;
 //写文件,生成CS类文件
 StreamWriter sw = new StreamWriter($&quot;{codeSavePath}/{codeFileName}.cs&quot;);
 sw.WriteLine(classCodeStr);
 sw.Close();
 //
 UnityEditor.AssetDatabase.SaveAssets();
 UnityEditor.AssetDatabase.Refresh();
 //
 Debug.Log($&quot;生成Excel的CS成功 : {excelMediumData.excelName}&quot;);
 }

 #endregion

 #region --- Create Asset ---

 //创建Excel对应的Asset数据文件
 public static void CreateAllExcelAsset(string allExcelPath,string assetSavePath)
 {
 //读取所有Excel文件
 //指定目录中与指定的搜索模式和选项匹配的文件的完整名称(包含路径)的数组;如果未找到任何文件,则为空数组。
 string[] excelFileFullPaths = Directory.GetFiles(allExcelPath,&quot;*.xlsx&quot;);
 if(excelFileFullPaths == null || excelFileFullPaths.Length == 0)
 {
 Debug.Log(&quot;Excel file count == 0&quot;);
 return;
 }
 //遍历所有Excel,创建Asset
 for(int i = 0; i < excelFileFullPaths.Length; i++)
 {
 CreateOneExcelAsset(excelFileFullPaths[i],assetSavePath);
 }
 }

 //创建Excel对应的Asset数据文件
 public static void CreateOneExcelAsset(string excelFullPath,string assetSavePath)
 {
 //解析Excel获取中间数据
 ExcelMediumData excelMediumData = CreateClassCodeByExcelPath(excelFullPath);
 if(excelMediumData == null)
 {
 Debug.LogError($&quot;读取Excel失败 : {excelFullPath}&quot;);
 return;
 }
 if(!excelMediumData.isValid)
 {
 Debug.LogError($&quot;读取Excel失败,Excel标记失效 : {excelMediumData.excelName}&quot;);
 return;
 }

 if(!excelMediumData.isCreateAsset)
 {
 Debug.LogError($&quot;读取Excel失败,Excel不允许生成Asset : {excelMediumData.excelName}&quot;);
 return;
 }

 ////获取当前程序集
 //Assembly assembly = Assembly.GetExecutingAssembly();
 ////创建类的实例,返回为 object 类型,需要强制类型转换,assembly.CreateInstance(&quot;类的完全限定名(即包括命名空间)&quot;);
 //object class0bj = assembly.CreateInstance(excelMediumData.excelName + &quot;Assignment&quot;,true);

 //必须遍历所有程序集来获得类型。当前在Assembly-CSharp-Editor中,目标类型在Assembly-CSharp中,不同程序将无法获取类型
 Type assignmentType = null;
 string assetAssignmentName = excelMediumData.excelName + &quot;AssetAssignment&quot;;
 foreach(var asm in AppDomain.CurrentDomain.GetAssemblies())
 {
 //查找目标类型
 Type tempType = asm.GetType(assetAssignmentName);
 if(tempType != null)
 {
 assignmentType = tempType;
 break;
 }
 }
 if(assignmentType == null)
 {
 Debug.LogError($&quot;创界Asset失败,未找到Asset生成类 : {excelMediumData.excelName}&quot;);
 return;
 }

 //反射获取方法
 MethodInfo methodInfo = assignmentType.GetMethod(&quot;CreateAsset&quot;);
 if(methodInfo == null)
 {
 if(assignmentType == null)
 {
 Debug.LogError($&quot;创界Asset失败,未找到Asset创建函数 : {excelMediumData.excelName}&quot;);
 return;
 }
 }

 methodInfo.Invoke(null,new object[] { excelMediumData,assetSavePath });
 //创建Asset文件成功
 Debug.Log($&quot;生成Excel的Asset成功 : {excelMediumData.excelName}&quot;);
 }

 #endregion

 #region --- private ---

 //解析Excel,创建中间数据
 private static ExcelMediumData CreateClassCodeByExcelPath(string excelFileFullPath)
 {
 if(string.IsNullOrEmpty(excelFileFullPath))
 return null;

 excelFileFullPath = excelFileFullPath.Replace(&quot;\\&quot;,&quot;/&quot;);
 //读取Excel
 FileStream stream = File.Open(excelFileFullPath,FileMode.Open,FileAccess.Read);
 if(stream == null)
 return null;
 //解析Excel
 IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
 //无效Excel
 if(excelReader == null || !excelReader.IsValid)
 {
 Debug.Log(&quot;Invalid excel : &quot; + excelFileFullPath);
 return null;
 }

 Debug.Log(&quot;开始解析Excel : &quot; + excelReader.Name);

 //记录Excel数据
 ExcelMediumData excelMediumData = new ExcelMediumData();

 //Excel名字
 excelMediumData.excelName = excelReader.Name;

 //当前遍历的行
 int curRowIndex = 0;
 //开始读取,按行遍历
 while(excelReader.Read())
 {
 //这一行没有读取到数据,视为无效行数据
 if(excelReader.FieldCount <= 0)
 {
 curRowIndex++;
 continue;
 }
 //读取每一行的完整数据
 string[] datas = new string[excelReader.FieldCount];
 for(int j = 0; j < excelReader.FieldCount; ++j)
 {
 //可以直接读取指定类型数据,不过只支持有限数据类型,这里统一读取string,然后再数据转化
 //excelReader.GetInt32(j); excelReader.GetFloat(j);

 //读取每一个单元格数据
 datas[j] = excelReader.GetString(j);
 }

 switch(curRowIndex)
 {
 case specialSignRow:
 //特殊标记行
 string specialSignStr = datas[0];
 if(specialSignStr.Length >= 4)
 {
 excelMediumData.isValid = specialSignStr[0] == 'T';
 excelMediumData.isCreateCSharp = specialSignStr[1] == 'T';
 excelMediumData.isCreateAssignment = specialSignStr[2] == 'T';
 excelMediumData.isCreateAsset = specialSignStr[3] == 'T';
 }
 else
 {
 Debug.LogError(&quot;未解析到特殊标记&quot;);
 }
 break;
 case excelNodeRow:
 //数据注释行
 excelMediumData.propertyNodeArray = datas;
 break;
 case excelNameRow:
 //数据名称行
 excelMediumData.propertyNameArray = datas;
 //注释列号
 for(int i = 0; i < datas.Length; i++)
 {
 if(string.IsNullOrEmpty(datas[i]) || datas[i].StartsWith(annotationSign))
 excelMediumData.annotationColList.Add(i);
 }
 break;
 case excelTypeRow:
 //数据类型行
 excelMediumData.propertyTypeArray = datas;
 break;
 default:
 //数据内容行
 excelMediumData.allRowItemList.Add(datas);
 //注释行号
 if(string.IsNullOrEmpty(datas[0]) || datas[0].StartsWith(annotationSign))
 excelMediumData.annotationRowList.Add(excelMediumData.allRowItemList.Count - 1);
 break;
 }
 //
 curRowIndex++;
 }

 if(CheckExcelMediumData(ref excelMediumData))
 {
 Debug.Log(&quot;读取Excel成功&quot;);
 return excelMediumData;
 }
 else
 {
 Debug.LogError(&quot;读取Excel失败&quot;);
 return null;
 }
 }

 //校验Excel数据
 private static bool CheckExcelMediumData(ref ExcelMediumData mediumData)
 {
 if(mediumData == null)
 return false;

 //检查数据有效性

 if(!mediumData.isValid)
 {
 Debug.LogError(&quot;Excel被标记无效&quot;);
 return false;
 }

 if(string.IsNullOrEmpty(mediumData.excelName))
 {
 Debug.LogError(&quot;Excel名字为空&quot;);
 return false;
 }

 if(mediumData.propertyNameArray == null || mediumData.propertyNameArray.Length == 0)
 {
 Debug.LogError(&quot;未解析到数据名称&quot;);
 return false;
 }
 if(mediumData.propertyTypeArray == null || mediumData.propertyTypeArray.Length == 0)
 {
 Debug.LogError(&quot;未解析到数据类型&quot;);
 return false;
 }
 if(mediumData.propertyNameArray.Length != mediumData.propertyTypeArray.Length)
 {
 Debug.LogError(&quot;数据名称与数据类型数量不一致&quot;);
 return false;
 }
 if(mediumData.allRowItemList.Count == 0)
 {
 Debug.LogError(&quot;数据内容为空&quot;);
 return false;
 }

 if(mediumData.propertyNameArray[0] != &quot;id&quot;)
 {
 Debug.LogError(&quot;第一个字段必须是id字段&quot;);
 return false;
 }

 return true;
 }

 #endregion

}

C#代码生成类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text;
using System.Linq;
using System;

public class ExcelCodeCreater
{

 //创建代码,生成数据C#类
 public static string CreateCodeStrByExcelData(ExcelMediumData excelMediumData)
 {
 if(excelMediumData == null)
 return null;

 //行数据类名
 string itemClassName = excelMediumData.excelName + &quot;ExcelItem&quot;;
 //整体数据类名
 string dataClassName = excelMediumData.excelName + &quot;ExcelData&quot;;

 //开始生成类
 StringBuilder classSource = new StringBuilder();
 classSource.AppendLine(&quot;/*Auto Create, Don't Edit !!!*/&quot;);
 classSource.AppendLine();
 //添加引用
 classSource.AppendLine(&quot;using UnityEngine;&quot;);
 classSource.AppendLine(&quot;using System.Collections.Generic;&quot;);
 classSource.AppendLine(&quot;using System;&quot;);
 classSource.AppendLine(&quot;using System.IO;&quot;);
 classSource.AppendLine();
 //生成CSharp数据类
 if(excelMediumData.isCreateCSharp)
 {
 //生成行数据类,记录每行数据
 classSource.AppendLine(CreateExcelRowItemClass(itemClassName,excelMediumData));
 classSource.AppendLine();
 //生成整体数据类,记录整个Excel的所有行数据
 classSource.AppendLine(CreateExcelAllDataClass(dataClassName,itemClassName,excelMediumData));
 classSource.AppendLine();
 }
 //生成Asset创建类
 if(excelMediumData.isCreateAssignment)
 {
 //生成Asset操作类,用于自动创建Excel对应的Asset文件并赋值
 classSource.AppendLine(CreateExcelAssetClass(excelMediumData));
 classSource.AppendLine();
 }
 //
 return classSource.ToString();
 }

 //----------

 //生成行数据类
 private static string CreateExcelRowItemClass(string itemClassName,ExcelMediumData excelMediumData)
 {
 //生成Excel行数据类
 StringBuilder classSource = new StringBuilder();
 //类名
 classSource.AppendLine(&quot;[Serializable]&quot;);
 classSource.AppendLine($&quot;public class {itemClassName} : ExcelItemBase&quot;);
 classSource.AppendLine(&quot;{&quot;);
 //声明所有字段
 for(int i = 0; i < excelMediumData.propertyNameArray.Length; i++)
 {
 //跳过注释字段
 if(excelMediumData.annotationColList.Contains(i))
 continue;

 //添加注释
 if(i < excelMediumData.propertyNodeArray.Length)
 {
 string propertyNode = excelMediumData.propertyNodeArray[i];
 if(!string.IsNullOrEmpty(propertyNode))
 {
 classSource.AppendLine(&quot;\t/// <summary>&quot;);
 classSource.AppendLine($&quot;\t/// {propertyNode}&quot;);
 classSource.AppendLine(&quot;\t/// </summary>>&quot;);
 }
 }

 //声明行数据类的字段
 string propertyName = excelMediumData.propertyNameArray[i];
 string propertyType = excelMediumData.propertyTypeArray[i];
 string typeStr = GetPropertyType(propertyType);
 classSource.AppendLine($&quot;\tpublic {typeStr} {propertyName};&quot;);
 }
 classSource.AppendLine(&quot;}&quot;);
 return classSource.ToString();
 }

 //----------

 //生成整体数据类
 private static string CreateExcelAllDataClass(string dataClassName,string itemClassName,ExcelMediumData excelMediumData)
 {
 StringBuilder classSource = new StringBuilder();
 //类名
 classSource.AppendLine($&quot;public class {dataClassName} : ExcelDataBase<{itemClassName}>&quot;);
 classSource.AppendLine(&quot;{&quot;);
 //声明字段,行数据类数组
 classSource.AppendLine($&quot;\tpublic {itemClassName}[] items;&quot;);
 classSource.AppendLine();
 //id字段类型
 string idTypeStr = GetPropertyType(excelMediumData.propertyTypeArray[0]);
 //声明字典
 classSource.AppendLine($&quot;\tpublic Dictionary<{idTypeStr},{itemClassName}> itemDic = new Dictionary<{idTypeStr},{itemClassName}>();&quot;);
 classSource.AppendLine();
 //字段初始化方法
 classSource.AppendLine(&quot;\tpublic void Init()&quot;);
 classSource.AppendLine(&quot;\t{&quot;);
 classSource.AppendLine(&quot;\t\titemDic.Clear();&quot;);
 classSource.AppendLine(&quot;\t\tif(items != null && items.Length > 0)&quot;);
 classSource.AppendLine(&quot;\t\t{&quot;);
 classSource.AppendLine(&quot;\t\t\tfor(int i = 0; i < items.Length; i++)&quot;);
 classSource.AppendLine(&quot;\t\t\t{&quot;);
 classSource.AppendLine(&quot;\t\t\t\titemDic.Add(items[i].id, items[i]);&quot;);
 classSource.AppendLine(&quot;\t\t\t}&quot;);
 classSource.AppendLine(&quot;\t\t}&quot;);
 classSource.AppendLine(&quot;\t}&quot;);
 classSource.AppendLine();
 //字典获取方法
 classSource.AppendLine($&quot;\tpublic {itemClassName} Get{itemClassName}({idTypeStr} id)&quot;);
 classSource.AppendLine(&quot;\t{&quot;);
 classSource.AppendLine(&quot;\t\tif(itemDic.ContainsKey(id))&quot;);
 classSource.AppendLine(&quot;\t\t\treturn itemDic[id];&quot;);
 classSource.AppendLine(&quot;\t\telse&quot;);
 classSource.AppendLine(&quot;\t\t\treturn null;&quot;);
 classSource.AppendLine(&quot;\t}&quot;);

 //每个字段Get函数
 classSource.AppendLine(&quot;\t#region --- Get Method ---&quot;);
 classSource.AppendLine();

 for(int i = 1; i < excelMediumData.propertyNameArray.Length; i++)
 {
 if(excelMediumData.annotationColList.Contains(i))
 continue;
 string propertyName = excelMediumData.propertyNameArray[i];
 string propertyType = excelMediumData.propertyTypeArray[i];
 //每个字段Get函数
 classSource.AppendLine(CreateCodePropertyMethod(itemClassName,idTypeStr,propertyName,propertyType));
 }
 classSource.AppendLine(&quot;\t#endregion&quot;);
 classSource.AppendLine(&quot;}&quot;);
 return classSource.ToString();
 }

 //生成数据字段对应Get方法
 private static string CreateCodePropertyMethod(string itemClassName,string idTypeStr,string propertyName,string propertyType)
 {
 StringBuilder methodBuilder = new StringBuilder();
 string itemNameStr = propertyName.FirstOrDefault().ToString().ToUpper() + propertyName.Substring(1);
 string itemTypeStr = GetPropertyType(propertyType);
 //字段Get函数
 methodBuilder.AppendLine($&quot;\tpublic {itemTypeStr} Get{itemNameStr}({idTypeStr} id)&quot;);
 methodBuilder.AppendLine(&quot;\t{&quot;);
 methodBuilder.AppendLine($&quot;\t\tvar item = Get{itemClassName}(id);&quot;);
 methodBuilder.AppendLine(&quot;\t\tif(item == null)&quot;);
 methodBuilder.AppendLine(&quot;\t\t\treturn default;&quot;);
 methodBuilder.AppendLine($&quot;\t\treturn item.{propertyName};&quot;);
 methodBuilder.AppendLine(&quot;\t}&quot;);
 //如果是一维数组
 if(propertyType.Contains(&quot;[]&quot;))
 {
 //typeStr:int[]或IntArr[] ,返回值:int或IntArr
 //string itemTypeStr1d = GetPropertyType(propertyType.Replace(&quot;[]&quot;,&quot;&quot;));
 string itemTypeStr1d = itemTypeStr.Replace(&quot;[]&quot;,&quot;&quot;);
 methodBuilder.AppendLine($&quot;\tpublic {itemTypeStr1d} Get{itemNameStr}({idTypeStr} id, int index)&quot;);
 methodBuilder.AppendLine(&quot;\t{&quot;);
 methodBuilder.AppendLine($&quot;\t\tvar item0 = Get{itemClassName} (id);&quot;);
 methodBuilder.AppendLine(&quot;\t\tif(item0 == null)&quot;);
 methodBuilder.AppendLine(&quot;\t\t\treturn default;&quot;);
 methodBuilder.AppendLine($&quot;\t\tvar item1 = item0.{propertyName};&quot;);
 methodBuilder.AppendLine(&quot;\t\tif(item1 == null || index < 0 || index >= item1.Length)&quot;);
 methodBuilder.AppendLine(&quot;\t\t\treturn default;&quot;);
 methodBuilder.AppendLine(&quot;\t\treturn item1[index];&quot;);
 methodBuilder.AppendLine(&quot;\t}&quot;);
 }
 //如果是二维数组
 if(propertyType.Contains(&quot;[][]&quot;))
 {
 //propertyType:int[][], 返回值:int
 string itemTypeStr1d = GetPropertyType(propertyType.Replace(&quot;[][]&quot;,&quot;&quot;));
 methodBuilder.AppendLine($&quot;\tpublic {itemTypeStr1d} Get{itemNameStr}({idTypeStr} id, int index1, int index2)&quot;);
 methodBuilder.AppendLine(&quot;\t{&quot;);
 methodBuilder.AppendLine($&quot;\t\tvar item0 = Get{itemClassName}(id);&quot;);
 methodBuilder.AppendLine(&quot;\t\tif(item0 == null)&quot;);
 methodBuilder.AppendLine(&quot;\t\t\treturn default;&quot;);
 methodBuilder.AppendLine($&quot;\t\tvar item1 = item0.{propertyName};&quot;);
 methodBuilder.AppendLine(&quot;\t\tif(item1 == null || index1 < 0 || index1 >= item1.Length)&quot;);
 methodBuilder.AppendLine(&quot;\t\t\treturn default;&quot;);
 methodBuilder.AppendLine(&quot;\t\tvar item2 = item1[index1];&quot;);
 methodBuilder.AppendLine(&quot;\t\tif(item2.array == null || index2 < 0 || index2 >= item2.array.Length)&quot;);
 methodBuilder.AppendLine(&quot;\t\t\treturn default;&quot;);
 methodBuilder.AppendLine(&quot;\t\treturn item2.array[index2];&quot;);
 methodBuilder.AppendLine(&quot;\t}&quot;);
 }
 //
 return methodBuilder.ToString();
 }

 //----------

 //生成Asset创建类
 private static string CreateExcelAssetClass(ExcelMediumData excelMediumData)
 {
 string itemClassName = excelMediumData.excelName + &quot;ExcelItem&quot;;
 string dataClassName = excelMediumData.excelName + &quot;ExcelData&quot;;
 string assignmentClassName = excelMediumData.excelName + &quot;AssetAssignment&quot;;

 StringBuilder classSource = new StringBuilder();
 classSource.AppendLine(&quot;#if UNITY_EDITOR&quot;);
 //类名
 classSource.AppendLine($&quot;public class {assignmentClassName}&quot;);
 classSource.AppendLine(&quot;{&quot;);
 //方法名
 classSource.AppendLine(&quot;\tpublic static bool CreateAsset(ExcelMediumData excelMediumData, string excelAssetPath)&quot;);
 //方法体,若有需要可加入try/catch
 classSource.AppendLine(&quot;\t{&quot;);
 classSource.AppendLine(&quot;\t\tvar allRowItemDicList = excelMediumData.GetAllRowItemDicList();&quot;);
 classSource.AppendLine(&quot;\t\tif(allRowItemDicList == null || allRowItemDicList.Count == 0)&quot;);
 classSource.AppendLine(&quot;\t\t\treturn false;&quot;);
 classSource.AppendLine();
 classSource.AppendLine(&quot;\t\tint rowCount = allRowItemDicList.Count;&quot;);
 classSource.AppendLine($&quot;\t\t{dataClassName} excelDataAsset = ScriptableObject.CreateInstance<{dataClassName}>();&quot;);
 classSource.AppendLine($&quot;\t\texcelDataAsset.items = new {itemClassName}[rowCount];&quot;);
 classSource.AppendLine();
 classSource.AppendLine(&quot;\t\tfor(int i = 0; i < rowCount; i++)&quot;);
 classSource.AppendLine(&quot;\t\t{&quot;);
 classSource.AppendLine(&quot;\t\t\tvar itemRowDic = allRowItemDicList[i];&quot;);
 classSource.AppendLine($&quot;\t\t\texcelDataAsset.items[i] = new {itemClassName}();&quot;);

 for(int i = 0; i < excelMediumData.propertyNameArray.Length; i++)
 {
 if(excelMediumData.annotationColList.Contains(i))
 continue;
 string propertyName = excelMediumData.propertyNameArray[i];
 string propertyType = excelMediumData.propertyTypeArray[i];
 classSource.Append($&quot;\t\t\texcelDataAsset.items[i].{propertyName} = &quot;);
 classSource.Append(AssignmentCodeProperty(propertyName,propertyType));
 classSource.AppendLine(&quot;;&quot;);
 }
 classSource.AppendLine(&quot;\t\t}&quot;);
 classSource.AppendLine(&quot;\t\tif(!Directory.Exists(excelAssetPath))&quot;);
 classSource.AppendLine(&quot;\t\t\tDirectory.CreateDirectory(excelAssetPath);&quot;);
 classSource.AppendLine($&quot;\t\tstring fullPath = Path.Combine(excelAssetPath,typeof({dataClassName}).Name) + \&quot;.asset\&quot;;&quot;);
 classSource.AppendLine(&quot;\t\tUnityEditor.AssetDatabase.DeleteAsset(fullPath);&quot;);
 classSource.AppendLine(&quot;\t\tUnityEditor.AssetDatabase.CreateAsset(excelDataAsset,fullPath);&quot;);
 classSource.AppendLine(&quot;\t\tUnityEditor.AssetDatabase.Refresh();&quot;);
 classSource.AppendLine(&quot;\t\treturn true;&quot;);
 classSource.AppendLine(&quot;\t}&quot;);
 // 
 classSource.AppendLine(&quot;}&quot;);
 classSource.AppendLine(&quot;#endif&quot;);
 return classSource.ToString();
 }

 //声明Asset操作类字段
 private static string AssignmentCodeProperty(string propertyName,string propertyType)
 {
 string stringValue = $&quot;itemRowDic[\&quot;{propertyName}\&quot;]&quot;;
 string typeStr = GetPropertyType(propertyType);
 switch(typeStr)
 {
 //字段
 case &quot;int&quot;:
 return &quot;StringUtility.StringToInt(&quot; + stringValue + &quot;)&quot;;
 case &quot;float&quot;:
 return &quot;StringUtility.StringToFloat(&quot; + stringValue + &quot;)&quot;;
 case &quot;bool&quot;:
 return &quot;StringUtility.StringToBool(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector2&quot;:
 return &quot;StringUtility.StringToVector2(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector3&quot;:
 return &quot;StringUtility.StringToVector3(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector2Int&quot;:
 return &quot;StringUtility.StringToVector2Int(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector3Int&quot;:
 return &quot;StringUtility.StringToVector3Int(&quot; + stringValue + &quot;)&quot;;
 case &quot;Color&quot;:
 return &quot;StringUtility.StringToColor(&quot; + stringValue + &quot;)&quot;;
 case &quot;Color32&quot;:
 return &quot;StringUtility.StringToColor32(&quot; + stringValue + &quot;)&quot;;
 case &quot;string&quot;:
 return stringValue;
 //一维
 case &quot;int[]&quot;:
 return &quot;StringUtility.StringToIntArray(&quot; + stringValue + &quot;)&quot;;
 case &quot;float[]&quot;:
 return &quot;StringUtility.StringToFloatArray(&quot; + stringValue + &quot;)&quot;;
 case &quot;bool[]&quot;:
 return &quot;StringUtility.StringToBoolArray(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector2[]&quot;:
 return &quot;StringUtility.StringToVector2Array(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector3[]&quot;:
 return &quot;StringUtility.StringToVector3Array(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector2Int[]&quot;:
 return &quot;StringUtility.StringToVector2IntArray(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector3Int[]&quot;:
 return &quot;StringUtility.StringToVector3IntArray(&quot; + stringValue + &quot;)&quot;;
 case &quot;Color[]&quot;:
 return &quot;StringUtility.StringToColorArray(&quot; + stringValue + &quot;)&quot;;
 case &quot;Color32[]&quot;:
 return &quot;StringUtility.StringToColor32Array(&quot; + stringValue + &quot;)&quot;;
 case &quot;string[]&quot;:
 return &quot;StringUtility.StringToStringArray(&quot; + stringValue + &quot;)&quot;;
 //二维
 case &quot;IntArr[]&quot;:
 return &quot;StringUtility.StringToIntArray2D(&quot; + stringValue + &quot;)&quot;;
 case &quot;FloatArr[]&quot;:
 return &quot;StringUtility.StringToFloatArray2D(&quot; + stringValue + &quot;)&quot;;
 case &quot;BoolArr[]&quot;:
 return &quot;StringUtility.StringToBoolArray2D(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector2Arr[]&quot;:
 return &quot;StringUtility.StringToVector2Array2D(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector3Arr[]&quot;:
 return &quot;StringUtility.StringToVector3Array2D(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector2IntArr[]&quot;:
 return &quot;StringUtility.StringToVector2IntArray2D(&quot; + stringValue + &quot;)&quot;;
 case &quot;Vector3IntArr[]&quot;:
 return &quot;StringUtility.StringToVector3IntArray2D(&quot; + stringValue + &quot;)&quot;;
 case &quot;ColorArr[]&quot;:
 return &quot;StringUtility.StringToColorArray2D(&quot; + stringValue + &quot;)&quot;;
 case &quot;Color32Arr[]&quot;:
 return &quot;StringUtility.StringToColor32Array2D(&quot; + stringValue + &quot;)&quot;;
 case &quot;StringArr[]&quot;:
 return &quot;StringUtility.StringToStringArray2D(&quot; + stringValue + &quot;)&quot;;
 default:
 //枚举
 if(propertyType.StartsWith(&quot;enum&quot;))
 {
 string enumType = propertyType.Split('|').FirstOrDefault();
 string enumName = propertyType.Split('|').LastOrDefault();
 if(enumType == &quot;enum&quot;)
 return &quot;StringUtility.StringToEnum<&quot; + enumName + &quot;>(&quot; + stringValue + &quot;)&quot;;
 else if(enumType == &quot;enum[]&quot;)
 return &quot;StringUtility.StringToEnumArray<&quot; + enumName + &quot;>(&quot; + stringValue + &quot;)&quot;;
 else if(enumType == &quot;enum[][]&quot;)
 return &quot;StringUtility.StringToEnumArray2D<&quot; + enumName + &quot;>(&quot; + stringValue + &quot;)&quot;;
 }
 return stringValue;
 }
 }

 //判断字段类型
 private static string GetPropertyType(string propertyType)
 {
 string lowerType = propertyType.ToLower();
 switch(lowerType)
 {
 case &quot;int&quot;:
 return &quot;int&quot;;
 case &quot;int[]&quot;:
 return &quot;int[]&quot;;
 case &quot;int[][]&quot;:
 return &quot;IntArr[]&quot;;
 case &quot;float&quot;:
 return &quot;float&quot;;
 case &quot;float[]&quot;:
 return &quot;float[]&quot;;
 case &quot;float[][]&quot;:
 return &quot;FloatArr[]&quot;;
 case &quot;bool&quot;:
 return &quot;bool&quot;;
 case &quot;bool[]&quot;:
 return &quot;bool[]&quot;;
 case &quot;bool[][]&quot;:
 return &quot;BoolArr[]&quot;;
 case &quot;string&quot;:
 return &quot;string&quot;;
 case &quot;string[]&quot;:
 return &quot;string[]&quot;;
 case &quot;string[][]&quot;:
 return &quot;StringArr[]&quot;;

 case &quot;vector2&quot;:
 return &quot;Vector2&quot;;
 case &quot;vector2[]&quot;:
 return &quot;Vector2[]&quot;;
 case &quot;vector2[][]&quot;:
 return &quot;Vector2Arr[]&quot;;
 case &quot;vector2int&quot;:
 return &quot;Vector2Int&quot;;
 case &quot;vector2int[]&quot;:
 return &quot;Vector2Int[]&quot;;
 case &quot;vector2int[][]&quot;:
 return &quot;Vector2IntArr[]&quot;;

 case &quot;vector3&quot;:
 return &quot;Vector3&quot;;
 case &quot;vector3[]&quot;:
 return &quot;Vector3[]&quot;;
 case &quot;vector3[][]&quot;:
 return &quot;Vector3Arr[]&quot;;
 case &quot;vector3int&quot;:
 return &quot;Vector3Int&quot;;
 case &quot;vector3int[]&quot;:
 return &quot;Vector3Int[]&quot;;
 case &quot;vector3int[][]&quot;:
 return &quot;Vector3IntArr[]&quot;;

 case &quot;color&quot;:
 return &quot;Color&quot;;
 case &quot;color[]&quot;:
 return &quot;Color[]&quot;;
 case &quot;color[][]&quot;:
 return &quot;ColorArr[]&quot;;
 case &quot;color32&quot;:
 return &quot;Color32&quot;;
 case &quot;color32[]&quot;:
 return &quot;Color32[]&quot;;
 case &quot;color32[][]&quot;:
 return &quot;Color32Arr[]&quot;;

 default:
 if(propertyType.StartsWith(&quot;enum&quot;))
 {
 string enumType = propertyType.Split('|').FirstOrDefault();
 string enumName = propertyType.Split('|').LastOrDefault();
 switch(enumType)
 {
 case &quot;enum&quot;:
 return enumName;
 case &quot;enum[]&quot;:
 return $&quot;{enumName}[]&quot;;
 case &quot;enum[][]&quot;:
 return $&quot;EnumArr<{enumName}>[]&quot;;
 default:
 break;
 }
 }
 return &quot;string&quot;;
 }
 }

}

Excel数据中间类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//Excel中间数据
public class ExcelMediumData
{
 //Excel名字
 public string excelName;

 //Excel是否有效
 public bool isValid = false;
 //是否生成CSharp数据类
 public bool isCreateCSharp = false;
 //是否生成Asset创建类
 public bool isCreateAssignment = false;
 //是否生成Asset文件
 public bool isCreateAsset = false;

 //数据注释
 public string[] propertyNodeArray = null;
 //数据名称
 public string[] propertyNameArray = null;
 //数据类型
 public string[] propertyTypeArray = null;
 //List<每行数据内容>
 public List<string[]> allRowItemList = new List<string[]>();

 //注释行号
 public List<int> annotationRowList = new List<int>();
 //注释列号
 public List<int> annotationColList = new List<int>();

 //List<每行数据>,List<Dictionary<单元格字段名称, 单元格字段值>>
 public List<Dictionary<string,string>> GetAllRowItemDicList()
 {
 if(propertyNameArray == null || propertyNameArray.Length == 0)
 return null;
 if(allRowItemList.Count == 0)
 return null;

 List<Dictionary<string,string>> allRowItemDicList = new List<Dictionary<string,string>>(allRowItemList.Count);

 for(int i = 0; i < allRowItemList.Count; i++)
 {
 string[] rowArray = allRowItemList[i];
 //跳过空数据
 if(rowArray == null || rowArray.Length == 0)
 continue;
 //跳过注释数据
 if(annotationRowList.Contains(i))
 continue;

 //每行数据,对应字段名称和字段值
 Dictionary<string,string> rowDic = new Dictionary<string,string>();
 for(int j = 0; j < propertyNameArray.Length; j++)
 {
 //跳过注释字段
 if(annotationColList.Contains(j))
 continue;

 string propertyName = propertyNameArray[j];
 string propertyValue = j < rowArray.Length ? rowArray[j] : null;
 rowDic[propertyName] = propertyValue;
 }
 allRowItemDicList.Add(rowDic);
 }
 return allRowItemDicList;
 }

}

Excel数据基类、扩展类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System;

public class ExcelDataBase<T> : ScriptableObject where T : ExcelItemBase
{

}

public class ExcelItemBase
{

}


[Serializable]
public struct StringArr
{
 public string[] array;
}
[Serializable]
public struct IntArr
{
 public int[] array;
}
[Serializable]
public struct FloatArr
{
 public float[] array;
}
[Serializable]
public struct BoolArr
{
 public bool[] array;
}

[Serializable]
public struct Vector2Arr
{
 public Vector2[] array;
}
[Serializable]
public struct Vector3Arr
{
 public Vector3[] array;
}
[Serializable]
public struct Vector2IntArr
{
 public Vector2Int[] array;
}
[Serializable]
public struct Vector3IntArr
{
 public Vector3Int[] array;
}
[Serializable]
public struct ColorArr
{
 public Color[] array;
}
[Serializable]
public struct Color32Arr
{
 public Color32[] array;
}

////不支持泛型枚举序列化
//[Serializable]
//public struct EnumArr<T> where T : Enum
//{
// public T[] array;
//}

字符串工具类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text.RegularExpressions;
using System;
using System.Text;
using System.Linq;
using System.Runtime.CompilerServices;

public static class StringUtility
{

 #region --- AddColor ---

 public static string AddColor(object obj,Color color)
 {
 return AddColor(obj,color);
 }
 public static string AddColor(this string str,Color color)
 {
 //把颜色转换为16进制字符串,添加到富文本
 return string.Format(&quot;<color=#{0}>{1}</color>&quot;,ColorUtility.ToHtmlStringRGBA(color),str);
 }
 public static string AddColor(string str1,string str2,Color color)
 {
 return AddColor(str1 + str2,color);
 }
 public static string AddColor(string str1,string str2,string str3,Color color)
 {
 return AddColor(str1 + str2 + str3,color);
 }

 #endregion

 #region --- string length ---

 /// <summary>
 /// 化简字符串长度
 /// </summary>
 /// <param name=&quot;targetStr&quot;></param>
 /// <param name=&quot;targetLength&quot;>目标长度,英文字符==1,中文字符==2</param>
 /// <returns></returns>
 public static string AbbrevStringWithinLength(string targetStr,int targetLength,string abbrevPostfix)
 {
 //C#实际统计:一个中文字符长度==1,英文字符长度==1
 //UI显示要求:一个中文字符长度==2,英文字符长度==1

 //校验参数
 if(string.IsNullOrEmpty(targetStr) || targetLength <= 0)
 return targetStr;
 //字符串长度 * 2 <= 目标长度,即使是全中文也在长度范围内
 if(targetStr.Length * 2 <= targetLength)
 return targetStr;
 //遍历字符
 char[] chars = targetStr.ToCharArray();
 int curLen = 0;
 for(int i = 0; i < chars.Length; i++)
 {
 //累加字符串长度
 if(chars[i] >= 0x4e00 && chars[i] <= 0x9fbb)
 curLen += 2;
 else
 curLen += 1;

 //如果当前位置累计长度超过目标长度,取0~i-1,即Substring(0,i)
 if(curLen > targetLength)
 return targetStr.Substring(0,i) + abbrevPostfix;
 }
 return targetStr;
 }

 #endregion

 #region --- String To Array ---

 //string

 public static byte StringToByte(string valueStr)
 {
 byte value;
 if(byte.TryParse(valueStr,out value))
 return value;
 else
 return 0;
 }

 public static string[] StringToStringArray(string valueStr,char splitSign = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;
 return valueStr.Split(splitSign);
 }

 public static StringArr[] StringToStringArray2D(string valueStr,char splitSign1 = '&',char splitSign2 = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;
 string[] strArr1 = valueStr.Split(splitSign1);
 if(strArr1.Length == 0)
 return null;

 StringArr[] arrArr = new StringArr[strArr1.Length];
 for(int i = 0; i < strArr1.Length; i++)
 {
 arrArr[i] = new StringArr()
 {
 array = strArr1[i].Split(splitSign2)
 };

 }
 return arrArr;
 }

 //int

 public static int StringToInt(string valueStr)
 {
 int value;
 if(int.TryParse(valueStr,out value))
 return value;
 else
 return 0;
 }

 public static int[] StringToIntArray(string valueStr,char splitSign = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;

 string[] valueArr = valueStr.Split(splitSign);
 if(valueArr == null || valueArr.Length == 0)
 return null;

 int[] intArr = new int[valueArr.Length];
 for(int i = 0; i < valueArr.Length; i++)
 {
 intArr[i] = StringToInt(valueArr[i]);
 }
 return intArr;
 }

 public static IntArr[] StringToIntArray2D(string valueStr,char splitSign1 = '&',char splitSign2 = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;
 string[] strArr1 = valueStr.Split(splitSign1);
 if(strArr1.Length == 0)
 return null;

 IntArr[] arrArr = new IntArr[strArr1.Length];
 for(int i = 0; i < strArr1.Length; i++)
 {
 arrArr[i] = new IntArr()
 {
 array = StringToIntArray(strArr1[i],splitSign2)
 };

 }
 return arrArr;
 }

 //float

 public static float StringToFloat(string valueStr)
 {
 float value;
 if(float.TryParse(valueStr,out value))
 return value;
 else
 return 0;
 }

 public static float[] StringToFloatArray(string valueStr,char splitSign = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;

 string[] valueArr = valueStr.Split(splitSign);
 if(valueArr == null || valueArr.Length == 0)
 return null;

 float[] floatArr = new float[valueArr.Length];
 for(int i = 0; i < valueArr.Length; i++)
 {
 floatArr[i] = StringToFloat(valueArr[i]);
 }
 return floatArr;
 }

 public static FloatArr[] StringToFloatArray2D(string valueStr,char splitSign1 = '&',char splitSign2 = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;
 string[] strArr1 = valueStr.Split(splitSign1);
 if(strArr1.Length == 0)
 return null;

 FloatArr[] arrArr = new FloatArr[strArr1.Length];
 for(int i = 0; i < strArr1.Length; i++)
 {
 arrArr[i] = new FloatArr()
 {
 array = StringToFloatArray(strArr1[i],splitSign2)
 };

 }
 return arrArr;
 }

 //bool

 public static bool StringToBool(string valueStr)
 {
 bool value;
 if(bool.TryParse(valueStr,out value))
 return value;
 else
 return false;
 }

 public static bool[] StringToBoolArray(string valueStr,char splitSign = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;

 string[] valueArr = valueStr.Split(splitSign);
 if(valueArr == null || valueArr.Length == 0)
 return null;

 bool[] boolArr = new bool[valueArr.Length];
 for(int i = 0; i < valueArr.Length; i++)
 {
 boolArr[i] = StringToBool(valueArr[i]);
 }
 return boolArr;
 }

 public static BoolArr[] StringToBoolArray2D(string valueStr,char splitSign1 = '&',char splitSign2 = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;
 string[] strArr1 = valueStr.Split(splitSign1);
 if(strArr1.Length == 0)
 return null;

 BoolArr[] arrArr = new BoolArr[strArr1.Length];
 for(int i = 0; i < strArr1.Length; i++)
 {
 arrArr[i] = new BoolArr()
 {
 array = StringToBoolArray(strArr1[i],splitSign2)
 };

 }
 return arrArr;
 }

 //enum

 public static T StringToEnum<T>(string valueStr) where T : Enum
 {
 if(string.IsNullOrEmpty(valueStr))
 return (T)default;

 //先校验字符串是否为枚举值
 int intValue;
 if(int.TryParse(valueStr,out intValue))
 {
 if(Enum.IsDefined(typeof(T),intValue))
 return (T)Enum.ToObject(typeof(T),intValue);
 }
 //如果不是枚举值,当做枚举名处理
 try
 {
 T t = (T)Enum.Parse(typeof(T),valueStr);
 if(Enum.IsDefined(typeof(T),t))
 return t;
 }
 catch(Exception e)
 {
 Debug.LogError(e);
 }
 Debug.LogError(string.Format(&quot;解析枚举错误 {0} : {1}&quot;,typeof(T),valueStr));
 return (T)default;
 }

 public static T[] StringToEnumArray<T>(string valueStr,char splitSign = '|') where T : Enum
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;

 string[] valueArr = valueStr.Split(splitSign);
 if(valueArr == null || valueArr.Length == 0)
 return null;

 T[] enumArr = new T[valueArr.Length];
 for(int i = 0; i < valueArr.Length; i++)
 {
 enumArr[i] = StringToEnum<T>(valueArr[i]);
 }
 return enumArr;
 }

 ////不支持泛型枚举序列化
 //public static EnumArr<T>[] StringToEnumArray2D<T>(string valueStr,char splitSign1 = '&',char splitSign2 = '|') where T : Enum
 //{
 // if(string.IsNullOrEmpty(valueStr))
 // return null;
 // string[] strArr1 = valueStr.Split(splitSign1);
 // if(strArr1.Length == 0)
 // return null;

 // EnumArr<T>[] arrArr = new EnumArr<T>[strArr1.Length];
 // for(int i = 0; i < strArr1.Length; i++)
 // {
 // arrArr[i] = new EnumArr<T>()
 // {
 // array = StringToEnumArray<T>(strArr1[i],splitSign2)
 // };

 // }
 // return arrArr;
 //}

 //vector2

 public static Vector2 StringToVector2(string valueStr,char splitSign = ',')
 {
 Vector2 value = Vector2.zero;
 if(!string.IsNullOrEmpty(valueStr))
 {
 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray != null && stringArray.Length >= 2)
 {
 value.x = StringToFloat(stringArray[0]);
 value.y = StringToFloat(stringArray[1]);
 return value;
 }
 }
 Debug.LogWarning(&quot;String to Vector2 error&quot;);
 return value;
 }

 public static Vector2[] StringToVector2Array(string valueStr,char splitSign = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;

 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray == null || stringArray.Length == 0)
 return null;

 Vector2[] vector2s = new Vector2[stringArray.Length];
 for(int i = 0; i < stringArray.Length; i++)
 {
 vector2s[i] = StringToVector2(stringArray[i]);
 }
 return vector2s;
 }

 public static Vector2Arr[] StringToVector2Array2D(string valueStr,char splitSign1 = '&',char splitSign2 = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;
 string[] strArr1 = valueStr.Split(splitSign1);
 if(strArr1.Length == 0)
 return null;

 Vector2Arr[] arrArr = new Vector2Arr[strArr1.Length];
 for(int i = 0; i < strArr1.Length; i++)
 {
 arrArr[i] = new Vector2Arr()
 {
 array = StringToVector2Array(strArr1[i],splitSign2)
 };
 }
 return arrArr;
 }

 //vector3

 public static Vector3 StringToVector3(string valueStr,char splitSign = ',')
 {
 Vector3 value = Vector3.zero;
 if(!string.IsNullOrEmpty(valueStr))
 {
 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray.Length >= 3)
 {
 value.x = StringToFloat(stringArray[0]);
 value.y = StringToFloat(stringArray[1]);
 value.z = StringToFloat(stringArray[2]);
 return value;
 }
 }
 Debug.LogWarning(&quot;String to Vector3 error&quot;);
 return value;
 }

 public static Vector3[] StringToVector3Array(string valueStr,char splitSign = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;

 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray == null || stringArray.Length == 0)
 return null;

 Vector3[] vector3s = new Vector3[stringArray.Length];
 for(int i = 0; i < stringArray.Length; i++)
 {
 vector3s[i] = StringToVector3(stringArray[i]);
 }
 return vector3s;
 }

 public static Vector3Arr[] StringToVector3Array2D(string valueStr,char splitSign1 = '&',char splitSign2 = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;
 string[] strArr1 = valueStr.Split(splitSign1);
 if(strArr1.Length == 0)
 return null;

 Vector3Arr[] arrArr = new Vector3Arr[strArr1.Length];
 for(int i = 0; i < strArr1.Length; i++)
 {
 arrArr[i] = new Vector3Arr()
 {
 array = StringToVector3Array(strArr1[i],splitSign2)
 };
 }
 return arrArr;
 }

 //vector2Int

 public static Vector2Int StringToVector2Int(string valueStr,char splitSign = ',')
 {
 Vector2Int value = Vector2Int.zero;
 if(!string.IsNullOrEmpty(valueStr))
 {
 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray != null && stringArray.Length >= 2)
 {
 value.x = StringToInt(stringArray[0]);
 value.y = StringToInt(stringArray[1]);
 return value;
 }
 }
 Debug.LogWarning(&quot;String to Vector2Int error&quot;);
 return value;
 }

 public static Vector2Int[] StringToVector2IntArray(string valueStr,char splitSign = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;

 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray == null || stringArray.Length == 0)
 return null;

 Vector2Int[] vector2Ints = new Vector2Int[stringArray.Length];
 for(int i = 0; i < stringArray.Length; i++)
 {
 vector2Ints[i] = StringToVector2Int(stringArray[i]);
 }
 return vector2Ints;
 }

 public static Vector2IntArr[] StringToVector2IntArray2D(string valueStr,char splitSign1 = '&',char splitSign2 = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;
 string[] strArr1 = valueStr.Split(splitSign1);
 if(strArr1.Length == 0)
 return null;

 Vector2IntArr[] arrArr = new Vector2IntArr[strArr1.Length];
 for(int i = 0; i < strArr1.Length; i++)
 {
 arrArr[i] = new Vector2IntArr()
 {
 array = StringToVector2IntArray(strArr1[i],splitSign2)
 };
 }
 return arrArr;
 }

 //vector3Int

 public static Vector3Int StringToVector3Int(string valueStr,char splitSign = ',')
 {
 Vector3Int value = Vector3Int.zero;
 if(!string.IsNullOrEmpty(valueStr))
 {
 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray.Length >= 3)
 {
 value.x = StringToInt(stringArray[0]);
 value.y = StringToInt(stringArray[1]);
 value.z = StringToInt(stringArray[2]);
 return value;
 }
 }
 Debug.LogWarning(&quot;String to Vector3 error&quot;);
 return value;
 }

 public static Vector3Int[] StringToVector3IntArray(string valueStr,char splitSign = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;

 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray == null || stringArray.Length == 0)
 return null;

 Vector3Int[] vector3Ints = new Vector3Int[stringArray.Length];
 for(int i = 0; i < stringArray.Length; i++)
 {
 vector3Ints[i] = StringToVector3Int(stringArray[i]);
 }
 return vector3Ints;
 }

 public static Vector3IntArr[] StringToVector3IntArray2D(string valueStr,char splitSign1 = '&',char splitSign2 = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;
 string[] strArr1 = valueStr.Split(splitSign1);
 if(strArr1.Length == 0)
 return null;

 Vector3IntArr[] arrArr = new Vector3IntArr[strArr1.Length];
 for(int i = 0; i < strArr1.Length; i++)
 {
 arrArr[i] = new Vector3IntArr()
 {
 array = StringToVector3IntArray(strArr1[i],splitSign2)
 };
 }
 return arrArr;
 }

 //color

 public static Color StringToColor(string valueStr,char splitSign = ',')
 {
 if(string.IsNullOrEmpty(valueStr))
 return Color.white;

 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray.Length < 3)
 return Color.white;

 Color color = new Color()
 {
 r = StringToFloat(stringArray[0]),
 g = StringToFloat(stringArray[1]),
 b = StringToFloat(stringArray[2]),
 a = stringArray.Length < 4 ? 1 : StringToFloat(stringArray[3])
 };
 return color;
 }
 public static Color32 StringToColor32(string valueStr,char splitSign = ',')
 {
 if(string.IsNullOrEmpty(valueStr))
 return Color.white;

 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray.Length < 3)
 return Color.white;

 Color32 color = new Color32()
 {
 r = StringToByte(stringArray[0]),
 g = StringToByte(stringArray[1]),
 b = StringToByte(stringArray[2]),
 a = stringArray.Length < 4 ? (byte)1 : StringToByte(stringArray[3])
 };
 return color;
 }

 public static Color[] StringToColorArray(string valueStr,char splitSign = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;

 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray == null || stringArray.Length == 0)
 return null;

 Color[] colors = new Color[stringArray.Length];
 for(int i = 0; i < stringArray.Length; i++)
 {
 colors[i] = StringToColor(stringArray[i]);
 }
 return colors;
 }

 public static Color32[] StringToColor32Array(string valueStr,char splitSign = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;

 string[] stringArray = valueStr.Split(splitSign);
 if(stringArray == null || stringArray.Length == 0)
 return null;

 Color32[] colors = new Color32[stringArray.Length];
 for(int i = 0; i < stringArray.Length; i++)
 {
 colors[i] = StringToColor32(stringArray[i]);
 }
 return colors;
 }

 public static ColorArr[] StringToColorArray2D(string valueStr,char splitSign1 = '&',char splitSign2 = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;
 string[] strArr1 = valueStr.Split(splitSign1);
 if(strArr1.Length == 0)
 return null;

 ColorArr[] arrArr = new ColorArr[strArr1.Length];
 for(int i = 0; i < strArr1.Length; i++)
 {
 arrArr[i] = new ColorArr()
 {
 array = StringToColorArray(strArr1[i],splitSign2)
 };
 }
 return arrArr;
 }
 public static Color32Arr[] StringToColor32Array2D(string valueStr,char splitSign1 = '&',char splitSign2 = '|')
 {
 if(string.IsNullOrEmpty(valueStr))
 return null;
 string[] strArr1 = valueStr.Split(splitSign1);
 if(strArr1.Length == 0)
 return null;

 Color32Arr[] arrArr = new Color32Arr[strArr1.Length];
 for(int i = 0; i < strArr1.Length; i++)
 {
 arrArr[i] = new Color32Arr()
 {
 array = StringToColor32Array(strArr1[i],splitSign2)
 };
 }
 return arrArr;
 }

 #endregion

 #region MyRegion

 public static string GetRandomString(int length)
 {
 StringBuilder builder = new StringBuilder();
 string abc = &quot;abcdefghijklmnopqrstuvwxyzo0123456789QWERTYUIOPASDFGHJKLZXCCVBMN&quot;;
 for(int i = 0; i < length; i++)
 {
 builder.Append(abc[UnityEngine.Random.Range(0,abc.Length - 1)]);
 }
 return builder.ToString();
 }

 public static string Join<T>(T[] arr,string join = &quot;,&quot;)
 {
 if(arr == null || arr.Length == 0)
 return null;

 StringBuilder builder = new StringBuilder();
 for(int i = 0; i < arr.Length; i++)
 {
 builder.Append(arr[i]);
 if(i < arr.Length - 1)
 builder.Append(join);
 }
 return builder.ToString();
 }

 /// <summary>
 /// 中文逗号转英文逗号
 /// </summary>
 /// <param name=&quot;input&quot;></param>
 /// <returns></returns>
 public static string ToDBC(string input)
 {
 char[] c = input.ToCharArray();
 for(int i = 0; i < c.Length; i++)
 {
 if(c[i] == 12288)
 {
 c[i] = (char)32;
 continue;
 }
 if(c[i] > 65280 && c[i] < 65375)
 c[i] = (char)(c[i] - 65248);
 }
 return new string(c);
 }

 /// <summary>
 /// 字符转 ascii 码 
 /// </summary>
 /// <param name=&quot;character&quot;></param>
 /// <returns></returns>
 public static int Asc(string character)
 {
 if(character.Length == 1)
 {
 System.Text.ASCIIEncoding asciiEncoding = new System.Text.ASCIIEncoding();
 int intAsciiCode = (int)asciiEncoding.GetBytes(character)[0];
 return (intAsciiCode);
 }
 Debug.LogError(&quot;Character is not valid.&quot;);
 return -1;
 }

 /// <summary>
 /// ascii码转字符
 /// </summary>
 /// <param name=&quot;asciiCode&quot;></param>
 /// <returns></returns>
 public static string Chr(int asciiCode)
 {
 if(asciiCode >= 0 && asciiCode <= 255)
 {
 System.Text.ASCIIEncoding asciiEncoding = new System.Text.ASCIIEncoding();
 byte[] byteArray = new byte[] { (byte)asciiCode };
 string strCharacter = asciiEncoding.GetString(byteArray);
 return (strCharacter);
 }
 Debug.LogError(&quot;ASCII Code is not valid.&quot;);
 return string.Empty;
 }

 /// <summary>
 /// 过滤掉表情符号
 /// </summary>
 /// <returns>The emoji.</returns>
 /// <param name=&quot;str&quot;>String.</param>
 public static string FilterEmoji(string str)
 {
 List<string> patten = new List<string>() { @&quot;\p{Cs}&quot;,@&quot;\p{Co}&quot;,@&quot;\p{Cn}&quot;,@&quot;[\u2702-\u27B0]&quot; };
 for(int i = 0; i < patten.Count; i++)
 {
 str = Regex.Replace(str,patten[i],&quot;&quot;);//屏蔽emoji 
 }
 return str;
 }

 /// <summary>
 /// 过滤掉表情符号
 /// </summary>
 /// <returns>The emoji.</returns>
 /// <param name=&quot;str&quot;>String.</param>
 public static bool IsFilterEmoji(string str)
 {
 bool bEmoji = false;
 List<string> patten = new List<string>() { @&quot;\p{Cs}&quot;,@&quot;\p{Co}&quot;,@&quot;\p{Cn}&quot;,@&quot;[\u2702-\u27B0]&quot; };
 for(int i = 0; i < patten.Count; i++)
 {
 bEmoji = Regex.IsMatch(str,patten[i]);
 if(bEmoji)
 {
 break;
 }
 }
 return bEmoji;
 }

 #endregion

 #region StringObjectDictionaryExtensions

 /// <summary>
 /// 针对字典中包含以下键值进行结构:mctid0=xxx;mccount0=1,mctid1=kn2,mccount=2。将其前缀去掉,数字后缀变为键,如{后缀,(去掉前后缀的键,值)},注意后缀可能是空字符串即没有后缀
 /// </summary>
 /// <param name=&quot;dic&quot;></param>
 /// <param name=&quot;prefix&quot;>前缀,可以是空引用或空字符串,都表示没有前缀。</param>
 [MethodImpl(MethodImplOptions.AggressiveInlining)]
 public static IEnumerable<IGrouping<string,(string, object)>> GetValuesWithoutPrefix(this IReadOnlyDictionary<string,object> dic,string prefix = null)
 {
 //prefix ??= string.Empty;
 prefix = prefix ?? string.Empty;

 var coll = from tmp in dic.Where(c => c.Key.StartsWith(prefix)) //仅针对指定前缀的键值
 let p3 = tmp.Key.Get3Segment(prefix)
 group (p3.Item2, tmp.Value) by p3.Item3;
 return coll;
 }

 /// <summary>
 /// 分解字符串为三段,前缀,词根,数字后缀(字符串形式)。
 /// </summary>
 /// <param name=&quot;str&quot;></param>
 /// <param name=&quot;prefix&quot;>前缀,可以是空引用或空字符串,都表示没有前缀。</param>
 /// <returns></returns>
 [MethodImpl(MethodImplOptions.AggressiveInlining)]
 public static (string, string, string) Get3Segment(this string str,string prefix = null)
 {
 //prefix ??= string.Empty;
 prefix = prefix ?? string.Empty;

 //最后十进制数字尾串的长度
 int suffixLen = Enumerable.Reverse(str).TakeWhile(c => char.IsDigit(c)).Count();
 //获取十进制数字后缀
 //string suufix = str[^suffixLen..]; //^suffixLen:倒序下标;suffixLen..:从指定位置开始直到末尾
 string suufix = str.Substring(str.Length - suffixLen);

 //return (prefix, str[prefix.Length..^suufix.Length], suufix);
 string middle = str.Substring(prefix.Length,str.Length - prefix.Length - suufix.Length);
 return (prefix, middle, suufix);
 }

 #endregion

}

Demo链接:

https://download.csdn.net/download/qq_39108767/85913988
原网站

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://xie.infoq.cn/article/3055153f2c569c95869491655