因工作过程中需要考虑游戏内中文显示方便国际化,游戏内涉及到中文的固定字符串用Excel存储。反复操作Excel对程序员来说很麻烦,于是有了写这样一个偷懒工具的想法。
初版界面如图:
ExcelEditor.png
代码分为四块:
1、Strings.cs
序列化生成strings.asset的数据结构。
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
[CreateAssetMenu]
public class Strings : ScriptableObject
{
public static readonly string STRING_ASSET_PATH = "Assets/Game/strings.asset";
public List<string> _keys;
public List<string> _vals;
public Dictionary<string, string> _dic;
private static Strings _instance;
public static Strings Instance
{
get
{
if (_instance == null)
{
_instance = AssetDatabase.LoadAssetAtPath<Strings>(STRING_ASSET_PATH);
}
return _instance;
}
}
public void UpdateDictionary(List<string> keys, List<string> vals)
{
_keys = keys;
_vals = vals;
if (_keys == null)
{
return;
}
_dic = new Dictionary<string, string>(_keys.Count);
for (int i = 0; i < _keys.Count; ++i)
{
_dic.Add(_keys[i], _vals[i]);
}
}
public string Get(string key)
{
string val;
if (_dic.TryGetValue(key, out val))
{
return val;
}
return key;
}
}
2、ExcelHelper.cs
封装的一些需要用到的操作Excel的公用方法。
using OfficeOpenXml;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEngine;
public class ExcelHelper
{
public static void GetStringsFromFiles(List<string> keys, List<string> vals)
{
string[] files = GetFiles();
foreach (string file in files)
{
string extension = Path.GetExtension(file);
string pattern = ".xlsx$";
bool isMatch = Regex.IsMatch(extension, pattern);
if (isMatch)
{
ReadSingleExcel(file, keys, vals); ;
}
}
}
public static string[] GetFiles()
{
string dataPath = Application.dataPath;
int lastIndex = dataPath.LastIndexOf('/');
string rootPath = dataPath.Substring(0, lastIndex);
rootPath = Path.Combine(rootPath, "Strings");
string[] files = Directory.GetFiles(rootPath);
return files;
}
public static void ReadSingleExcel(string path, List<string> keys, List<string> vals)
{
Debug.Log("read path:" + path);
FileStream fs = new FileStream(path, FileMode.Open);
ExcelPackage package = new ExcelPackage(fs);
ExcelWorksheet sheet = package.Workbook.Worksheets[1];
int rowCount = sheet.Dimension.End.Row;
for (int row = 2; row <= rowCount; ++row)
{
string key = sheet.GetValue(row, 1).ToString();
string cnVal = sheet.GetValue(row, 2).ToString();
keys.Add(key);
vals.Add(cnVal);
}
fs.Close();
fs.Dispose();
}
public static void ModifiedItem(string path, int row, string key, string cnVal = "", string enVal = "en")
{
Debug.Log("write to path:" + path + " key:" + key + " cnVal:" + cnVal);
FileInfo fi = new FileInfo(path);
if (!fi.Exists)
{
return;
}
ExcelPackage package = new ExcelPackage(fi);
ExcelWorksheet sheet = package.Workbook.Worksheets[1];
int operateRow = row;
sheet.SetValue(operateRow, 1, key);
sheet.SetValue(operateRow, 2, cnVal);
package.SaveAs(fi);
}
public static void AddItem(string path, string key, string val)
{
Debug.Log("add to path:" + path + " key:" + key + " val:" + val);
FileInfo fi = new FileInfo(path);
if (!fi.Exists)
{
return;
}
ExcelPackage package = new ExcelPackage(fi);
ExcelWorksheet sheet = package.Workbook.Worksheets[1];
int rowCount = sheet.Dimension.Rows;
int operateRow = rowCount + 1;
sheet.SetValue(operateRow, 1, key);
sheet.SetValue(operateRow, 2, val);
package.SaveAs(fi);
}
public static void DeleteItem(string path, int row)
{
Debug.Log("delete row:" + row + " in path:" + path);
FileInfo fi = new FileInfo(path);
if (!fi.Exists)
{
return;
}
ExcelPackage package = new ExcelPackage(fi);
ExcelWorksheet sheet = package.Workbook.Worksheets[1];
sheet.DeleteRow(row);
package.SaveAs(fi);
}
}
3、ExcelTempInstance.cs
可视化编辑界面的一些临时缓存的数据信息。
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Text.RegularExpressions;
/// <summary>
/// ExcelEditorWindow内容显示临时缓存结构
/// </summary>
public class ExcelTempInstance
{
private List<string> _cacheKeys = new List<string>();
private List<string> _cacheVals = new List<string>();
public List<string> keys;
public List<string> vals;
private string _path;
public string newKey = "";
public string newVal = "";
/// <summary>
/// all string k-v
/// </summary>
private Dictionary<string, string> _dic;
/// <summary>
/// all string v-k
/// </summary>
private Dictionary<string, string> _dicReverse;
internal void SerializedExcelData(string path)
{
_path = path;
newKey = "";
newVal = "";
_cacheKeys.Clear();
_cacheVals.Clear();
ExcelHelper.ReadSingleExcel(_path, _cacheKeys, _cacheVals);
keys = new List<string>(_cacheKeys);
vals = new List<string>(_cacheVals);
_dic = new Dictionary<string, string>(Strings.Instance._keys.Count);
_dicReverse = new Dictionary<string, string>(Strings.Instance._keys.Count);
//Strings.Instance._vals;
for (int i = 0; i < Strings.Instance._keys.Count; ++i)
{
_dic.Add(Strings.Instance._keys[i], Strings.Instance._vals[i]);
_dicReverse.Add(Strings.Instance._vals[i], Strings.Instance._keys[i]);
}
}
//modified
internal void ModifiedItem(int index)
{
string cacheKey = _cacheKeys[index];
string key = keys[index];
string cacheVal = _cacheVals[index];
string val = vals[index];
bool isKeyModified = !cacheKey.Equals(key);
bool isValModified = !cacheVal.Equals(val);
if (!isKeyModified && !isValModified)
{
EditorUtility.DisplayDialog("ERROR!", "No modified here!!!", "OK");
return;
}
ExcelHelper.ModifiedItem(_path, index + 2, keys[index], vals[index]);
SerializedExcelData(_path);
Debug.Log("apply modified success~");
}
//delete
internal void DeleteItem(int index)
{
_cacheKeys.RemoveAt(index);
_cacheVals.RemoveAt(index);
keys.RemoveAt(index);
vals.RemoveAt(index);
ExcelHelper.DeleteItem(_path, index + 2);
SerializedExcelData(_path);
}
//add
internal void AddItem()
{
if (string.IsNullOrEmpty(newKey) || string.IsNullOrEmpty(newVal))
{
EditorUtility.DisplayDialog("ERROR!", "Empty param!!!", "OK");
return;
}
string val;
if (_dic.TryGetValue(newKey, out val))
{
EditorUtility.DisplayDialog("ERROR!", "param \"" + newKey + "\" is repeated!!!", "OK");
return;
}
string valReverse;
if (_dicReverse.TryGetValue(newVal, out valReverse))
{
EditorUtility.DisplayDialog("ERROR!", "param \"" + newVal + "\" is repeated!!!", "OK");
return;
}
ExcelHelper.AddItem(_path, newKey, newVal);
SerializedExcelData(_path);
}
internal void SearchItems(string matchKey, string matchVal)
{
keys.Clear();
vals.Clear();
bool isEmptyKey = string.IsNullOrEmpty(matchKey);
bool isEmptyVal = string.IsNullOrEmpty(matchVal);
if (isEmptyKey && isEmptyVal)
{
return;
}
string baseKey;
string baseVal;
for (int i = 0; i < Strings.Instance._keys.Count; ++i)
{
baseKey = Strings.Instance._keys[i];
baseVal = Strings.Instance._vals[i];
if (!isEmptyKey)
{
if (Regex.IsMatch(baseKey, matchKey))
{
keys.Add(baseKey);
vals.Add(baseVal);
continue;
}
}
if (!isEmptyVal)
{
if (Regex.IsMatch(baseVal, matchVal))
{
keys.Add(baseKey);
vals.Add(baseVal);
}
}
}
}
internal void RestoreItem(int index)
{
keys[index] = _cacheKeys[index];
vals[index] = _cacheVals[index];
}
}
/// <summary>
/// Excel内容展示类型
/// </summary>
enum ExcelShowType
{
/// <summary>
/// 可操作状态
/// </summary>
Default = 0,
/// <summary>
/// 搜索状态,不可操作
/// </summary>
Search = 1 << 0,
}
4、ExcelEditorWindow.cs
可视化操作界面。
using UnityEngine;
using UnityEditor;
using System.IO;
public partial class ExcelEditorWindow : EditorWindow
{
[MenuItem("Tools/Strings/Excel Editor")]
static void DoMain()
{
EditorWindow.GetWindowWithRect(typeof(ExcelEditorWindow), new Rect(0, 0, 960, 640), true, "ExcelEditor");
}
//head
private string _searchKey = "";
private string _searchVal = "";
//file list
string[] _filePaths;
string[] _fileNames;
private int _nameSelectedIdx = 0;
private Vector2 _nameScrollPos = Vector2.zero;
private ExcelTempInstance _excelInst = new ExcelTempInstance();
//file content
private Vector2 _contentScrollPos = Vector2.zero;
//status
ExcelShowType _showType;
//use for initialize
private void OnEnable()
{
_filePaths = ExcelHelper.GetFiles();
_fileNames = new string[_filePaths.Length];
for (int i = 0; i < _filePaths.Length; ++i)
{
_fileNames[i] = Path.GetFileNameWithoutExtension(_filePaths[i]);
}
SerializedExcelData();
}
private void SerializedExcelData()
{
string path = _filePaths[_nameSelectedIdx];
_excelInst.SerializedExcelData(path);
_showType = ExcelShowType.Default;
}
private void OnGUI()
{
DrawHeadTool();
DrawFileNameList();
DrawFileContent();
Event e = Event.current;
if (e.keyCode == KeyCode.KeypadEnter)
{
GUI.FocusControl(null);
}
}
private void DrawHeadTool()
{
Rect boxRect = new Rect(15f, 8.0f, Screen.width * 0.965f, Screen.height * 0.05f);
GUI.Box(boxRect, "");
Rect areaRect = new Rect(18f, 12f, Screen.width * 0.965f, Screen.height * 0.05f);
GUILayout.BeginArea(areaRect);
GUILayout.BeginHorizontal(GUILayout.Height(20));
GUILayout.Space(5);
if (GUILayout.Button("open path", GUILayout.Width(100), GUILayout.Height(20)))
{
string dataPath = Application.dataPath;
int lastIndex = dataPath.LastIndexOf('/');
string rootPath = dataPath.Substring(0, lastIndex);
rootPath = Path.Combine(rootPath, "Strings");
System.Diagnostics.Process.Start(rootPath);
}
DrawHeadSearch();
GUILayout.EndHorizontal();
GUILayout.EndArea();
}
private void DrawHeadSearch()
{
GUILayout.Space(60);
GUILayout.Label("key:", GUILayout.Width(30));
_searchKey = EditorGUILayout.TextField(_searchKey, GUILayout.Width(180));
GUILayout.Space(10);
GUILayout.Label("value:", GUILayout.Width(40));
_searchVal = EditorGUILayout.TextField(_searchVal, GUILayout.Width(180));
GUILayout.Space(5);
if (GUILayout.Button("search", GUILayout.Width(60), GUILayout.Height(20)))
{
_showType = ExcelShowType.Search;
_excelInst.SearchItems(_searchKey, _searchVal);
}
}
private void DrawFileNameList()
{
Vector2 boxStartPosition = new Vector2(15f, Screen.height * 0.08f);
Vector2 boxSize = new Vector2(150f, Screen.height * 0.9f);
Rect boxRect = new Rect(boxStartPosition, boxSize);
GUI.Box(boxRect, "Files List");
Vector2 offsetPosition = new Vector2(0f, 25f);
Vector2 offsetSize = new Vector2(0f, -30f);
Rect areaRect = new Rect(boxStartPosition + offsetPosition, boxSize + offsetSize);
GUILayout.BeginArea(areaRect);
_nameScrollPos = EditorGUILayout.BeginScrollView(_nameScrollPos);
EditorGUI.BeginChangeCheck();
_nameSelectedIdx = GUILayout.SelectionGrid(_nameSelectedIdx, _fileNames, 1, GUILayout.Height(30f * _fileNames.Length), GUILayout.Width(125));
if (EditorGUI.EndChangeCheck())
{
SerializedExcelData();
}
EditorGUILayout.EndScrollView();
GUILayout.EndArea();
}
private void DrawFileContent()
{
Vector2 boxStartPosition = new Vector2(180f, Screen.height * 0.08f);
Vector2 boxSize = new Vector2(Screen.width - 200f, Screen.height * 0.9f);
Rect boxRect = new Rect(boxStartPosition, boxSize);
GUI.Box(boxRect, "");
Vector2 offsetPosition = new Vector2(0f, 10f);
Vector2 offsetSize = new Vector2(0f, -15f);
Rect areaRect = new Rect(boxStartPosition + offsetPosition, boxSize + offsetSize);
GUILayout.BeginArea(areaRect);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(150);
EditorGUILayout.LabelField("key");
EditorGUILayout.LabelField("value");
EditorGUILayout.EndHorizontal();
GUILayout.Space(10);
_contentScrollPos = EditorGUILayout.BeginScrollView(_contentScrollPos);
for (int i = 0; i < _excelInst.keys.Count; ++i)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("no." + (i + 1), GUILayout.MaxWidth(40));
EditorGUI.BeginChangeCheck();
_excelInst.keys[i] = EditorGUILayout.TextField(_excelInst.keys[i], GUILayout.MaxWidth(180));
_excelInst.vals[i] = EditorGUILayout.TextField(_excelInst.vals[i]);
if (EditorGUI.EndChangeCheck())
{
//Debug.Log(_excelInst.keys[i] + " ---> " + _excelInst.vals[i]);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("");
if (_showType == ExcelShowType.Default)
{
if (GUILayout.Button("apply", GUILayout.Width(100), GUILayout.Height(20)))
{
if (EditorUtility.DisplayDialog("CONFIRM", "Are you sure to change???", "OK", "NO"))
{
_excelInst.ModifiedItem(i);
GUIUtility.keyboardControl = 0;
}
else
{
_excelInst.RestoreItem(i);
GUIUtility.keyboardControl = 0;
}
}
if (GUILayout.Button("delete", GUILayout.Width(80), GUILayout.Height(20)))
{
if (EditorUtility.DisplayDialog("CONFIRM", "Are you sure to delete???", "OK", "NO"))
{
_excelInst.DeleteItem(i);
GUIUtility.keyboardControl = 0;
}
}
}
EditorGUILayout.EndHorizontal();
GUILayout.Space(15);
}
EditorGUILayout.EndScrollView();
if (_showType == ExcelShowType.Default)
{
DrawFileContentBottom();
}
GUILayout.EndArea();
}
private void DrawFileContentBottom()
{
GUILayout.Space(20);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("new", GUILayout.MaxWidth(45));
_excelInst.newKey = EditorGUILayout.TextField(_excelInst.newKey, GUILayout.MaxWidth(180));
_excelInst.newVal = EditorGUILayout.TextField(_excelInst.newVal);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("");
if (GUILayout.Button("add", GUILayout.Width(120), GUILayout.Height(20)))
{
_excelInst.AddItem();
GUIUtility.keyboardControl = 0;
}
EditorGUILayout.EndHorizontal();
}
}
网友评论