当前位置:网站首页>【Unity编辑器扩展实践】、利用txt模板动态生成UI代码
【Unity编辑器扩展实践】、利用txt模板动态生成UI代码
2022-06-28 12:00:00 【Unique_849997563】
在使用Unity3D开发过程中,随着工作时间的推移,你肯定会发现写的代码,就只有那几个模板。比如控制UI的View代码,你会发现格式都是一样的,添加引用、UI变量声明、Awake中给UI变量赋值、添加Button事件、Destroy中注销事件。可以说都可以通过一个模板写出来。这里就介绍一个我用C#写的一个利用txt模板来生成代码的工具,如果有不太好的地方,还请谅解。
之前我写了一个模板生成的代码,Unity中使用Txt模板生成View代码,但是一直觉得很不方便,自己都没用,一心想写一个好一点的来替代它。所以接下来写的这个,算Version0.0.2吧。网上也有大神写了些代码生成器,只是他们是用WriteLine写的,不适合我这种初级玩家,因为我觉得如果要改成我需要的生成器,改动的太多了,所以搞个模板,想少改些代码。
先来整理一下我的思路:
1、遍历各个子物体,预览各个子物体的对象。
2、选择具体的组件是否需要生成UI代码。
3、点击生成按钮,遍历已经选择的组件,读模板,生成对应的代码,写入对应的文本。
道理就这么简单,需要你会一点编辑器,会一点文件读写,基本上就能搞定了,这里写的代码有点不完美,将就用了,以后再优化吧。
我考虑到的注意事项:
1、C#代码不能有重复的变量名,如果你同一个子物体有很多组件需要使用,或者同一个预制上有多个同名子物体需要使用,需要处理生成的变量名。我是用变量名加一个index作为key存再字典里面,如果有同名的,将变量名后边加一个index++;
2、变量名规范,变量名不能有空格,还有一些特殊符号,注意去掉。比如你用右键创建的Scroll View的话,中间就会有一个空格;
3、模板、或者需要替换的字符串中有占位符,又有左右大括号时遇到问题。比如你需要生成要给方法体的时候,注意你的写法,如果不知道怎么解决,可以看一下C#使用String.Format常见报错 有一部分解决方案。
4、如果你跟我一样,使用EditorPrefs来记录选择了哪些组件,一定不要用EditorPrefs.DeleteAll来清理所有的数据,因为EditorPrefs不仅仅是你在使用,Unity也在用这个,如果你使用了,你会发现,你以前打开Unity可以看到那些老的工程路径不见了。
就先总结就这些吧,想到了再写,接下来上代码了。
一、组件相关的Item
因为要查找子物体,组件,子物体的显影,组件的选择,所以创建了一个类,用来管理具体的某个子物体。遍历根节点的时候递归创建,就可以在这个类里面得到一些基础信息了,如:物体名,到根节点的路径,第几个层级(用来折叠GUI的),需要的组件列表。
using System.Collections.Generic;
using UnityEngine;
namespace GenerateCodeProject
{
public class GenerateItemData
{
public void InitItem(Transform tran, GenerateItemData parent = null, int siblingIndex = 0)
{
m_tran = tran;
Name = m_tran.name;
Parent = parent;
if (Parent != null)
{
Index = Parent.Index + 1;
}
else
{
Index = 0;
}
Component[] _coms = m_tran.GetComponents<Component>();
ItemComponentStrList.Clear();
ItemComponentStrList.Add("GameObject");
ItemComponentStrList.Add("Transform");
for (int i = 0; i < _coms.Length; i++)
{
Component _com = _coms[i];
if (_com.GetType().Name.Equals("PrefabLinker")
||_com.GetType().Name.Equals("CanvasRenderer")
|| _com.GetType().Name.Equals("CanvasGroup"))
{
continue;
}
ItemComponentStrList.Add(_com.GetType().Name);
}
SiblingIndex = siblingIndex;
}
/// <summary>
/// 当前子物体Transform
/// </summary>
private Transform m_tran;
/// <summary>
/// 物体名字
/// </summary>
public string Name = string.Empty;
/// <summary>
/// 父物体 ItemData 类
/// </summary>
public GenerateItemData Parent;
/// <summary>
/// 子物体层级(父物体为0)
/// </summary>
public int Index = 0;
/// <summary>
/// 第几个子物体(参照tran.SetAsFirstSibling)
/// </summary>
public int SiblingIndex = 0;
public int ChildCount {
get
{
return m_tran.childCount;
}
}
private string m_showComponentKey = "ShowComponentKey_{0}_{1}";
private string m_showChildrenKey = "ShowChildrenKey_{0}";
private bool m_showChildrent = false;
/// <summary>
/// 唯一的key值,用层级index来做的。
/// </summary>
public string OnlyKey
{
get
{
if (Parent != null)
{
return Parent.OnlyKey + "&" + SiblingIndex;
}
return SiblingIndex.ToString();
}
}
/// <summary>
/// 物体路径
/// </summary>
public string Path
{
get
{
string m_path = Name;
if (Parent != null)
{
m_path = Parent.Path + "/" + Name;
}
return m_path;
}
}
/// <summary>
/// 子物体上的组件
/// </summary>
public List<string> ItemComponentStrList = new List<string>();
private string GetItemComponentKey(string com)
{
string _showComponentKey = string.Format(m_showComponentKey, OnlyKey, com);
return _showComponentKey;
}
/// <summary>
/// 获取 是否显示组件的状态
/// </summary>
/// <param name="com"></param>
/// <returns></returns>
public bool GetItemComponentShowState(string com)
{
string _showComponentKey = GetItemComponentKey(com);
if (GenerateCodeManager.EditorPrefsHasKey(_showComponentKey))
{
return GenerateCodeManager.EditorPrefsGetBool(_showComponentKey);
}
return false;
}
/// <summary>
/// 更新 是否显示组件的状态
/// </summary>
/// <param name="com"></param>
/// <param name="state"></param>
public void UpdateItemComponentDic(string com, bool state)
{
string _showComponentKey = GetItemComponentKey(com);
GenerateCodeManager.EditorPrefsSetBool(_showComponentKey, state);
}
private string ShowChildrenKey
{
get
{
return string.Format(m_showChildrenKey, OnlyKey);
}
}
/// <summary>
/// 是否显示子物体,用来折叠子物体
/// </summary>
public bool ShowChildren
{
get
{
if (GenerateCodeManager.EditorPrefsHasKey(ShowChildrenKey))
{
m_showChildrent = GenerateCodeManager.EditorPrefsGetBool(ShowChildrenKey);
}
return m_showChildrent;
}
set {
GenerateCodeManager.EditorPrefsSetBool(ShowChildrenKey, value);
}
}
}
}
二、代码关的Item
为了和预制的Item分开,我新建了一个类,用来管理代码的生成,这就是具体的组件了,在这个类里面获取变量名,函数名(如果是Button组件),组件名,路径,变量名Index(就是用这个避免重复变量名)。
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace GenerateCodeProject
{
public class CodeItemData
{
private string m_variableName;
/// <summary>
/// 变量名
/// </summary>
public string VariableName
{
get
{
if (Index >0)
{
return "m_" + FirstCharToLower(m_variableName)+ Index;
}
return "m_" + FirstCharToLower(m_variableName);
}
set
{
m_variableName = value;
}
}
public string EventName
{
get
{
if (ComponentType.Equals("AorButton")
|| ComponentType.Equals("Button"))
{
return "OnClick" + FirstCharToUpper(m_variableName);
}
return "";
}
}
/// <summary>
/// 组件名
/// </summary>
public string ComponentType;
/// <summary>
/// 组件路径
/// </summary>
public string Path;
/// <summary>
/// 第几个相同的变量名
/// </summary>
public int Index = 0;
private static string FirstCharToLower(string input)
{
if (String.IsNullOrEmpty(input))
return input;
input = Regex.Replace(input, @"\s", "");
input = Regex.Replace(input, "#", "");
string str = input.First().ToString().ToLower() + input.Substring(1);
return str;
}
private static string FirstCharToUpper(string input)
{
if (String.IsNullOrEmpty(input))
return input;
input = Regex.Replace(input, @"\s", "");
input = Regex.Replace(input, "#", "");
string str = input.First().ToString().ToUpper() + input.Substring(1);
return str;
}
/// <summary>
/// 变量声明
/// </summary>
/// <returns></returns>
public string GetVariableName()
{
return string.Format(Def.GetVariableString, ComponentType, VariableName);
}
/// <summary>
/// 获取组件的路径
/// </summary>
/// <returns></returns>
public string GetComponentPath()
{
if (ComponentType.Equals("GameObject"))
{
return string.Format(Def.GetPathStringWithSpecial, VariableName, Def.ParentGameObjectString, Path);
}
else if (ComponentType.Equals("Transform"))
{
return string.Format(Def.GetPathStringWithSpecial, VariableName, Def.ParentTranString, Path);
}
return string.Format(Def.GetPathGenericityString, VariableName, ComponentType, Def.ParentTranString, Path);
}
/// <summary>
/// 设置点击事件
/// </summary>
/// <returns></returns>
public string SetButtonEvent()
{
if (ComponentType.Equals("AorButton")
|| ComponentType.Equals("Button"))
{
return string.Format(Def.SetButtonEventString, VariableName, EventName);
}
return "";
}
/// <summary>
/// 方法
/// </summary>
/// <returns></returns>
public string GetButtonFunction()
{
if (ComponentType.Equals("AorButton")
|| ComponentType.Equals("Button"))
{
return string.Format(Def.SetButtonFunctionString, EventName);
}
return "";
}
}
}
三、辅助代码类
为了避免代码太脏乱差,假巴意思的将一些常量、通用方法整理出来。
常量(这个需要改成自己需要的亚子):
namespace GenerateCodeProject
{
public class Def
{
public static string TemplateFile =
@"$safeitemrootname$
$componentvariablename$
$getcomponent$
";
public static string GetPathString = "{0} = ({1}, \"{2}\");\n";
public static string GetPathGenericityString = "{0} = {1}({2}, \"{3}\");\n";
public static string GetPathStringWithSpecial = " {0} = {1}, \"{2}\");\n";
public static string GetVariableString = "private {0} {1};\n";
public static string ParentTranString = "transform";
public static string ParentGameObjectString = "gameObject";
public static string SetButtonEventString = "({0}, {1});\n";
public static string SetButtonFunctionString = "private void {0}()\n{
{}}\n";
}
}
方法:文件相关的创建模板的时候会用到,可持续化数据相关的是绘制GUI窗口显隐组件的时候用到的,代码生成相关是组后生成代码时,获取CodeItemData类。
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace GenerateCodeProject
{
public class GenerateCodeManager
{
#region 文件相关
/// <summary>
/// 文件夹路径
/// </summary>
public static string DirectoryPath
{
get
{
return Application.dataPath.Replace("Assets", "Template").Replace(@"/",@"\");
}
}
/// <summary>
/// 文件路径
/// </summary>
public static string TemplateFilePath
{
get
{
return DirectoryPath + @"\Template.txt";
}
}
public static string TemplateFile = Def.TemplateFile;
public static string GetGenerateScriptPath(string name)
{
return DirectoryPath + @"\" + name + ".lua";
}
public static void CheckFileExistAndCreate(string path)
{
if (!File.Exists(path))
{
//写
FileStream _fs = new FileStream(path, FileMode.Create);
StreamWriter _textWriter = new StreamWriter(_fs, Encoding.Default);
_textWriter.Write(TemplateFile);
_textWriter.Flush();
_textWriter.Close();
_fs.Close();
}
}
#endregion
#region 可持续化数据相关
private static List<string> m_editorPrefsList = new List<string>();
public static void ClearAllPrefs()
{
Debug.LogError(Application.dataPath);
for (int i = 0; i < m_editorPrefsList.Count; i++)
{
EditorPrefs.DeleteKey(m_editorPrefsList[i]);
}
m_editorPrefsList.Clear();
}
public static void EditorPrefsSetBool(string key, bool value)
{
if (!m_editorPrefsList.Contains(key))
{
m_editorPrefsList.Add(key);
}
EditorPrefs.SetBool(key, value);
}
public static bool EditorPrefsGetBool(string key)
{
return EditorPrefs.GetBool(key); ;
}
public static bool EditorPrefsHasKey(string key)
{
return EditorPrefs.HasKey(key); ;
}
#endregion
#region 代码生成
public static Dictionary<string ,CodeItemData> GetCodeItemDataDic(List<GenerateItemData> dataList)
{
Dictionary<string, CodeItemData> _itemDataDic = new Dictionary<string, CodeItemData>();
List<CodeItemData> _itemDataList = GetCodeItemDataList(dataList);
for (int i = 0; i < _itemDataList.Count; i++)
{
CodeItemData _data = _itemDataList[i];
while (_itemDataDic.ContainsKey(_data.VariableName))
{
_data.Index += 1;
if (_data.Index >=10)
{
Debug.LogError("循环大于10了!");
break;
}
}
_itemDataDic.Add(_data.VariableName, _data);
}
return _itemDataDic;
}
public static List<CodeItemData> GetCodeItemDataList(List<GenerateItemData> dataList)
{
List<CodeItemData> _itemDataList = new List<CodeItemData>();
for (int i = 0; i < dataList.Count; i++)
{
GenerateItemData _data = dataList[i];
List<CodeItemData> _tempList = GetCodeItemData(_data);
_itemDataList.AddRange(_tempList);
}
return _itemDataList;
}
public static List<CodeItemData> GetCodeItemData(GenerateItemData data)
{
List<CodeItemData> _itemDataList = new List<CodeItemData>();
for (int j = 0; j < data.ItemComponentStrList.Count; j++)
{
string _com = data.ItemComponentStrList[j];
if (!data.GetItemComponentShowState(_com))
{
continue;
}
CodeItemData _codeData = new CodeItemData();
_codeData.VariableName = data.Name;
_codeData.ComponentType = _com;
_codeData.Path = data.Path;
_itemDataList.Add(_codeData);
}
return _itemDataList;
}
#endregion
}
}
四、绘制GUI窗口
这个脚本主要时绘制预览物体、选择具体组件的窗口(这个时最开始写的,写的有点乱,没有整理)。
如果你用EditorGUILayout.Foldout来折叠预制的话,记得用EditorGUI.indentLevel缩进你的UI。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text;
namespace GenerateCodeProject
{
public class GenerateCodeWindow : EditorWindow
{
[MenuItem("GenerateCode/测试测试")]
public static void ShowMyWindow()
{
GenerateCodeWindow _window = EditorWindow.GetWindow<GenerateCodeWindow>("测试测试");
_window.Show();
}
private Transform m_selectTransform;
private List<Transform> m_allTransformList = new List<Transform>();
private List<GenerateItemData> m_allItemDataList = new List<GenerateItemData>();
private Vector2 m_scrollView = new Vector2(800, 800);
private void OnGUI()
{
GUILayout.BeginHorizontal();
if (GUILayout.Button("查看"))
{
m_selectTransform = GetSelectTranform();
Repaint();
}
if (GUILayout.Button("清空"))
{
m_selectTransform = null;
GenerateCodeManager.ClearAllPrefs();
Repaint();
}
if (GUILayout.Button("生成"))
{
m_allItemDataList = GetAllItemDataList(m_selectTransform);
string _path = GenerateCodeManager.GetGenerateScriptPath(Selection.activeGameObject.name);
WriteFileWithTemplate(_path, m_allItemDataList);
}
if (GUILayout.Button("测试测试"))
{
CheckTemplate();
}
GUILayout.EndHorizontal();
if (m_selectTransform == null)
{
return;
}
#region ItemData;
m_scrollView = GUILayout.BeginScrollView(m_scrollView);
m_allItemDataList.Clear();
m_allItemDataList = GetAllItemDataList(m_selectTransform);
for (int i = 0; i < m_allItemDataList.Count; i++)
{
GenerateItemData _data = m_allItemDataList[i];
EditorGUILayout.BeginHorizontal();
string _showName = _data.Name;
EditorGUI.indentLevel = _data.Index;
if (_data.ChildCount > 0)
{
_data.ShowChildren = EditorGUILayout.Foldout(_data.ShowChildren, _showName);
}
else
{
EditorGUILayout.LabelField(" " + _showName);
}
bool _toggle = false;
for (int _comIndex = 0; _comIndex < _data.ItemComponentStrList.Count; _comIndex++)
{
string _com = _data.ItemComponentStrList[_comIndex];
_toggle = GUILayout.Toggle(_data.GetItemComponentShowState(_com), _com, GUILayout.Width(120));
_data.UpdateItemComponentDic(_com, _toggle);
}
EditorGUILayout.EndHorizontal();
}
GUILayout.EndScrollView();
#endregion
}
private Transform GetSelectTranform()
{
return Selection.activeTransform;
}
private List<Transform> GetAllTransform(Transform tran)
{
List<Transform> _tempList = new List<Transform>();
if (tran == null)
{
return _tempList;
}
_tempList.Add(tran);
if (tran.childCount > 0)
{
for (int i = 0; i < tran.childCount; i++)
{
_tempList.AddRange(GetAllTransform(tran.GetChild(i)));
}
}
return _tempList;
}
private List<GenerateItemData> GetAllItemDataList(Transform tran, GenerateItemData parent = null, int siblingIndex = 0)
{
List<GenerateItemData> _tempList = new List<GenerateItemData>();
if (tran == null)
{
return _tempList;
}
GenerateItemData _data = new GenerateItemData();
_data.InitItem(tran, parent, siblingIndex);
_tempList.Add(_data);
if (!_data.ShowChildren)
{
return _tempList;
}
for (int i = 0; i < tran.childCount; i++)
{
_tempList.AddRange(GetAllItemDataList(tran.GetChild(i), _data, i));
}
return _tempList;
}
public string TemplatePath
{
get
{
return GenerateCodeManager.TemplateFilePath;
}
}
private void CheckTemplate()
{
string _testPath = GenerateCodeManager.DirectoryPath;
if (!Directory.Exists(_testPath))
{
Directory.CreateDirectory(_testPath);
}
if (!File.Exists(GenerateCodeManager.TemplateFilePath))
{
GenerateCodeManager.CheckFileExistAndCreate(GenerateCodeManager.TemplateFilePath);
}
}
/// <summary>
/// 根据模板写文件
/// </summary>
/// <param name="path"></param>
/// <param name="_allItemDataList"></param>
public void WriteFileWithTemplate(string path, List<GenerateItemData> _allItemDataList)
{
CheckTemplate();
//读
StreamReader _testReader = new StreamReader(TemplatePath, Encoding.Default);
string _text = _testReader.ReadToEnd();
string _scriptName = Selection.activeGameObject.name;
string _componentVariabelName = string.Empty;
string _getComponent = string.Empty;
string _setButtonEvent = string.Empty;
string _getButtonFunc = string.Empty;
Dictionary<string, CodeItemData> _itemDataDic = GenerateCodeManager.GetCodeItemDataDic(_allItemDataList);
foreach (var item in _itemDataDic)
{
string _variavleName = item.Value.GetVariableName();
string _path = item.Value.GetComponentPath();
_componentVariabelName += _variavleName;
_getComponent += _path;
_setButtonEvent += item.Value.SetButtonEvent();
_getButtonFunc += item.Value.GetButtonFunction();
}
_text = _text.Replace("$safeitemrootname$", _scriptName);
_text = _text.Replace("$componentvariablename$", _componentVariabelName);
_text = _text.Replace("$getcomponent$", _getComponent);
_text = _text.Replace("$setbuttonevent$", _setButtonEvent);
_text = _text.Replace("$setbuttonfunction$", _getButtonFunc);
_testReader.Close();
//写
FileStream _fs = new FileStream(path, FileMode.OpenOrCreate);
StreamWriter _textWriter = new StreamWriter(_fs, Encoding.Default);
_textWriter.Write(_text);
_textWriter.Flush();
_textWriter.Close();
_fs.Close();
System.Diagnostics.Process.Start("explorer.exe", path);
Debug.Log("代码生成成功!"+ path);
}
}
}
最后效果就是这样的:
额,这个好像不是最新的。我还改了点GUI的东西,有空在改吧。
啊,好累,就写这么多吧,有空了想到没有写到的又来补充吧,如果你恰好看到这篇博客,觉得有漏洞,记得给我说呀!
以上就是我用C#写的一个利用txt模板来生成代码的工具。如果你有更好的生成工具,期待你的分享!
边栏推荐
猜你喜欢
随机推荐
AcWing 609. Salary (implemented in C language)
KDD 2022 | 图“预训练、提示、微调”范式下的图神经网络泛化框架
AcWing 604. Area of circle (implemented in C language)
JS foundation 10
Which programming language will attract excellent talents?
Simulation of the Saier lottery to seek expectation
SEO优化的许多好处是与流量有直接关系
零基础C语言(一)
Ali three sides: what is the difference between using on or where in the left join associated table and the condition
不到一小时,苹果摧毁了15家初创公司
Vivo手机的权限管理
Remoteviews layout and type restriction source code analysis
fatal: unsafe repository (‘/home/anji/gopath/src/gateway‘ is owned by someone else)
RemoteViews布局和类型限制源码分析
Batch will png . bmp . JPEG format pictures are converted to Jpg format picture
Day28 strict mode, string JS 2021.09.22
What is DAPP system development and analytical understanding
1. print hourglass
來吧元宇宙,果然這熱度一時半會兒過不去了
Necessary for beginners PR 2021 quick start tutorial, PR green screen matting operation method