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

在使用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();

            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;
        }
        /// 
        /// 当前子物体Transform
        /// 
        private Transform m_tran;
        /// 
        /// 物体名字
        /// 
        public string Name = string.Empty;
        /// 
        /// 父物体 ItemData 类
        /// 
        public GenerateItemData Parent;
        /// 
        /// 子物体层级(父物体为0)
        /// 
        public int Index = 0;
        /// 
        /// 第几个子物体(参照tran.SetAsFirstSibling)
        /// 
        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;
        /// 
        /// 唯一的key值,用层级index来做的。
        /// 
        public string OnlyKey
        {
            get
            {
                if (Parent != null)
                {
                    return Parent.OnlyKey + "&" + SiblingIndex;
                }
                return SiblingIndex.ToString();
            }
        }
        /// 
        /// 物体路径
        /// 
        public string Path
        {
            get
            {
                string m_path = Name;
                if (Parent != null)
                {
                    m_path = Parent.Path + "/" + Name;
                }
                return m_path;
            }
        }

        /// 
        /// 子物体上的组件
        /// 
        public List ItemComponentStrList = new List();

        private string GetItemComponentKey(string com)
        {
            string _showComponentKey = string.Format(m_showComponentKey, OnlyKey, com);
            return _showComponentKey;
        }

        /// 
        /// 获取 是否显示组件的状态
        /// 
        /// 
        /// 
        public bool GetItemComponentShowState(string com)
        {
            string _showComponentKey = GetItemComponentKey(com);

            if (GenerateCodeManager.EditorPrefsHasKey(_showComponentKey))
            {
                return GenerateCodeManager.EditorPrefsGetBool(_showComponentKey);
            }
            return false;
        }

        /// 
        /// 更新 是否显示组件的状态
        /// 
        /// 
        /// 
        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);
            }
        }
        /// 
        /// 是否显示子物体,用来折叠子物体
        /// 
        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;
        /// 
        /// 变量名
        /// 
        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 "";
            }
        }
        /// 
        /// 组件名
        /// 
        public string ComponentType;

        /// 
        /// 组件路径
        /// 
        public string Path;

        /// 
        /// 第几个相同的变量名
        /// 
        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;
        }

        /// 
        /// 变量声明
        /// 
        /// 
        public string GetVariableName()
        {
            return string.Format(Def.GetVariableString, ComponentType, VariableName);
        }
        /// 
        /// 获取组件的路径
        /// 
        /// 
        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);
        }
        /// 
        /// 设置点击事件
        /// 
        /// 
        public string SetButtonEvent()
        {
            if (ComponentType.Equals("AorButton")
                || ComponentType.Equals("Button"))
            {
                return string.Format(Def.SetButtonEventString, VariableName, EventName);
            }
            return "";
        }
        /// 
        /// 方法
        /// 
        /// 
        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 文件相关
        /// 
        /// 文件夹路径
        /// 
        public static string DirectoryPath
        {
            get
            {
                return  Application.dataPath.Replace("Assets", "Template").Replace(@"/",@"\");
            }
        }
        /// 
        /// 文件路径
        /// 
        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 m_editorPrefsList = new List();
        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 GetCodeItemDataDic(List dataList)
        {
            Dictionary _itemDataDic = new Dictionary();
            List _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 GetCodeItemDataList(List dataList)
        {
            List _itemDataList = new List();
            for (int i = 0; i < dataList.Count; i++)
            {
                GenerateItemData _data = dataList[i];
                List _tempList = GetCodeItemData(_data);
                _itemDataList.AddRange(_tempList);
            }

            return _itemDataList;
        }

        public static List GetCodeItemData(GenerateItemData data)
        {
            List _itemDataList = new List();
            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("测试测试");
            _window.Show();
        }

        private Transform m_selectTransform;
        private List m_allTransformList = new List();

        private List m_allItemDataList = new List();
        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 GetAllTransform(Transform tran)
        {
            List _tempList = new List();
            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 GetAllItemDataList(Transform tran, GenerateItemData parent = null, int siblingIndex = 0)
        {
            List _tempList = new List();

            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);
            }
        }
        /// 
        /// 根据模板写文件
        /// 
        /// 
        /// 
        public void WriteFileWithTemplate(string path, List _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 _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代码)