用Unity做个游戏(五) - 编辑器扩展

前言

项目这个东西果然还是做起来才会发现坑,尽量早填上好了

View Prefab

上一篇的vwTest这个UI预设体的根节点vwTest是一个Panel控件,内容是个背景框,这个想了下不太妥,应该改一下,包含具体内容的控件不应该在根节点中,现在把根节点改成透明panel,尺寸为整个View的尺寸。背景框相关的放到根节点下边。

0501

0502

添加UI

为了更方便地添加UI,我给SFSceneManager加了几个静态方法,通过指定预设体、父节点的transform、层级顺序(越大越靠前),可以简化添加UI的步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static public GameObject addView(GameObject prefab, Transform trans = null, int sibIdx = -1)
{
if (prefab == null)
{
SFUtils.logWarning("prefab为空");
return null;
}
Transform parent = trans;
if (parent == null)
{
parent = SFSceneManager.uiRoot.transform;
}
var GO = GameObject.Instantiate(prefab, parent) as GameObject;
if (zOrder >= 0)
{
GO.SetSiblingIndex(sibIdx);
}
return GO;
}
static public GameObject addView(string viewName, Transform trans = null, int sibIdx = -1)
{
var prefab = Resources.Load(viewName) as GameObject;
if (prefab == null)
{
SFUtils.logWarning(string.Format("找不到view:{0}", viewName));
return null;
}
return SFSceneManager.addView(prefab, trans, sibIdx);
}

只提供第一个参数的话默认会加在整个UI界面的最前面,当然也可以指定加在某个自定义节点下面,也可以指定层级顺序

UI代码生成

设想的工作流程是这样:UI设计人员修改或创建UI Prefab,在Project视图里右键点击Prefab,会有一个导出生成代码的菜单项,点击它,就可以一键生成代码并且自动把View脚本挂载到Prefab上。为了实现这种效果,就必须扩展Unity的编辑器。

Unity提供了非常丰富的编辑器扩展接口,如添加菜单项,自定义Inspector面板,甚至自定义Scene视图中的辅助UI。

我们这里只使用添加菜单项的功能:在Assets中创建Editor文件夹,在里面创建一个C#脚本,文件名随意,然后写一个类,类名也随意:

1
2
3
4
5
6
7
8
public class SFUIExporterMenu
{
[MenuItem("Assets/SF/Export UI")]
private static void exportUI()
{
generateUICode(Selection.activeGameObject);
}
}

Selection.activeGameObject表示当前在Project视图中选中的Prefab,如果选中的不是Prefab这个就是null。

MenuItem特性表示这个方法用于扩展菜单项,参数"Assets/SF/Export UI"表示这个自定义菜单项的位置,在Assets菜单中添加的话,在Asset上右键就可以看到相应的菜单项了

0503

不过如果我手滑右键了其他非Prefab,显然这时是不应该允许导出的,因为这不是UI,所以还要加一个验证,选中非法的Asset时将菜单项置灰。

1
2
3
4
5
6
[MenuItem("Assets/SF/Export UI", true)]
private static bool exportUIValidation()
{
var GO = Selection.activeGameObject;
return GO != null && GO.name.Substring(0, 2) == "vw";
}

特性的第二个参数true代表这个方法是用来验证合法性的,返回true为合法,false非法。

然后就是根据Prefab来生成代码并保存了

1
2
3
4
5
6
7
string viewName = prefab.name.Substring(2);
string viewFilepath = string.Format("Assets/Scripts/UI/SF%sView.cs", viewName);
var viewFile = new FileInfo(viewFilepath);
var sw = viewFile.CreateText();
sw.Write(viewContent);
sw.Close();
// 生成viewContent的逻辑比较复杂,不在文章中贴出了

自动挂载脚本

当然最后要把生成的View脚本挂载在Prefab上

1
2
3
4
5
6
7
string componentName = "SF" + viewName + "View";
AssetDatabase.Refresh();
var component = prefab.GetComponent(componentName);
if (component == null)
{
prefab.AddComponent(getTypeByName(componentName));
}

下面是根据名称获取类型的方法,因为Component.AddComponent(string)已经被官方在5.0版本中废弃了,我们必须通过其他的方式曲线救国,下面的方法参考了ぼちつく的博客,这个方法的运行效率必然很低,尤其是项目规模大起来之后,不过至少给编辑器用的扩展不会在游戏运行时的阶段执行,而且也不会太频繁,所以性能稍微差一点也是可以接受的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static Type getTypeByName(string className)
{
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type type in assembly.GetTypes())
{
if (type.Name == className)
{
return type;
}
}
}
return null;
}

其他

研究编辑器扩展的时候发现了一个好玩儿的东西,继承UnityEditor.AssetModificationProcessor并实现OnWillCreateAsset()方法可以在创建Assets时收到通知,可以利用这个方法来给之后创建的脚本加上头部文件信息的注释。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SFScriptHeaderGenerator : UnityEditor.AssetModificationProcessor
{
static private string header =
"/**\n" +
" * Created on ##DateTime## by ##UserName##\n" +
" * All rights reserved.\n" +
" */\n\n";
public static void OnWillCreateAsset(string path)
{
path = path.Replace(".meta", "");
if (path.EndsWith(".cs"))
{
string fullText = header;
fullText = fullText.Replace("##DateTime##", System.DateTime.Now.ToString("yyyy/MM/dd"));
fullText = fullText.Replace("##UserName##", System.Environment.UserName);
fullText += File.ReadAllText(path);
File.WriteAllText(path, fullText);
}
}
}

效果如图:

0504

完整代码

上面贴出的代码片段由于篇幅限制只保留了关键部分,完整的代码可在我的github上找到

-------------本文结束-------------
0%