Unity编辑器工具开发,批量修改Prefab

【Unity编辑器扩展】UI变量代码自动生成工具(编辑器扩展干货/大幅提高效率)_TopGames的博客-CSDN博客_unity编辑器扩展工具

比如,项目的UI界面Prefab都拼好了,突然要求更换所有文字字体. 再比如,我用Ferr2D Terrain Tool费劲九牛二虎之力拖出了一百多关地形,突然发现Unity自带的SpriteShape2D更好用,又节省资源.如果一个一个手动修改,还不得把人累死,更何况也很难保证不存在漏网之鱼. OK,所以我觉得,懒惰挺好的,因为它是我寻找高效工作方式的动力?

首先,在做批处理之前必须要清楚一个问题,那就是手动修改Prefab的步骤. 批处理无非就是用代码自动化重复这些步骤,强大的Unity编辑器已经留好了这些操作的API, 通过手动可以执行的操作也都可以通过API来完成.

一.批量更换字体, 其实就是修改Text的font属性, 不涉及Prefab删/增节点

0.先做一个界面,方便选择字体文件:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEditor;

public class PrefabTools : EditorWindow
{
    private static Font toFont;
    private static TMP_FontAsset tmFont;
    
    [MenuItem("Game Framework/Replace Font")]
    public static void ShowWin()
    {
        EditorWindow.CreateInstance<PrefabTools>().Show();
    }
    private void OnGUI()
    {
        GUILayout.Space(10);
        toFont = (Font)EditorGUILayout.ObjectField(new GUIContent("Font:"),toFont, typeof(Font), true, GUILayout.MinWidth(100f));
        tmFont = (TMP_FontAsset)EditorGUILayout.ObjectField(new GUIContent("TMP_FontAsset:"),tmFont, typeof(TMP_FontAsset), true, GUILayout.MinWidth(100f));
        terrTemplate = (GameObject)EditorGUILayout.ObjectField(new GUIContent("SpriteShape Template"), terrTemplate, typeof(GameObject), true, GUILayout.MinWidth(100f));
        terrPfb = (GameObject)EditorGUILayout.ObjectField(new GUIContent("Terr Prefab"), terrPfb, typeof(GameObject), true, GUILayout.MinWidth(100f));
        GUILayout.Space(10);
        if (GUILayout.Button("Replace Font"))
        {
            //ReplaceFont();
        }
        if (GUILayout.Button("Replace SpriteShape"))
        {
            
            //ReplaceTerr();
            //CreateJoints();
            //FindAllScripts();
        }
    }
}

1.找到所有UI界面Prefab:

string[] sdirs = { "Assets/MainGame/Prefabs/UI" };
var asstIds = AssetDatabase.FindAssets("t:Prefab", sdirs);
for (int i = 0; i < asstIds.Length; i++)
{
      string path = AssetDatabase.GUIDToAssetPath(asstIds[i]);
      var pfb = AssetDatabase.LoadAssetAtPath<GameObject>(path);
}

2.获取Prefab上所有Text组件,修改font

            var texts = pfb.GetComponentsInChildren<Text>(true);
            foreach (var item in texts)
            {
                item.font = toFont;
            }
            var tmTexts = pfb.GetComponentsInChildren<TextMeshProUGUI>(true);
            foreach (var item in tmTexts)
            {
                item.font = tmFont;
            }

3.保存修改后的Prefab

PrefabUtility.SavePrefabAsset(pfb, out bool success);

当然还可以显示一个进度条,给用户一些反馈. 

完整方法代码:

public static void ReplaceFont()
    {
        EditorUtility.DisplayProgressBar("Progress", "Replace Font...", 0);
        var asstIds = AssetDatabase.FindAssets("t:Prefab", sdirs);
        int count = 0;
        for (int i = 0; i < asstIds.Length; i++)
        {
            string path = AssetDatabase.GUIDToAssetPath(asstIds[i]);
            var pfb = AssetDatabase.LoadAssetAtPath<GameObject>(path);
            //var pfb = PrefabUtility.InstantiatePrefab(pfbFile) as GameObject;//不涉及增删节点,不用实例化
            var texts = pfb.GetComponentsInChildren<Text>(true);
            foreach (var item in texts)
            {
                item.font = toFont;
            }
            var tmTexts = pfb.GetComponentsInChildren<TextMeshProUGUI>(true);
            foreach (var item in tmTexts)
            {
                item.font = tmFont;
            }
            PrefabUtility.SavePrefabAsset(pfb, out bool success);
            if (success)
            {
                count++;
            }
            EditorUtility.DisplayProgressBar("Replace Font Progress", pfb.name, count / (float)asstIds.Length);
        }
        EditorUtility.ClearProgressBar();
    }

