UI代码生成工具
一、需求背景
在一般手游项目开发过程中,新系统的开发必然伴随着UI页面的增加,UI系统的开发占了开发任务中很大的比重,提高UI系统开发的效率是一个值得研究的方向。
开发UI的时候,必不可少大量获取UI组件的代码,相信有一定经验的Unity3D开发,对下面这些代码都是烂熟于心,形成肌肉记忆了吧。
比如C#
xxxBtn = transform.Find("Path/xxx/xxx").GetComponent<Button>();
xxxBtn.onClick.AddListener(OnXxxBtnClick);
或是Lua
self.xxxBtn = self:GetChild("Path/xxx/xxx","Button")
self:AddOnClickListener(self.xxxBtn,self.OnXxxBtnClick);
其实很多项目都有自己的代码生成工具,但是封装和命名习惯每个项目每个人都不同,和几个项目的大佬交流了之后也是发现这类工具的使用率普遍不高,一起讨论了缺点和优化方向之后,(为了KPI)决定试试,于是有了下面的这个工具。
二、工具展示
三、操作说明
- 拖拽选择父物体
- 添加配置
需要生成代码的组件,需要按以下字段添加配置- Force Exist : 是否固定显示,为true的话,每个Transform右侧都会显示该类型组件(用于GameObject之类的GetComponents获取不到但经常用的组件) 否则只显示在配置列表中能匹配到的类型
- Type Name : 组件名 例:UnityEngine.UI.Button (其中命名空间可以省略)
- Show Name : 显示在Hierarchy面板的缩略名
- Build Class Name :实现了IBaseBuilder接口的代码生成类类名(下文详述)
- Content :预留字符串 可以传入拼接生成代码所需的自定义字符
- 在Hierarchy面板中的Toggle Group(根据上述配置生成)中勾选需要生成代码的组件
- 点击生成代码 生成的代码会自动复制到剪切板
- 将代码粘贴进脚本中
四、核心痛点和解决方案
- 操作体验
通常这类工具,传统的实现方式是获取父节点下所有transform的组件,在一个Editor页面中罗列出来,勾选自己需要的组件,点击生成,复制粘贴进脚本中。- 痛点
- 一个页面下的组件数量太多了,可能还需要搞个滑动列表来滑动选择,不够直观。
- 大家对获取组件代码的熟练度之高,几次拖拖拖点点点的功夫,靠肌肉记忆手写都已经写出来了
- 解决方案
- 将组件的勾选扩展到Hierarchy面板中
- 将组件的勾选扩展到Hierarchy面板中
- 痛点
操作更直观,手写应该也要在Hierarchy面板中复制路径Transform的名字,现在直接勾选,生成代码就好了。
- 代码风格不同
- 痛点
- 有的项目是C#代码(极少数)有的项目是Lua代码
- 有的项目对获取组件添加监听的方法做了不同的封装
- 代码风格不同,有的人喜欢加transform加this,有的人不加
- …
- 解决方案
- 没法解决。只能退而求其次,由使用者自己实现字符串拼接方法
- 定义了一个接口方法,传入CompnontNode数据实例类**类结构方便扩展,目前包含的字段有
- typeName 组件类型名称
- path 相对路径
- name transform.Name
- content 由配置传入的字符串
- 使用者实现BuildCode方法按自己/项目的编码风格实现字符串的拼接。
- 生成代码的时候获取配置中与组件类型对应的代码生成类的类名,通过反射实例化代码生成类,调用BuildCode方法,部分代码如下
- 定义了一个接口方法,传入CompnontNode数据实例类**类结构方便扩展,目前包含的字段有
- 没法解决。只能退而求其次,由使用者自己实现字符串拼接方法
- 痛点
public Interface IBaseBuilder
{
string BuildCode(ComponentNode data)
}
public class CompnontNode
{
public string typeName;
public string path;
public string transformName;
}
//反射部分伪代码实现
IBaseBuilder builder = (IBaseBuilder)asssembly.CreateInstance("命名空间." + "从配置中获取的类型名")
五、工具代码结构说明(有扩展需求的小伙伴可以留意)
- UICodeBuilder 工具主要功能实现的类,继承EditorWindows,实现主要是两个方法,其余都是Editor UI布局相关
- CreateChildsList (由父物体递归生成缓存字典Dictionay<InstanceId,相对路径>)
- HierarchyWindowOnGUI Hierarchy面板扩展(需InstanceId在缓存字典中存在才会显示)其中获取transform名字和相对路径的正则表达式有需要可以自行修改
- DataManager 管理配置中的数据,本工具配置的本地化存储采用ScriptableObject的形式,修改配置时会实时同步本地资源文件BuilderData
- BuildNode类可根据需求扩展,即工具面板上的配置的数据结构类
- ComponentNode类可根据需求扩展,即作为参数传入生成代码方法的数据结构类
- 配置文件BuilderData的读取路径为了方便直接写死了,如果要调整文件夹结构需要修改
- UIBuilderScriptableObject 上述ScriptableObject的实例类,如果本地BuilderData文件损坏,需要在此脚本中解开右键菜单栏扩展注释重新生成
- IBaseBuilder 接口 定义拼接代码字符串的方法
- ButtonBuilder 示例写法 小伙伴们可根据需求自行修改
- GameObjectBuilder 示例写法 小伙伴们可根据需求自行修改
- … 需要小伙伴们自行扩展实现
- ToolsDefine 一些常量定义在这里
六、优化的一些想法
- 增加自动复制进文件里的功能
我们现在的操作是这样的
在Unity中操作生成代码 -> 再切换IDE页面 -> 找准代码位置 -> Ctrl + V粘贴代码
操作频繁了其实也很烦,可以加入生成代码后找到lua文件自动复制进去的功能
需要比较严格的命名规范难做到通用。比如Prefab名字需要和lua文件一致或者起码有命名规律可循,lua中的UI代码需统一写在某个方法中。
满足以上命名条件的话,其实就是文件IO,字符串匹配的操作了,有需要的小伙伴可以自行扩展CreateCode方法。
假如一次操作能节约3s,一天大概需要这样操作50次,一年就能节省…总之很多很多的时间。 - 一个人的思想难免有局限,欢迎朋友们交流看法和提供建议,好的工具或工作流都是不断在实战中调整优化最终形成的。
七、总结
工具想做到其他人其他项目也能用难度真的是直线增加。反射是假快乐,写死才是真的快乐(辞退警告),碰巧在中台支援部门才有闲功夫折腾这些东西。工具估计能用上愿意用的人也不多,不过工具开发中的一些想法思路可以借鉴,随手写写自己用的小工具,打造一套自己用着顺手的偷懒用的,高度定制化的游戏引擎IDE,也是不错的追求。