Allen Kashiwa Blog

羁旅红尘不曾醉,笑歌年少且长行。

专注游戏开发与设计


唯佳人与游戏不可辜负

Unity预设序列化的自动化方案

预设

在Unity中,我们会将重复使用的资源做成预设(Prefab)。预设上可以挂载脚本并在Aspector面板中指定可序列化的值便于配置。

在实际操作过程中,我们的脚本上需要配置的部分可能常常需要指定一些固定的组件。加入有这样的一个脚本:

image

像这样,rigBody和capsuleCollider如果在游戏运行时获取会有些消耗,而在Editor模式下又需要我们每次从Hierarchy面板拖动到Aspector面板来指定费时费力。而这一切我们可以通过脚本自动完成。我们在Monster脚本上添加如下接口:

public void DoSerialized()
{
	rigBody = GetComponent<Rigidbody>();
	capsuleCollider = GetComponent<CapsuleCollider>();
}

Apply

Unity提供了在预设Apply时对该预设进行操作的Attribute:InitializeOnLoadMethod

我们在Editor目录下创建一个脚本AssetHelper,实现该接口:

static void StartInitializeOnLoadMethod()
{
    // 注册Apply时的回调
    PrefabUtility.prefabInstanceUpdated = delegate(GameObject instance)
    {
        if(instance)
        SaveMonsterPrefab(instance);
    };
}    

static void SaveMonsterPrefab(GameObject instance)
{
    string prefabPath = AssetDatabase.GetAssetPath(PrefabUtility.GetPrefabParent(instance));
    if(!IsMonsterPrefab(prefabPath))
        return;

    Debug.LogFormat("SaveMonsterPrefab Path = {0}", prefabPath);
    Monster comp = instance.GetComponent<Monster>();
    if (null == comp)
    {
        string msg = string.Format("{0} 缺少Monster组件", prefabPath);
        EditorUtility.DisplayDialog("Apply a Monster prefab!", msg, "OK");
        return;
    }

    comp.DoSerialized();
}


static bool IsMonsterPrefab(string path){
    if(path.Contains(MONSTER_FOLDER) && Path.GetExtension(path) == ".prefab")
        return true;

    return false;
}

以后创建Prefab的时候就可以直接添加一个Monster脚本,按一下Apply就行了。

保存

Apply的方法有个明显的缺点,那就是Prefab的改动有时候不会拖到Hierarchy中,而是直接修改,然后Ctrl+S保存,从而没有Apply过程。不用担心,即便这样Unity也有解决方案。

我们将AssetHelper类继承UnityEditor.AssetModificationProcessor类,并实现OnWillSaveAssets方法。这样就可以在资源保存时对资源做操作。实现细节如下:

static string[] OnWillSaveAssets(string[] paths){
    SaveMonsterPrefabs(paths);
    return paths;
}

static void SaveMonsterPrefabs(string[] paths)
{
    foreach (string path in paths)
    {
        if(!IsMonsterPrefab(path))
            continue;

        Debug.LogFormat("SaveMonsterPrefabs {0}", path);

        GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(path, typeof(GameObject));
        if(prefab == null)
        {
            Debug.LogWarning(string.Format("Can not load prefab {0}", path));
            continue;
        }

        GameObject go = UnityEngine.Object.Instantiate(prefab) as GameObject;
        Monster comp = go.GetComponent<Monster>();
        if (null == comp)
        {
            Debug.LogWarning(string.Format("{0} 缺少Monster组件", path));
            continue;
        }

        comp.DoSerialized();
        PrefabUtility.ReplacePrefab(go, prefab);
        UnityEngine.Object.DestroyImmediate(go);
    }
}

这样一来对资源的修改都会做序列化了。

后记

本文所有源代码依然放在我的Github上,有兴趣的朋友可以下来自行参考。

用支付宝请我喝咖啡

用微信请我吃辣条

最近的文章

眼见为实——如何在Unity中画出碰撞范围

碰撞器游戏中经常需要做范围判定,常见的方式是进行物理上的碰撞检测。在Unity引擎中提供了各种类型的Collider和碰撞检测接口。其中,Collider可以在Scene窗口中看到绿色线标识的范围。例如,CapsuleCollider:这样的范围标识能带来直观的感受。但是在开发过程中,范围判定不一定全是通过Collider来做检测,并且挂载Collider的物体极有可能在我们需要看到显示范围时已经被销毁。所以,我们有必要做一个能画出各种几何图形的工具。Unity中提供的GizmosDraw...…

继续阅读
更早的文章

Unity——来个可变角度的扇形技能指示器

技能指示器如果你的游戏有战斗系统,那极有可能会做成技能系统,如果你的游戏有技能系统,那极有可能需要提示当前技能的攻击范围,以便玩家预判命中或者闪避这次攻击。具体的形式如这样的:这样的:还有这样的:矩形(箭头也算是矩形的)和圆形比较好弄,可以绘制好贴图用一个面片搞定。大小通过缩放面片实现。麻烦的是扇形。因为技能肯定不单一,扇形的角度就会有变化。不可能每种角度的扇形都做个特效,这样游戏资源会变多而且策划不能随意调整角度。所以,这个时候程序员们就得解决这个问题,让这样的范围指示器自动生成,避免繁...…

继续阅读