二.批量Ferr2D Terrain地形转2D SpriteShape

我用的Unity版本是2019.2.3f1, 2D SpriteShape目前还是预览版, 但是不影响它的方便好用. Unity论坛有详细介绍:SpriteShape

原理: 实际上就是把已经拖好的Ferr2D Terrain地形的顶点给SpriteShape用.

需要注意的是,这里需要对prefab增/删节点, Unity是不允许修改prefab实例的结构的,它会提示让你打开prefab进行修改. 而这个双击打开Prefab的操作就是对应API, PrefabUtility.LoadPrefabContents(asset_path), 通过这个API打开Prefab就可以任意修改prefab结构了.

完整方法代码:

public static void ReplaceTerr()
    {
        EditorUtility.DisplayProgressBar("Progress", "Replace SpriteShape...", 0);
        string[] dirs = { "Assets/MainGame/Prefabs/Entity/Levels" };
        var asstIds = AssetDatabase.FindAssets("t:Prefab", dirs);
        int count = 0;
        for (int i = 0; i < asstIds.Length; i++)
        {
            string path = AssetDatabase.GUIDToAssetPath(asstIds[i]);
            var lvRoot = PrefabUtility.LoadPrefabContents(path);
            var terrs = lvRoot.GetComponentsInChildren<Ferr2DT_PathTerrain>();
            bool changed = terrs.Length > 0;
            for (int j = 0; j < terrs.Length; j++)
            {
                var terr = terrs[j];
                var spShape = PrefabUtility.InstantiatePrefab(terrTemplate, terr.transform.parent) as GameObject;
                spShape.name = string.Format("SpriteShape_{0}", j);
                spShape.transform.position = terr.transform.position;
                spShape.transform.rotation = terr.transform.rotation;
                spShape.transform.localScale = Vector3.one;
                var spline = spShape.GetComponent<UnityEngine.U2D.SpriteShapeController>().spline;
                spline.Clear();
                var pathDt = terr.PathData;
                var points = pathDt.GetPathRaw();
                int rawIndex = spline.GetPointCount() - 1;
                for (int pindex = 0; pindex < points.Count; pindex++)
                {
                    var p = points[pindex] * terr.transform.localScale;
                    spline.InsertPointAt(pindex, p);
                }
                spShape.GetComponent<UnityEngine.U2D.SpriteShapeController>().BakeCollider();
                var logic = terr.GetComponent<PhysicsTarget>();
                if (null != logic)
                {
                    if (UnityEditorInternal.ComponentUtility.CopyComponent(logic))
                    {
                        UnityEditorInternal.ComponentUtility.PasteComponentAsNew(spShape);
                    }
                }
            }
            for (int index = terrs.Length - 1; index >= 0; index--)
            {
                DestroyImmediate(terrs[index].gameObject);
            }
            if (changed)
            {
                PrefabUtility.SaveAsPrefabAsset(lvRoot, path);
            }

            PrefabUtility.UnloadPrefabContents(lvRoot);
            count++;
            EditorUtility.DisplayProgressBar("Replace SpriteShape Progress", path, count / (float)asstIds.Length);
        }
        EditorUtility.ClearProgressBar();
    }

代码中还有两个比较常用的API, 就是组件的复制和粘贴,不用一个一个修改替换组件的属性了,复制粘贴即可,非常方便.

UnityEditorInternal.ComponentUtility.CopyComponent(logic)
UnityEditorInternal.ComponentUtility.PasteComponentAsNew(spShape);

功能如图:

代码和原理都非常简单,却能数倍提高效率,以后要多多研究编辑器扩展方面了?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值