在使用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);
}
}
}
}
二、代码关的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 "";
}
}
}
三、辅助代码类
为了避免代码太脏乱差,假巴意思的将一些常量、通用方法整理出来。
常量(这个需要改成自己需要的亚子):
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 文件相关
///
/// 文件夹路径
///
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
}
}
四、绘制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);
}
}
}
最后效果就是这样的:
额,这个好像不是最新的。我还改了点GUI的东西,有空在改吧。
啊,好累,就写这么多吧,有空了想到没有写到的又来补充吧,如果你恰好看到这篇博客,觉得有漏洞,记得给我说呀!
以上就是我用C#写的一个利用txt模板来生成代码的工具。如果你有更好的生成工具,期待你的分享!