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

1,655 阅读4分钟

本文首发自inspoy的杂七杂八 | 菜鸡inspoy的学习记录

前言

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

View Prefab

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

0501

0502

添加UI

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

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#脚本,文件名随意,然后写一个类,类名也随意:

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时将菜单项置灰。

[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来生成代码并保存了

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上

string componentName = "SF" + viewName + "View";
AssetDatabase.Refresh();
var component = prefab.GetComponent(componentName);
if (component == null)
{
   prefab.AddComponent(getTypeByName(componentName));
}

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

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时收到通知,可以利用这个方法来给之后创建的脚本加上头部文件信息的注释。代码如下:

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上找到