当前位置:网站首页>【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模板来生成代码的工具。如果你有更好的生成工具,期待你的分享!
边栏推荐
- fatal: unsafe repository (‘/home/anji/gopath/src/gateway‘ is owned by someone else)
- Difference (one dimension)
- Leetcode 705. 设计哈希集合
- 【C语言】结构体嵌套二级指针的使用
- How to deploy the software testing environment?
- 【JS】斐波那契数列实现(递归与循环)
- AGCO AI frontier promotion (2.16)
- What is DAPP system development and analytical understanding
- Connectionreseterror: [winerror 10054] the remote host forced an existing connection to be closed
- Practice and Thinking on the architecture of a set of 100000 TPS im integrated message system
猜你喜欢

Source code analysis of ArrayList

What is data compliance? How to achieve data compliance?

什么是数据合规?怎样做到数据合规?

Practice and Thinking on the architecture of a set of 100000 TPS im integrated message system

RemoteViews布局和类型限制源码分析
![Connectionreseterror: [winerror 10054] the remote host forced an existing connection to be closed](/img/9a/97813f5ac4d7c15711891cff25b9dd.jpg)
Connectionreseterror: [winerror 10054] the remote host forced an existing connection to be closed

自定义标题栏View

【C语言】判断三角形

【C语言】NextDay问题

Simple understanding of ThreadLocal
随机推荐
Day32 JS note event (Part 1) September 27, 2021
Contract quantification system development (construction explanation) - contract quantification system development (source code analysis and ready-made cases)
Share the easy-to-use fastadmin open source system - practical part
Day23 JS notes 2021.09.14
Simple understanding of ThreadLocal
Software test interview classic + 1000 high-frequency real questions, and the hit rate of big companies is 80%
What is data compliance? How to achieve data compliance?
Day24 JS notes 2021.09.15
Contract quantitative trading system development | contract quantitative app development (ready-made cases)
In less than an hour, apple destroyed 15 startups
来吧元宇宙,果然这热度一时半会儿过不去了
AcWing 604. Area of circle (implemented in C language)
【JS】斐波那契数列实现(递归与循环)
Array method in JS 2021.09.18
Source code analysis of ArrayList
AcWing 609. Salary (implemented in C language)
AGCO AI frontier promotion (2.16)
Redis hash hash type string (5)
【C语言】文件读写函数使用
PrecomputedTextCompat用法及原理