美文网首页Unity开发
Unity编辑器扩展实践一、利用txt模板动态生成UI代码

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

作者: 不文不武的禾文 | 来源:发表于2020-04-10 22:10 被阅读0次

    在使用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);
                }
            }
        }
    }
    
    
    image.gif

    二、代码关的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 "";
            }
        }
    }
    
    
    image.gif

    三、辅助代码类

    为了避免代码太脏乱差,假巴意思的将一些常量、通用方法整理出来。

    常量(这个需要改成自己需要的亚子):

    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";
    
        }
    
    }
    
    
    image.gif

    方法:文件相关的创建模板的时候会用到,可持续化数据相关的是绘制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
    
        }
    }
    
    
    image.gif

    四、绘制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);
    
            }
    
        }
    }
    
    
    image.gif

    最后效果就是这样的:

    image image.gif

    额,这个好像不是最新的。我还改了点GUI的东西,有空在改吧。

    啊,好累,就写这么多吧,有空了想到没有写到的又来补充吧,如果你恰好看到这篇博客,觉得有漏洞,记得给我说呀!

    以上就是我用C#写的一个利用txt模板来生成代码的工具。如果你有更好的生成工具,期待你的分享!

    相关文章

      网友评论

        本文标题:Unity编辑器扩展实践一、利用txt模板动态生成UI代码

        本文链接:https://www.haomeiwen.com/subject/oasjmhtx.html