Unity Addressable 实战四(基于资源打包策略的编辑器实现)

873 阅读8分钟

        通过上一篇文章《Unity Addressable 实战三(游戏资源打包策略) - 掘金 (juejin.cn)》,我们了解了游戏资源的基本打包策略。俗话说,工欲善其事必先利其器,这一篇文章我们就一起来探讨一下基于策略的编辑器工具实现。在探讨之前建议大家先前往阅读《Unity 编辑器的高效拓展设计方案 - 掘金 (juejin.cn)》,有助于对本篇文章的理解。

        首先我们来预览一下编辑器的效果,如下:

2024-01-03_162255.png

2024-01-03_173847.png

        如上图,我们的规则编辑器有菜单栏,有规则名称列表,有规则列表内容,右下角还有添加规则的功能按钮。我们一共定义了三条规则:

1. 单个目录打包(顾名思义,就是一个目录一个资源包(也称为组))
2. 单个文件打包(一个文件一资源包,当我们热更需要颗粒更细化时,这个规则非常有用)
3. 命名寻址(上面两个规则都是按文件名寻址的,这个规则提供自定义名称寻址。这个规则在某些时候非常有用,例如获取一个技能特效列表,可以命名寻址为skiilfx1,skiilfx2,skiilfx3等,项目后期无论资源如何变化或项目迁移对项目这一块的改动的代价都会比较小)

        这三条规则可以适应大部分游戏资源打包场景了,下面我们继续实现这个规则编辑器。

先定义一个规则类,AssetRuleConfig.cs:

using System;
using System.Collections.Generic;
using System.Data;
using UnityEditor;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using System.IO;

namespace EditorAsset
{
    /// <summary>
    /// 规则类型
    /// </summary>
    public enum AssetRuleType
    {
        /// <summary>
        /// 单个目录打包规则
        /// </summary>
        RuleDirectory = 0,
        /// <summary>
        /// 目录内每个文件单独为一组规则
        /// </summary>
        RuleFile = 1,
        /// <summary>
        /// 指定寻址名称,指向到某个资源规则
        /// </summary>
        RuleAddressRename = 2
    }

    /// <summary>
    /// 规则对象
    /// </summary>
    public class AssetRuleItem
    {
        public string Name;        
        public string Desc;
        public int Level;
        public AssetRuleType RuleType = AssetRuleType.RuleDirectory;       

        public List<string> Rules;

        public AssetRuleItem(int level)
        {
            Level = level;
            Rules = new List<string>();
        }

        /// <summary>
        /// 获取规则格式
        /// </summary>
        /// <returns></returns>
        public string GetRuleFormat()
        {
            if (RuleType == AssetRuleType.RuleDirectory)
                return AssetRuleDirectory.RuleFormat;
            else if (RuleType == AssetRuleType.RuleFile)
                return AssetRuleFile.RuleFormat;
            else if (RuleType == AssetRuleType.RuleAddressRename)
                return AssetRuleRename.RuleFormat;
            return "";
        }

        /// <summary>
        /// 获取规则格式说明
        /// </summary>
        /// <returns></returns>
        public string GetRuleFormatDesc()
        {
            if (RuleType == AssetRuleType.RuleDirectory)
                return AssetRuleDirectory.RuleFormatDesc;
            else if (RuleType == AssetRuleType.RuleFile)
                return AssetRuleFile.RuleFormatDesc;
            else if (RuleType == AssetRuleType.RuleAddressRename)
                return AssetRuleRename.RuleFormatDesc;
            return "";
        }
    }

    /// <summary>
    /// 规则序列化类
    /// </summary>
    public class AssetRuleConfig
    {
        public List<AssetRuleItem> Rules = new List<AssetRuleItem>();

        public void Init()
        {
            Rules.Clear();
            var arlRuleDirectory = new AssetRuleItem(0);
            arlRuleDirectory.Name = "单个目录打包";
            arlRuleDirectory.Desc = "单个目录(包括子文件夹)独立为一组,同组名的目录打包到同一组";
            arlRuleDirectory.RuleType = AssetRuleType.RuleDirectory;            
            Rules.Add(arlRuleDirectory);

            var arlRuleFile = new AssetRuleItem(1);
            arlRuleFile.Name = "单个文件打包";
            arlRuleFile.Desc = "目录内每个文件独立为一组";
            arlRuleFile.RuleType = AssetRuleType.RuleFile;            
            Rules.Add(arlRuleFile);

            var arlRuleRename = new AssetRuleItem(2);
            arlRuleRename.Name = "命名寻址";
            arlRuleRename.Desc = "指定寻址名称,指向到某个资源";
            arlRuleRename.RuleType = AssetRuleType.RuleAddressRename;            
            Rules.Add(arlRuleRename);
        }

