首先上一张我在工作中喜欢使用的模式框架模板。
组成部分
表格数据
每张表格都有一张自己的数据类,继承ICsvData,解析表格的类只有一个,就是ParseCsv<>泛型单例继承IParseCsv,作为一个单例,表格数据相关可以放在一个公共文件夹CSV下来,这样以后其他系统用到这些表格就可以直接使用。CsvEditorConfig常量数据
窗口
通常一个大窗口内部会分为几个子Panel,例如可以这样分:
1.TabBar
2.TreeHierarchy
3.PropertyPanel就是中间的属性内容面板
4.Graphics 或者 预览模型面板
一个窗口继承一个EditorWindow,而这些子Panel,也就是UI Panel可以作为一个类对象在这个窗口对象中引用,所有的UIPanel都继承UIBase,UIBase里面处理好了鼠标点击事件,键盘事件,提供外部接口OnGUI之前OnPrepareGUI() 可以重写,在OnPrepareGUI里面添加child ui panel给继承UiBase panel的childList 等,在绘制任何一个uibase panel之后都会绘制他的child list的孩子panel,上面分成的大类可以统一放在EditorWindow窗口类的OnGUI里面一个个按顺序OnPrepareGUI后在OnGUI的调用绘制窗口。
绘制列表
列表可以是单层级普通垂直列表,也可以是多层级抽屉垂直列表那种,我这里说一下单层级列表,多层级抽屉也类似,只是我现在刚好做了个单层级的。TreeHierarchy类的OnPrepareGUI方法的时候,根据列表的数量创建继承UIPanel的child panel,添加进childList中,child panel里面自己根据传递的rect参数进行绘制,多层级就是根据数据设计多层级的child panel类。
编辑器之间窗口UI数据变更影响到其他窗口的问题
使用事件来影响,编辑器UI跟普通游戏UI一样,一个UI变了,需要影响其他UI,就发事件出去。
内部子Panel之间的分割线拖动问题
每个子Panel绘制之前都会搞清楚自己的Rect 范围,这个是在EditorWindow OnGUI里面传递设计的,所以我这里建了一个继承UIBase的Resizer类,resizer类绘制在子panel的分割线之间,根据需要的类型绘制成竖直型和水平型的,里面可以重写鼠标拖动事件来修改绘制resizer的rect范围尺寸参数,从而影响到其他子Panel的范围绘制即可。
图形化界面绘制
由于图形化界面绘制通常要写很多代码,所以我同样也抽象出了几个类,直接说图形化窗口GraphicsDefaultPanel,它里面已经绘制好了默认的网格背景和拖动移动网格的功能,提供方法重写给子类重写绘制背景上的内容,绘制在背景上的形状图形,GraphicsItem可以派生出多个其他形状,这里现在就用Cube,以及连接线,连接线现在两种,一种图形与图形之间的连接,这种的有鼠标,键盘选中删除等默认事件方法,另外一种是EntryLink就是在准备去连接的时候
的这种根据鼠标移动的线,他不需要选中删除等默认事件,形状和线类都会把panel作为父亲传进去,形状里面有一些默认行为,重写如何高亮,是否选中,鼠标点击范围内选中,右键打开context menu菜单,按Delete删除,拖动移动位置等行为,某些需要重写方法,移动位置不需要,连接线的行为就是按delete删除,可重写。所有继承GraphicsBasePanel的图形化窗口都有一个GraphicsDataState的引用,他持有当前图形化窗口的一些即时数据的状态,是否处于连接中,当前选中连接线,当前选中图形Item等。
EditorWindowState
一个单独的类单例,保存当前统一EditorWindow下的所有即时状态,如果editorwindow下包含图形化窗口,他也持有GraphicsDataState,图形化窗口的dataState就可以从这里传过去。
窗口下的杂项数据
单独弄一个Config类,里面可以有const,static等想要的配置数据。
大量容易修改的属性数据如何绘制。
仿照odin的特性的方式根据反射绘制,写一套根据特性绘制的框架,在需要绘制属性的地方提供外部类serobj需要的时候创建利用changeobj(),传递要绘制的类进去,调用Draw()方法绘制。
个性化的文本框,输入框,标题,按钮等
平时总结号写过的漂亮的组件,组成静态方法,可以写在一个公共的EditorCommon类里面,方便使用。
表格与窗口间的数据存取
单独弄一个单例类DataMgr,里面LoadData()方法调用parsecsv的方法读取,savedata调用parsecsv的方法保存。
文件夹式TreeViewMenu面板和无文件夹,单文件式面板
单文件
多文件
单文件面板只需要有一个itembase类继承TreeBtnItembase,然后window下new多个itembase,重写他的OnDelete和DisplayName即可。
多文件需要考虑打组和拖动排序或者创建新文件夹,派生类itembase继承TreeDragItemBase,选中放在item里面,不用管,expand属性放在数据类里面,需要重写,OnDelete需要重写,HasChild需要重写,OnDoubleClick 方法,双击展开回调,OnDragEnd回调是当一个itembase拖到当前这个itembase之后发生回调,LeftMouseDown鼠标在当前itembase点击之后的回调。LineItem是向上向下拖动之后会显示出来的下划线的样式。
Undo,ReDO问题
我们要思考,为什么像unity自身所带的inspector界面,unity的官方编辑器界面都是自带undo,redo功能的呢?主要是因为他们那些要绘制的属性在绘制之前都会把属性包裹成SerializedProperty,这样修改值得时候unity就可以知道这次操作,帮我们处理好undo的问题,就不用老是自己去思考调undo,redo的函数了。 另外,值得一提的是PropertyDrawer我们都知道是用来绘制可序列化属性类或者结构体的,而继承了monobehavior的inspector面板上可以直接拿到需要绘制的类的serialobject类,所以inspector面板除了重新propertydraw以外我们不需要做其他操作,而如果想在我们自己的界面上使用propertydrawer,我们就要找到把需要绘制的类变成serialobject的方法(例如scriptableobject),拿到serialobject之后就可以使用findproperty拿到serializedproperty,使用propertyfield原始方法绘制即可默认具有undo,redo操作了。
PropertyDrawer
propertyDrawer要和PropertyAttribute搭配使用。自适应默认serializedproperty的显示外观。而继承的Attribute需要自己反射找到来控制
一些小技巧:
预览窗口的实现方式
使用SceneView
- 含有GuiStyle的参数可以直接填写那个默认样式的字符串,就可以让当前gui 使用这个样式
- 划线使用Handles,画之前可以使用Handles.color全局修改一次颜色
- 绘制通用GUI的时候可以通过GUI.backgroundColor = Color.green;来修改默认绘制颜色
- 不能在GUILayout里面使用GUI的rect画图
- 不能在Layout 里面使用传统GUI画图
- 传统GUI都是世界坐标,没有局部坐标的说法,Layout有局部坐标的说
- 在GUILayout的布局里面使用EditorGUILayout的布局是没有用的,GUILayout里面就用GUILayout
- OnGUI之后获得的鼠标位置才是正确的在当前GUI下的局部坐标位置