当前位置:网站首页>[unity Editor Extension practice] dynamically generate UI code using TXT template
[unity Editor Extension practice] dynamically generate UI code using TXT template
2022-06-28 12:21:00 【Unique_ eight hundred and forty-nine million nine hundred and n】
In the use of Unity3D Development process , As working hours go by , You will certainly find the code written , There are only a few templates . For example, control. UI Of View Code , You will find that the format is the same , Add reference 、UI Variable declarations 、Awake Middle feeding UI Variable assignment 、 add to Button event 、Destroy Logout event in . It can be said that it can be written through a template . Here is an example that I use C# Write a use txt Template to generate code , If there is something bad , Also please understand .
I wrote a template generated code before ,Unity Use in Txt The template to generate View Code , But I always feel very inconvenient , It's useless , I want to write a better one to replace it . So the next thing to write , count Version0.0.2 Well . There are also great gods on the Internet who have written some code generators , They just use WriteLine Written , Not suitable for junior players like me , Because I think if I want to change it to the generator I need , Too many changes , So make a template , Want to change less code .
Let's sort out my thoughts first :
1、 Traverse each sub object , Preview the objects of each sub object .
2、 Select whether specific components need to be generated UI Code .
3、 Click generate button , Traverse the selected components , Read the template , Generate the corresponding code , Write corresponding text .
The reason is so simple , You need to know a little about the editor , Can read and write files a little , It's basically done , The code written here is a bit imperfect , Make do with , Optimize later .
Considerations I have taken into account :
1、C# Code cannot have duplicate variable names , If you have many components to use for the same sub object , Or there are multiple sub objects with the same name on the same prefabrication , The generated variable name needs to be processed . I'm adding a variable name index As key Save it in the dictionary , If there is one with the same name , Add a... After the variable name index++;
2、 Variable name specification , Variable names cannot have spaces , There are also some special symbols , Take care of it. . For example, you use the right button to create Scroll View Words , There will be a space in the middle ;
3、 Templates 、 Or there is a placeholder in the string to be replaced , There is a problem when there are left and right braces . For example, when you need to generate a method body , Pay attention to your writing , If you don't know how to solve it , You can look at it C# Use String.Format It's common to report mistakes There are some solutions .
4、 If you're like me , Use EditorPrefs To record which components were selected , Definitely not EditorPrefs.DeleteAll To clean up all the data , because EditorPrefs It's not just you who are using ,Unity Also using this , If you use , You'll find that , You used to open Unity You can see that the old project paths are missing .
Let's sum it up first , Write again when you think of it , Next, the code .
One 、 Component related Item
Because you want to find sub objects , Components , Development of sub objects , Component selection , So I created a class , Used to manage a specific sub object . Recursively create when traversing the root node , You can get some basic information in this class , Such as : Object name , The path to the root node , Level ( To fold GUI Of ), List of required components .
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>
/// Current sub object Transform
/// </summary>
private Transform m_tran;
/// <summary>
/// Object name
/// </summary>
public string Name = string.Empty;
/// <summary>
/// The parent object ItemData class
/// </summary>
public GenerateItemData Parent;
/// <summary>
/// Sub object level ( The parent object is 0)
/// </summary>
public int Index = 0;
/// <summary>
/// The number of sub objects ( reference 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>
/// Unique key value , Use level index To do the .
/// </summary>
public string OnlyKey
{
get
{
if (Parent != null)
{
return Parent.OnlyKey + "&" + SiblingIndex;
}
return SiblingIndex.ToString();
}
}
/// <summary>
/// Object path
/// </summary>
public string Path
{
get
{
string m_path = Name;
if (Parent != null)
{
m_path = Parent.Path + "/" + Name;
}
return m_path;
}
}
/// <summary>
/// Components on sub objects
/// </summary>
public List<string> ItemComponentStrList = new List<string>();
private string GetItemComponentKey(string com)
{
string _showComponentKey = string.Format(m_showComponentKey, OnlyKey, com);
return _showComponentKey;
}
/// <summary>
/// obtain Whether to display the status of components
/// </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>
/// to update Whether to display the status of components
/// </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>
/// Whether to display sub objects , Used to fold sub objects
/// </summary>
public bool ShowChildren
{
get
{
if (GenerateCodeManager.EditorPrefsHasKey(ShowChildrenKey))
{
m_showChildrent = GenerateCodeManager.EditorPrefsGetBool(ShowChildrenKey);
}
return m_showChildrent;
}
set {
GenerateCodeManager.EditorPrefsSetBool(ShowChildrenKey, value);
}
}
}
}
Two 、 The code is off Item
For and prefabricated Item Separate , I've created a new class , Used to manage code generation , This is the specific component , Get the variable name in this class , Function name ( If it is Button Components ), Component name , route , Variable name Index( Use this to avoid repeating variable names ).
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace GenerateCodeProject
{
public class CodeItemData
{
private string m_variableName;
/// <summary>
/// Variable name
/// </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>
/// Component name
/// </summary>
public string ComponentType;
/// <summary>
/// Component path
/// </summary>
public string Path;
/// <summary>
/// The same variable names
/// </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>
/// Variable declarations
/// </summary>
/// <returns></returns>
public string GetVariableName()
{
return string.Format(Def.GetVariableString, ComponentType, VariableName);
}
/// <summary>
/// Get the path of the component
/// </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>
/// Set click event
/// </summary>
/// <returns></returns>
public string SetButtonEvent()
{
if (ComponentType.Equals("AorButton")
|| ComponentType.Equals("Button"))
{
return string.Format(Def.SetButtonEventString, VariableName, EventName);
}
return "";
}
/// <summary>
/// Method
/// </summary>
/// <returns></returns>
public string GetButtonFunction()
{
if (ComponentType.Equals("AorButton")
|| ComponentType.Equals("Button"))
{
return string.Format(Def.SetButtonFunctionString, EventName);
}
return "";
}
}
}
3、 ... and 、 Auxiliary code class
In order to avoid the code is too dirty , Fake Ba means to put some constants 、 General methods are sorted out .
Constant ( This needs to be changed into the sub son you need ):
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";
}
}
Method : File related templates are created using , Sustainability data is about mapping GUI It is used to display and hide components in the window , Code generation is related to code generation after a group , obtain CodeItemData class .
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace GenerateCodeProject
{
public class GenerateCodeManager
{
#region Documents related to
/// <summary>
/// Folder path
/// </summary>
public static string DirectoryPath
{
get
{
return Application.dataPath.Replace("Assets", "Template").Replace(@"/",@"\");
}
}
/// <summary>
/// File path
/// </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))
{
// Write
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 Sustainability data
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 Code generation
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(" The cycle is greater than 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
}
}
Four 、 draw GUI window
This script mainly draws preview objects 、 Select the window of the specific component ( This was first written , It's a bit messy , There is no arrangement ).
If you use EditorGUILayout.Foldout To fold and prefabricate , Remember to use EditorGUI.indentLevel Indent your 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/ Test test ")]
public static void ShowMyWindow()
{
GenerateCodeWindow _window = EditorWindow.GetWindow<GenerateCodeWindow>(" Test test ");
_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(" see "))
{
m_selectTransform = GetSelectTranform();
Repaint();
}
if (GUILayout.Button(" Empty "))
{
m_selectTransform = null;
GenerateCodeManager.ClearAllPrefs();
Repaint();
}
if (GUILayout.Button(" Generate "))
{
m_allItemDataList = GetAllItemDataList(m_selectTransform);
string _path = GenerateCodeManager.GetGenerateScriptPath(Selection.activeGameObject.name);
WriteFileWithTemplate(_path, m_allItemDataList);
}
if (GUILayout.Button(" Test test "))
{
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>
/// Write files according to the template
/// </summary>
/// <param name="path"></param>
/// <param name="_allItemDataList"></param>
public void WriteFileWithTemplate(string path, List<GenerateItemData> _allItemDataList)
{
CheckTemplate();
// read
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();
// Write
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(" Code generation succeeded !"+ path);
}
}
}
The final effect is like this :

forehead , This doesn't seem to be the latest . I also made some changes GUI Things that are , Change it when you are free .
ah , Tired , Just write so much , When you are free, I want to add what I haven't written , If you happen to see this blog , I think there are loopholes , Remember to tell me !
That's what I use C# Write a use txt Template to generate code . If you have a better build tool , Looking forward to your sharing !
边栏推荐
- CDC synchronization if the primary key of a database table changes, will it be synchronized into two data or will it be synchronized to update the primary key?
- 來吧元宇宙,果然這熱度一時半會兒過不去了
- [C language] use of file read / write function
- 不到一小时,苹果摧毁了15家初创公司
- [C language] use of nested secondary pointer of structure
- Levels – 虚幻引擎场景制作「建议收藏」
- 【C语言】结构体嵌套二级指针的使用
- What is data compliance? How to achieve data compliance?
- Difference (one dimension)
- 洛谷_P1303 A*B Problem_高精度计算
猜你喜欢

.NET混合开发解决方案24 WebView2对比CefSharp的超强优势

Prefix and (2D)

ByteV搭建动态数字孪生网络安全平台----助力网络安全发展

【Unity编辑器扩展基础】、EditorGUILayout (一)

深度学习又有新坑了!悉尼大学提出全新跨模态任务,用文本指导图像进行抠图...

【Unity编辑器扩展实践】、利用txt模板动态生成UI代码

KDD 2022 | graph neural network generalization framework under the paradigm of "pre training, prompting and fine tuning"

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

Custom title bar view
![[Beijing University of Aeronautics and Astronautics] information sharing for the first and second examinations of postgraduate entrance examination](/img/06/df5a64441814c9ecfa2f039318496e.jpg)
[Beijing University of Aeronautics and Astronautics] information sharing for the first and second examinations of postgraduate entrance examination
随机推荐
期货开户有门槛吗,如何网上安全的开通期货账户
Custom title bar view
[C language] three sorting methods for random number files
【附源码+代码注释】误差状态卡尔曼滤波(error-state Kalman Filter),扩展卡尔曼滤波,实现GPS+IMU融合,EKF ESKF GPS+IMU
.NET混合开发解决方案24 WebView2对比CefSharp的超强优势
【vi/vim】基本使用及命令汇总
【Unity编辑器扩展基础】、EditorGUILayout (一)
AcWing 607. Average 2 (implemented in C language)
【C语言】判断三角形
[source code + code comments] error state Kalman filter, extended Kalman filter, gps+imu fusion, EKF eskf gps+imu
IDEA全局搜索快捷设置
【C语言】文件读写函数使用
. Net hybrid development solution 24 webview2's superior advantages over cefsharp
Zero basic C language (I)
吐血推荐17个提升开发效率的“轮子”
请问通达信股票软件可靠吗?在上面交易股票安全吗?
30套JSP网站源代码合集「建议收藏」
fatal: unsafe repository (‘/home/anji/gopath/src/gateway‘ is owned by someone else)
Source code analysis of ArrayList
AcWing 610. Salary and bonus (implemented in C language)