        public AssetRuleItem GetRuleItem(AssetRuleType rt)
        {
            foreach(var r in Rules)
            {
                if (r.RuleType == rt)
                    return r;
            }
            return null;
        }
    }
}

定义了规则对象,还们还得设计一个规则对象的管理器,定义一个AssetRuleMgr.cs:

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using UnityEditor.AddressableAssets;
using System.Data;
using Newtonsoft.Json;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
using System.Linq;
using SimpleGUI;
namespace EditorAsset
{
    /// <summary>
    /// 规则管理器
    /// </summary>
    public class AssetRuleMgr
    {                      
        public static void Init()
        {
            //创建编辑配置文件
            if (!File.Exists(AssetDefine.AssetRuleConfigPath))
                File.CreateText(AssetDefine.AssetRuleConfigPath);
        }                              
        /// <summary>
        /// 获取规则序列化文件对象
        /// </summary>
        /// <returns></returns>
        public static AssetRuleConfig GetRuleConfig()
        {
            AssetRuleConfig config = null;
            if (File.Exists(AssetDefine.AssetRuleConfigPath))
            {
                string content = File.ReadAllText(AssetDefine.AssetRuleConfigPath);
                config = JsonConvert.DeserializeObject<AssetRuleConfig>(content);                
            }
            if (config == null)
            {
                config = new AssetRuleConfig();
                config.Init();
            }           
            return config;
        }

        /// <summary>
        /// 保存编辑规则
        /// </summary>
        public static void SaveRuleConfig(AssetRuleConfig config)
        {          
            string content = JsonConvert.SerializeObject(config, Formatting.Indented);
            File.WriteAllText(AssetDefine.AssetRuleConfigPath, content);
            GUITools.ShowDialog("保存", "保存成功", "ok");           
        }       

        private static AssetRuleGroup GetRuleGroup(List<AssetRuleGroup> gs, string name)
        {
            foreach(var g in gs)
            {
                if (g.GroupName == name)
                    return g;
            }
            return null;
        }

        private static bool HasAsset(List<AssetRuleGroup> gs, string aguid)
        {
            foreach(var g in gs)
            {
                foreach (var a in g.Assets)
                {
                    if (a.Guid == aguid)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// 获取所有的规则组
        /// </summary>
        /// <param name="simplifyAddress">是否简化addressable地址</param>
        /// <returns></returns>
        public static List<AssetRuleGroup> GetAllRuleGroups(bool simplifyAddress)
        {            
            var config = GetRuleConfig();
            var args = new List<AssetRuleGroup>();
            var notFiles = new List<string>();
            //按优先级排序,优先级高的在前面,低的在后面,创建的时候已经创建好的分组和资源就不会再变动
            var rules = config.Rules.OrderByDescending(a => a.Level).ToList();
            foreach (var rule in rules)
            {
                if(rule.RuleType == AssetRuleType.RuleDirectory)
                {
                    foreach(var r in rule.Rules)
                    {
                        var rd = new AssetRuleDirectory(r);                       
                        var fls = rd.GetAssets();                     
                        if (fls != null)
                        {
                            if (fls.Length == 0)
                                continue;
                            var gname = rd.GroupName.ToLower();                           
                            var arg = GetRuleGroup(args, gname);
                            if (arg == null)
                            {
                                arg = new AssetRuleGroup(gname);                                
                                args.Add(arg);
                            }
                            arg.RuleText = r;

                            foreach (var fl in fls)
                            {
                                if (!File.Exists(fl))
                                {
                                    notFiles.Add(rd.GroupName + "  " + fl);
                                    continue;
                                }
                                string guid = AssetDatabase.AssetPathToGUID(fl);
                                if (HasAsset(args, guid))
                                    continue;

                                var sourceAddress = simplifyAddress ? AssetDir.GetAssetNameNoExt(fl) : fl;
                                AssetRuleGroupItem ard = new AssetRuleGroupItem();
                                ard.Guid = AssetDatabase.AssetPathToGUID(fl);
                                ard.AddressName = rd.AssetPrefix.Trim() + sourceAddress + rd.AssetSuffix.Trim();
                                ard.AssetPath = fl;
                                arg.Assets.Add(ard);
                            }
                        }   
                        else
                        {
                            notFiles.Add(rd.GroupName + "  " + rd.Dir);
                        }
                    }
                    
                }
                else if(rule.RuleType == AssetRuleType.RuleFile)
                {
                    foreach (var r in rule.Rules)
                    {
                        var rf = new AssetRuleFile(r);
                        var fls = rf.GetAssets();                 
                        if (fls != null)
                        {
                            foreach (var fl in fls)
                            {
                                var gname = AssetDir.CombineFilename(rf.GroupNamePrefix, fl).ToLower();                              
                                if (!File.Exists(fl))
                                {
                                    notFiles.Add(gname + "  " + fl);
                                    continue;
                                }
                                string guid = AssetDatabase.AssetPathToGUID(fl);
                                if (HasAsset(args, guid))
                                    continue;

                                var sourceAddress = simplifyAddress ? AssetDir.GetAssetNameNoExt(fl) : fl;
                                var arg = GetRuleGroup(args, gname);
                                if (arg == null)
                                {
                                    arg = new AssetRuleGroup(gname);
                                    arg.RuleText = r;
                                    args.Add(arg);
                                }
                                AssetRuleGroupItem ard = new AssetRuleGroupItem();
                                ard.Guid = guid;
                                ard.AddressName = rf.AssetPrefix.Trim() + sourceAddress + rf.AssetSuffix.Trim();
                                ard.AssetPath = fl;
                                arg.Assets.Add(ard);
                            }
                        }
                        else
                        {
                            notFiles.Add(rf.GroupNamePrefix + "  " + rf.Dir);
                        }
                    }                        
                }
                else if( rule.RuleType == AssetRuleType.RuleAddressRename)
                {
                    foreach (var r in rule.Rules)
                    {                        
                        var rr = new AssetRuleRename(r);
                        if (!File.Exists(rr.AssetPath))
                        {
                            notFiles.Add(rr.GroupName + "  " +  rr.AssetPath);
                            continue;
                        }
                        var gname = rr.GroupName.ToLower();                     
                        var guid = AssetDatabase.AssetPathToGUID(rr.AssetPath);
                        if (HasAsset(args, guid))
                            continue;

                        var arg = GetRuleGroup(args, gname);
                        if (arg == null)
                        {
                            arg = new AssetRuleGroup(gname);
                            //arg.RuleText = r; //命名寻址规则不用设置组规则
                            args.Add(arg);
                        }
                        AssetRuleGroupItem ard = new AssetRuleGroupItem();
                        ard.Guid = guid;
                        ard.AddressName = rr.AddressName;
                        ard.AssetPath = rr.AssetPath;
                        arg.Assets.Add(ard);                        
                    }
                }                              
            }
            //组内资源排序,最大限度保持一至性
            foreach(var item in args)
            {
                item.Orderby();
            }
            //组排序,让addressable生成持久化组文件数据时保持数据稳定性
            var rargs = args.OrderBy(a => a.GroupName).ToList();
            if(notFiles.Count > 0)
            {
                foreach(var nf in notFiles)
                {
                    Debug.LogWarning("规则指定的文件或文件夹不存在, " + nf);
                }
                //GUITools.ShowDialog("警告", "规则指定的文件不存在,请查看控制台输出窗口。", "ok");
            }
            return rargs;
        }
        /// <summary>
        /// 是否是不可操作的组
        /// </summary>
        /// <param name="aag"></param>
        /// <returns></returns>
        private static bool IsReadOnly(AddressableAssetGroup aag)
        {
            if (aag.IsDefaultGroup() || aag.ReadOnly)
                return true;
            return false;
        }

        /// <summary>
        /// 刷新组
        /// </summary>
        public static void RefrashAllGroups()
        {
            var gs = GetAllRuleGroups(true);
            List<AddressableAssetGroup> aags = AssetDefine.Settings.groups;
            int count = aags.Count;
            //先清空各类不合法的组
            for (int n = count - 1; n >= 0; n--)
            {
                GUITools.ShowProgressBar("正在清理无效组", "请稍候...", n, count);
                AddressableAssetGroup aag = aags[n];
                if (aag == null)
                {
                    aags.RemoveAt(n);
                    continue;
                }

                if (IsReadOnly(aag))
                    continue;
                if (AssetFileMgr.IsCommonGroup(aag))
                    continue;

                var finded = false;
                foreach(var g in gs)
                {
                    if(aag.Name == g.GroupName)
                    {
                        finded = true;
                        break;
                    }
                }
                if(!finded)
                    AssetDefine.Settings.RemoveGroup(aag);
            }
            GUITools.HideProgressBar();


            //再生成组
            count = gs.Count;
            for(int n = 0; n < count; n++)
            {
                var g = gs[n];
                GUITools.ShowProgressBar("正在创建组", "请稍候...", n, count);
                g.CreateGroup();
                g.CreateAsset(true);
            }
            GUITools.HideProgressBar();

            //移除空组,因为有可能多条规则按充生成后,前面生成的组的资源有可能被后面的规则生成移走了
            RemoveNotAssetGroup();      
            //固定排序,保持序列化数据稳定
            AssetDefine.Settings.groups.Sort((a, b) => { return a.Name.CompareTo(b.Name); });           
            AssetDatabase.Refresh();
            AssetDatabase.SaveAssets();            
            GUITools.ShowDialog("刷新组", "刷新成功", "ok");
        }

        /// <summary>
        /// 重置组
        /// </summary>
        public static void ResetAllGroups()
        {
            List<AddressableAssetGroup> aags = AssetDefine.Settings.groups;
            int count = aags.Count;
            for (int n = count - 1; n >= 0; n--)
            {
                GUITools.ShowProgressBar("正在重置组", "请稍候...", n, count);
                AddressableAssetGroup aag = aags[n];
                if (IsReadOnly(aag))
                    continue;

                AssetDefine.Settings.RemoveGroup(aag);
            }
            RefrashAllGroups();
            GUITools.HideProgressBar();
        }

        /// <summary>
        /// 移除没有资源的组
        /// </summary>
        public static void RemoveNotAssetGroup()
        {
            List<AddressableAssetGroup> aags = AssetDefine.Settings.groups;
            for (int i = aags.Count - 1; i >= 0; i--)
            {
                AddressableAssetGroup aag = aags[i];
                if (aag == null)
                {
                    aags.RemoveAt(i);
                    continue;
                }
                if (IsReadOnly(aag))
                    continue;

                if (aag.entries.Count == 0)
                {
                    AssetDefine.Settings.RemoveGroup(aag);
                }
            }
        }        
    }
}

然后我们把规则做成列表显示在编辑器左边栏上,定义一个UIAssetRuleConfig.cs:

using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using EditorTreeView;
using UnityEditor.IMGUI.Controls;
using SimpleGUI;

namespace EditorAsset
{

    internal class AssetRuleConfigTreeNode : TreeNode
    {     
        public AssetRuleItem RuleItem;
        public AssetRuleConfigTreeNode() { }
        public AssetRuleConfigTreeNode(int id, string name, int depth) : base(id, name, depth)
        {

        }
    }

    internal class AssetRuleConfigTreeView : TreeView<AssetRuleConfigTreeNode>
    {
        public UIAssetRuleConfig Window;
        public AssetRuleConfigTreeView(TreeViewState state, MultiColumnHeader multiColumnHeader, string rootName) : base(state, multiColumnHeader, rootName)
        {

        }

        protected override void RowGUI(RowGUIArgs args)
        {
            var item = (TreeViewItem<AssetRuleConfigTreeNode>)args.item;           
            var cols = args.GetNumVisibleColumns();
            for (int i = 0; i < cols; i++)
            {
                var cellr = args.GetCellRect(i);                
                if (i == 0)
                {
                    var r = GetCenterRect(cellr, 5, 0, cellr.width - 5, cellr.height);
                    EditorGUI.LabelField(r, item.Data.RuleItem.Level.ToString());
                }
                else if (i == 1)
                {
                    var r = GetRect(cellr, 5, 0, cellr.width - 5, cellr.height);
                    EditorGUI.LabelField(r, item.Data.RuleItem.Name);                   
                }
            }
        }
        protected override void SingleClickedItem(int id)
        {
            base.SingleClickedItem(id);
            var item = Model.GetNode(id);          
            Window.SelectedRule = item.RuleItem;
        }

        protected override bool CanRename(TreeViewItem item)
        {
            return false;
        }

    }

    /// <summary>
    /// 规则
    /// </summary>
    public class UIAssetRuleConfig : UICell
    {       
        private TreeViewState _viewState;
        private AssetRuleConfigTreeView _tv;   
        public Action<AssetRuleItem, AssetRuleItem> OnSelected;
        public AssetRuleConfig Config { private set; get; }
        private AssetRuleItem _selectedRule;
        public AssetRuleItem SelectedRule
        {
            set
            { 
                if(_selectedRule != value)
                {
                    OnSelected?.Invoke(_selectedRule, value);
                    _selectedRule = value;
                }
            }
            get { return _selectedRule; } 
        }
        
        public UIAssetRuleConfig() { }
        public UIAssetRuleConfig(Rect pos) : base(pos)
        {
                   
        }

        private void InitTreeViewHeader()
        {
            if (_viewState == null)
                _viewState = new TreeViewState();
            var col1 = TreeViewColumn.CreateColumn("优先级", 60, 60, 60, TextAlignment.Center, true);
            var width = Area.width - 60;
            var col2 = TreeViewColumn.CreateColumn("名称", width, width, width, TextAlignment.Center, true);
            var headerState = TreeViewColumn.CreateMultiColumnHeaderState(new MultiColumnHeaderState.Column[] { col1, col2 });
            var header = new TreeViewColumn(headerState);
            _tv = new AssetRuleConfigTreeView(_viewState, header, "root");
            _tv.SetShowBorder(true);
            _tv.SetShowAlternatingRowBg(true);
            _tv.SetRowHeight(20);

            _tv.Window = this;
        }

        private void InitData()
        {
            InitTreeViewHeader();
            var model = _tv.Model;

            _selectedRule = null;
            Config = AssetRuleMgr.GetRuleConfig();
            if (SelectedRule == null && Config.Rules.Count > 0)
                SelectedRule = Config.Rules[0];

            if (_selectedRule != null)
            {
                model.Root.Childrens.Clear();
                foreach (var r in Config.Rules)
                {                 
                    var node = new AssetRuleConfigTreeNode(model.GetUniqueId(), r.Name, 0);                  
                    node.RuleItem = r;               
                    model.Root.AddChildren(node);
                }
            }
            _tv.Reload();
        }

        public override void OnAreaChanged(Rect old, Rect last)
        {
            base.OnAreaChanged(old, last);
            InitData();
        }

        public override void DrawContent()
        {
            if(_tv == null) return;
            float leftSpace = 0;
            float rightSpace = 0;
            float bottomSpace = 10;
            float topSpace = 5;
            float height = topSpace;
                         
            var r = new Rect(leftSpace, height, Area.width - rightSpace, Area.height - height - bottomSpace);
            _tv.OnGUI(r);
      
        }      
    }
}

有了规则,我们还需要定义和处理规则内的细则,所以我们还要把规则的内容显示在编辑器的右边栏,定义一个UIAssetRuleItem.cs:

using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using System.IO;
using Newtonsoft.Json;
using System.Collections.Generic;
using System;
using EditorTreeView;
using UnityEditor.IMGUI.Controls;
using System.Linq;
using SimpleGUI;

namespace EditorAsset
{

    internal class AssetRuleItemTreeNode : TreeNode
    {
        public int ItemDataIndex;
        public AssetRuleItem RuleItem;
        public AssetRuleItemTreeNode() { }
        public AssetRuleItemTreeNode(int id, string name, int depth) : base(id, name, depth)
        {

        }
    }

    internal class AssetRuleItemTreeView : TreeView<AssetRuleItemTreeNode>
    {
        public UIAssetRuleItem Window;
        public AssetRuleItemTreeView(TreeViewState state, MultiColumnHeader multiColumnHeader, string rootName) : base(state, multiColumnHeader, rootName)
        {

        }

        protected override void RowGUI(RowGUIArgs args)
        {
            var item = (TreeViewItem<AssetRuleItemTreeNode>)args.item;
            var text = item.Data.RuleItem.Rules[item.Data.ItemDataIndex];
            var cols = args.GetNumVisibleColumns();
            for (int i = 0; i < cols; i++)
            {
                var cellr = args.GetCellRect(i);
                if (i == 0)
                {
                    var r = GetRect(cellr, 10, 0, cellr.width - 10, cellr.height);
                    EditorGUI.LabelField(r, text);
                }    
                else if(i == 1)
                {
                    var r = GetRect(cellr, 5, 0, 30, cellr.height);
                    if(GUI.Button(r,"-"))
                    {
                        if (Window != null)
                        {
                            Window.DelRule(text);
                            Reload();
                        }
                    }
                }
            }
        }

        protected override List<TreeViewItem> Sort(int sortedColumnIndex, bool ascending, IEnumerable<TreeViewItem<AssetRuleItemTreeNode>> items)
        {
            IOrderedEnumerable<TreeViewItem<AssetRuleItemTreeNode>> aa = null;           
            if (sortedColumnIndex == 0)
            {
                if (ascending)
                    aa = items.OrderBy(a => a.Data.RuleItem.Rules[a.Data.ItemDataIndex]);
                else
                    aa = items.OrderByDescending(a => a.Data.RuleItem.Rules[a.Data.ItemDataIndex]);
            }          
            if(aa != null)
                return aa.Cast<TreeViewItem>().ToList();
            return items.Cast<TreeViewItem>().ToList();
        }
      
        protected override Rect GetRenameRect(Rect rowRect, int row, TreeViewItem item)
        {
            var r = GetCellRectForTreeFoldouts(rowRect);           
            return base.GetRenameRect(r, row, item);        
        }

        protected override void RenameEnded(RenameEndedArgs args)
        {
            if (args.acceptedRename)
            {
                var tn = Model.ForeachDepth(Model.Root, (a) => { return args.itemID == a.Id; });
                if (tn != null)
                {
                    tn.RuleItem.Rules[tn.ItemDataIndex] = args.newName;                    
                }
                Reload();
            }
        }
    }
        /// <summary>
        /// 规则项
        /// </summary>
    public class UIAssetRuleItem : UICell
    {       
        private TreeViewState _viewState;
        private AssetRuleItemTreeView _tv;
        private SearchField _searchField;
        private AssetRuleItem _currentItem;
        public UIAssetRuleItem() { }
        public UIAssetRuleItem(Rect pos) : base(pos)
        {

        }

        public override void Init(Rect pos)
        {
            base.Init(pos);
            InitTreeViewHeader();
        }

        private void InitTreeViewHeader()
        {
            if (_viewState == null)
                _viewState = new TreeViewState();
            if (_tv == null)
            {
                var col1 = TreeViewColumn.CreateColumn("AssetPath", 800, 800, 1000, TextAlignment.Center, true);
                var col2 = TreeViewColumn.CreateColumn("Delete", 50, 50, 50, TextAlignment.Center, false);
                var headerState = TreeViewColumn.CreateMultiColumnHeaderState(new MultiColumnHeaderState.Column[] { col1, col2 });
                var header = new TreeViewColumn(headerState);
                _tv = new AssetRuleItemTreeView(_viewState, header, "root");
                _tv.SetShowBorder(true);
                _tv.SetShowAlternatingRowBg(true);
                _tv.SetRowHeight(20);

                _searchField = new SearchField();
                _searchField.downOrUpArrowKeyPressed += _tv.SetFocusAndEnsureSelectedItem;
                _tv.Window = this;
            }
        }

        private void InitData()
        {
            if (_tv == null)
            {
                InitTreeViewHeader();
            }
            var model = _tv.Model;
            if (_currentItem != null)
            {
                model.Root.Childrens.Clear();
                for(int i = 0; i < _currentItem.Rules.Count;i++)
                {
                    var m = _currentItem.Rules[i];
                    var node = new AssetRuleItemTreeNode(model.GetUniqueId(), m, 0);
                    node.ItemDataIndex = i;
                    node.RuleItem = _currentItem;
                    model.Root.AddChildren(node);
                }
            }
            _tv.Reload();
        }
        public override void DrawContent()
        {
            float leftSpace = 0;
            float rightSpace = 10;
            float bottomSpace = 10;
            float topSpace = 10;
            float height = topSpace;
                   
            GUILayout.BeginVertical(GUILayout.Height(90));
            GUILayout.Space(topSpace);
            GUILayout.BeginHorizontal();
            GUILayout.Label("规则:" + _currentItem.Name, GUILayout.Width(200));
            GUILayout.Label("优先级:" + _currentItem.Level, GUILayout.Width(80));
            GUILayout.EndHorizontal();

            GUILayout.Label("说明:" + _currentItem.Desc);
            GUILayout.Label("格式:" + _currentItem.GetRuleFormat());
            GUILayout.Label("格式说明:" + _currentItem.GetRuleFormatDesc());

            GUILayout.EndVertical();
            height += 90;
            
            GUILayout.BeginVertical(GUILayout.Height(Area.height - height));
            var r = new Rect(leftSpace, height, Area.width - rightSpace, 20f);
            _tv.searchString = _searchField.OnToolbarGUI(r, _tv.searchString);
            height += 20;
            
            r = new Rect(leftSpace, height, Area.width - rightSpace, Area.height - height-30);
            _tv.OnGUI(r);
            GUILayout.EndVertical();
           
            GUILayout.Space(r.height + 30);
            GUILayout.BeginVertical();            
            GUILayout.BeginHorizontal();
            GUILayout.FlexibleSpace();
            if (GUILayout.Button("+", GUILayout.Width(40)))
            {
                AddRule();
            }
            GUILayout.Space(30);
            GUILayout.EndHorizontal();
            GUILayout.Space(bottomSpace);

            GUILayout.EndVertical();

        }

        public void OnCurrentRuleChanged(AssetRuleItem oldRule, AssetRuleItem newRule)
        {
            _currentItem = newRule;
            InitData();
        }        
       
        public void DelRule(string r)
        {
            _currentItem.Rules.Remove(r);
            InitData();
        }
        private void AddRule()
        {
            _currentItem.Rules.Add(_currentItem.GetRuleFormat());
            InitData();
            var items = _tv.GetRows();
            var index = items.Count;
            var item = items[index - 1];
            _tv.BeginRename(item);          
            _tv.state.scrollPos = new Vector2(0, Area.height);
        }
    }
}

我们对规则进行编辑后,要进行保存等其它操作,所以还得定义一个工具栏,UIAssetRuleItemToolbar.cs:

using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using SimpleGUI;

namespace EditorAsset
{
    /// <summary>
    /// 规则项工具栏
    /// </summary>
    public class UIAssetRuleItemToolbar : UICell
    {
        public AssetRuleItem SelectedRule { private set; get; }
        public Action<AssetRuleItem, AssetRuleItem> OnSelected;
        
        public UIAssetRuleItemToolbar() { }
        public UIAssetRuleItemToolbar( Rect pos) : base(pos)
        {
                   
        }
                     
        public override void DrawContent()
        {
            GUIStyleDefine.DrawAreaBg(new Rect(0, 0, Area.width, Area.height), Color.black);
            GUILayout.BeginHorizontal(GUIStyleDefine.NewToolbarContainerStyle);
            GUILayout.FlexibleSpace();
            if (GUILayout.Button("  转到组视图  ", EditorStyles.toolbarButton))
            {
                WinAsset.MainUI.Menu.Tab.Click(0);
            }
            if (GUILayout.Button("  保存  ", EditorStyles.toolbarButton))
            {
                AssetRuleMgr.SaveRuleConfig(WinAsset.MainUI.RuleConfig.Config);
            }                              
            GUILayout.EndHorizontal();
        }     
    }
}

        至此,我们的基于策略的规则编辑器基本已经告一段落,往后只要有新的资源要打包、分配到组、改变打包规则等,只要修改相应的规则内容,然后点"刷新组"就可以全部重新刷新资源包了。

        由于本系列文章重点不在介绍编辑器实现上,所以会有不展示或漏缺的代码,稍后笔者会把整个addressable系列的代码上传到个人空间上供大家下载。