Unity UGUI实现图文混排组件(改)

本文章所包含代码源自https://blog.youkuaiyun.com/lahmiley/article/details/83759753, 感谢@lahmiley提供解决方案

由本人整理修改后, 在本博客发布丶记录:

如有侵权, 请联系我删除

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine.Events;
using Object = UnityEngine.Object;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace UnityEngine.UI
{

    /// <summary>
    /// Emoji Text组件
    /// 
    /// 用法用例:
    /// 图片        (NodeImage):        <tag=image, id=90001/>  
    /// 按钮        (NodeButton):       <tag=button, id=90001, callback=gameName&methodName&arg1&arg2&.../> 
    /// Emoji表情   (NodeEmoji):        <tag=emoji, id=90001/>  
    /// 超链接      (NodeHyperlink):    <tag=hyperlink, color=#ffoooo>超链接</tag>
    /// 说明: tag必须在<...>中排首位, id指资源ID, callback指触发事件后回调(对象名&方法名&参数1&参数2&...), color指超链接文本颜色
    /// 
    /// 资源加载:
    /// 图片丶按钮: 由AssetCacheMgr管理,传入id参数,返回Sprite纹理
    /// Emoji表情:  由AssetCacheMgr管理,传入id参数,返回GameObject预制体
    /// </summary>
    [AddComponentMenu("UI/Text - EmojiText")]
    public class EmojiText : Text
    {

#if UNITY_EDITOR
        [MenuItem("GameObject/UI/Text - EmojiText")]
        static void Create()
        {
            var select_obj = Selection.activeGameObject;
            //当前非资源文件
            var asset_path = AssetDatabase.GetAssetPath(select_obj);
            if (string.IsNullOrEmpty(asset_path))
            {
                //父节点
                Transform parent = null;
                //从当前节点向上遍历,寻找Canvas节点
                var select = select_obj?.transform;
                while (select != null)
                {
                    if (select.GetComponent<Canvas>() != null)
                    {
                        parent = select_obj.transform;
                        break;
                    }
                    select = select.parent;
                }
                //从全局Find一个Canvas节点
                parent = FindObjectOfType<Canvas>()?.transform;
                //创建一个新的Canvas节点
                if (parent == null)
                {
                    var canvas_obj = new GameObject("Canvas");
                    canvas_obj.transform.SetParent(null);
                    var canvas = canvas_obj.AddComponent<Canvas>();
                    canvas_obj.AddComponent<CanvasScaler>();
                    canvas_obj.AddComponent<GraphicRaycaster>();
                    canvas.worldCamera = Camera.main;
                    canvas.renderMode = RenderMode.ScreenSpaceCamera;
                    parent = canvas_obj.transform;
                    //EventSystem
                    if (FindObjectOfType<EventSystems.EventSystem>() == null)
                    {
                        var system_obj = new GameObject("EventSystem");
                        system_obj.transform.SetParent(null);
                        system_obj.AddComponent<EventSystems.EventSystem>();
                        system_obj.AddComponent<EventSystems.StandaloneInputModule>();
                    }
                }
                //创建节点
                var obj = new GameObject("EmojiText");
                obj.AddComponent<EmojiText>();
                obj.transform.SetParent(parent);
                obj.transform.localScale = Vector3.one;
            }
        }
#endif
        //匹配一个字符串
        //例:<tag=image,id=90001/>  <tag=button,id=90001/> <tag=emoji,id=90001/>  <tag=hyperlink,color=#ffoooo>超链接</tag>
        static readonly Regex regexTag = new Regex(@"<tag=(button|image|emoji|hyperlink){1}.+?(/>|</tag>){1}", RegexOptions.Singleline);

        //对象缓存池
        private List<NodeImage> imageCache = new List<NodeImage>();
        private List<NodeEmoji> emojiCache = new List<NodeEmoji>();
        private List<NodeButton> buttonCache = new List<NodeButton>();
        private List<NodeHyperlink> hyperlinkCache = new List<NodeHyperlink>();

        private Dictionary<int, TagData> tagDict;
        //重新创建tag内容
        private bool waitUpdateTag = false;
        //目标内容信息
        public string srcText { get; private set; }
        private int srcSize = -1;

        public override string text
        {
            set
            {
                srcText = value;
                srcSize = fontSize;
                waitUpdateTag = false;
                base.text = Parse(value);
            }
            get
            {
                return base.text;
            }
        }

        private void Update()
        {
#if UNITY_EDITOR
            if (!UnityEditor.EditorApplication.isPlaying)
            {
                return;
            }
#endif
            if (font == null)
                return;
            if (srcSize != -1 && srcSize != fontSize)
            {
                srcSize = fontSize;
                text = srcText;
                return;
            }
            if (waitUpdateTag)
            {
                waitUpdateTag = false;
                UpdateDatas();
            }
        }
        protected override void OnDestroy()
        {
#if UNITY_EDITOR
            if (!UnityEditor.EditorApplication.isPlaying)
            {
                return;
            }
#endif
            ClearCache();
        }
        protected override void OnPopulateMesh(VertexHelper helper)
        {
            if (font == null)
                return;

            if (tagDict != null)
            {
                foreach (TagData tagData in tagDict.Values)
                {
                    tagData.SetValid(false);
                }
            }
            //禁用纹理重建回调
            m_DisableFontTextureRebuiltCallback = true;

            var settings = GetGenerationSettings(rectTransform.rect.size);
            cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);

            //应用顶点偏移
            IList<UIVertex> verts = cachedTextGenerator.verts;
            float units_per_pixel = 1 / pixelsPerUnit;
            //最后4个顶点独立为一行
            int vert_count = verts.Count - 4;
            if (vert_count > 0)
            {
                Vector2 rounding_offset = new Vector2(verts[0].position.x, verts[0].position.y) * units_per_pixel;
                rounding_offset = PixelAdjustPoint(rounding_offset) - rounding_offset;
                helper.Clear();


                TagData hyperlink_tag = null;
                var x_offset = rectTransform.sizeDelta.x * rectTransform.pivot.x;
                var y_offset = rectTransform.sizeDelta.y * rectTransform.pivot.y;

                Vector4 button_bound = new Vector4(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue);
                var button_bound_init = false;
                var have_rounding_offset = rounding_offset != Vector2.zero;
                var last_char_min_y = float.MaxValue;

                for (int i = 0; i < vert_count; ++i)
                {
                    int temp_verts_index = i & 3;
                    m_TempVerts[temp_verts_index] = verts[i];
                    m_TempVerts[temp_verts_index].position *= units_per_pixel;
                    if (have_rounding_offset)
                    {
                        m_TempVerts[temp_verts_index].position.x += rounding_offset.x;
                        m_TempVerts[temp_verts_index].position.y += rounding_offset.y;
                    }
                    if (temp_verts_index == 3)
                    {
                        int index = Mathf.FloorToInt(i / 4);
                        if (hyperlink_tag != null)
                        {
                            CalculateHyperlinkBound(hyperlink_tag, ref button_bound, ref button_bound_init, ref last_char_min_y, x_offset, y_offset, index);
                        }
                        else if (tagDict != null && tagDict.ContainsKey(index))
                        {
                            TagData tag_data = tagDict[index];
                            tag_data.SetValid(true);
                            if (tag_data.type != TagType.Hyperlink)
                            {
                                float minX = float.MaxValue;
                                float minY = float.MaxValue;
                                for (int j = 0; j < 4; j++)
                                {
                                    //清除占位符显示 也可以用<color=#00000000><quad></color>来隐藏
                                    m_TempVerts[j].uv0 = Vector2.zero;
                                    //获取占位符左下角坐标
                                    if (m_TempVerts[j].position.x < minX) minX = m_TempVerts[j].position.x;
                                    if (m_TempVerts[j].position.y < minY) minY = m_TempVerts[j].position.y;

                                }
                                tag_data.SetStartPosition(new Vector3(minX + x_offset, minY + y_offset, 0));
                            }
                            else if (tag_data.type == TagType.Hyperlink)
                            {
                                tag_data.ClearBound();
                                hyperlink_tag = tag_data;
                                if (IsTempVertexValid())
                                {
                                    Vector4 charBound = GetCharBound();
                                    button_bound = charBound;
                                    last_char_min_y = charBound.y;
                                    button_bound_init = true;
                                }
                            }
                        }
                        helper.AddUIVertexQuad(m_TempVerts);
                    }
                }
            }

            //恢复回调
            m_DisableFontTextureRebuiltCallback = false;
            waitUpdateTag = true;
        }

        string Parse(string source_str)
        {
            //Clear Tag Data
            tagDict?.Clear();
            //Match
            MatchCollection matchs = regexTag.Matches(source_str);
            int count = matchs.Count;
            if (count > 0)
            {
                tagDict = tagDict ?? new Dictionary<int, TagData>();
                StringBuilder builder = new StringBuilder();
                //解析字符串后的tag的首个匹配下标
                int start_index = 0;
                //sourceString上次匹配成功的结束下标
                int last_match_end_index = 0;
                for (int i = 0; i < count; i++)
                {
                    Match match = matchs[i];
                    //Tag Before String / Tag标识之前的字符串
                    string tag_before = source_str.Substring(last_match_end_index, match.Index - last_match_end_index);
                    builder.Append(tag_before);
                    start_index += tag_before.Length;
                    //Tag Data
                    TagData tag_data = new TagData(match.Groups[0].Value, fontSize);
                    tag_data.startIndex = start_index;
                    tagDict.Add(start_index, tag_data);
                    builder.Append(tag_data.populateText);
                    start_index += tag_data.populateLength;

                    last_match_end_index = match.Index + match.Length;
                    //Last Tag/ 处理后面的文本
                    if (i == count - 1 && last_match_end_index < source_str.Length)
                    {
                        builder.Append(source_str.Substring(last_match_end_index, source_str.Length - last_match_end_index));
                    }
                }
                return builder.ToString();
            }
            return source_str;
        }

        void UpdateDatas()
        {
            ClearCache();
            if (tagDict != null)
            {
                foreach (TagData tag_data in tagDict.Values)
                {
                    if (!tag_data.valid)
                    {
                        continue;
                    }
                    switch (tag_data.type)
                    {
                        case TagType.Image:
                            {
                                NodeImage item = ObjectPoolMgr.GetInstance().ImagePool.Get(tag_data);
                                item.SetParent(transform);
                                item.SetData(tag_data);

                                imageCache.Add(item);
                            }
                            break;
                        case TagType.Emoji:
                            {
                                NodeEmoji item = ObjectPoolMgr.GetInstance().EmojiPool.Get(tag_data);
                                item.SetParent(transform);
                                item.SetData(tag_data);

                                emojiCache.Add(item);
                            }
                            break;
                        case TagType.Button:
                            {
                                NodeButton item = ObjectPoolMgr.GetInstance().ButtonPool.Get(tag_data);
                                item.SetParent(transform);
                                item.SetData(tag_data);

                                buttonCache.Add(item);
                            }
                            break;
                        case TagType.Hyperlink:
                            {
                                List<Vector4> boundList = tag_data.boundList;
                                if (boundList != null && boundList.Count > 0)
                                {
                                    for (int i = 0; i < boundList.Count; i++)
                                    {
                                        NodeHyperlink item = ObjectPoolMgr.GetInstance().HyperlinkPool.Get(tag_data);
                                        item.SetParent(transform);
                                        item.SetData(tag_data);
                                        item.SetBound(boundList[i]);

                                        hyperlinkCache.Add(item);
                                    }
                                }
                            }
                            break;
                    }
                }
            }
        }
        void ClearCache()
        {
            if (ObjectPoolMgr.Instance != null)
            {
                //Release All
                ObjectPoolMgr.GetInstance().ImagePool.Release(imageCache);
                ObjectPoolMgr.GetInstance().ButtonPool.Release(buttonCache);
                ObjectPoolMgr.GetInstance().HyperlinkPool.Release(hyperlinkCache);
                ObjectPoolMgr.GetInstance().EmojiPool.Release(emojiCache);
            }
            //Clear
            imageCache.Clear();
            buttonCache.Clear();
            hyperlinkCache.Clear();
            emojiCache.Clear();
        }

        readonly UIVertex[] m_TempVerts = new UIVertex[4];
        bool IsTempVertexValid()
        {
            Vector3 vector0 = m_TempVerts[0].position;
            Vector3 vector1 = m_TempVerts[1].position;
            if (vector0.x == vector1.x && vector0.y == vector1.y)
            {
                return false;
            }
            return true;
        }
        Vector4 GetCharBound()
        {
            Vector4 result = new Vector4(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue);
            for (int i = 0; i < 4; i++)
            {
                Vector3 position = m_TempVerts[i].position;
                if (position.x < result.x) result.x = position.x;
                if (position.y < result.y) result.y = position.y;
                if (position.x > result.z) result.z = position.x;
                if (position.y > result.w) result.w = position.y;
            }
            return result;
        }
        void CalculateHyperlinkBound(TagData hyperlink_tag, ref Vector4 button_bound, ref bool button_bound_init, ref float last_char_min_Y, float x_offset, float y_offset, int index)
        {
            if (IsTempVertexValid())
            {
                Vector4 charBound = GetCharBound();
                if (button_bound_init)
                {
                    //判断换行
                    if ((last_char_min_Y != float.MaxValue) && (last_char_min_Y > charBound.w))
                    {
                        button_bound.x += x_offset;
                        button_bound.y += y_offset;
                        button_bound.z += x_offset;
                        button_bound.w += y_offset;
                        hyperlink_tag.AddBound(button_bound);
                        last_char_min_Y = charBound.y;
                        button_bound = charBound;
                    }
                    else
                    {
                        last_char_min_Y = charBound.y;
                        if (charBound.x < button_bound.x) button_bound.x = charBound.x;
                        if (charBound.y < button_bound.y) button_bound.y = charBound.y;
                        if (charBound.z > button_bound.z) button_bound.z = charBound.z;
                        if (charBound.w > button_bound.w) button_bound.w = charBound.w;
                    }
                }
                else
                {
                    button_bound = charBound;
                    last_char_min_Y = charBound.y;
                    button_bound_init = true;
                }
            }

            if ((hyperlink_tag.endIndex - 1) == index)
            {
                if (button_bound_init)
                {
                    button_bound.x += x_offset;
                    button_bound.y += y_offset;
                    button_bound.z += x_offset;
                    button_bound.w += y_offset;
                    hyperlink_tag.AddBound(button_bound); ;
                }
                button_bound_init = false;
                hyperlink_tag = null;
            }
        }
    }

    #region 标签数据<对象>

    internal enum TagType
    {
        Image = 1,  //图标
        Emoji = 2,  //动态表情
        Button = 3, //按钮
        Hyperlink = 4,  //超链接
    }
    internal class TagData
    {
        const float IMAGE_SCALE = 1.5f;
        const float EMOJI_SCALE = 1.5f;
        const float BUTTON_SCALE = 2f;

        public TagType type { get; private set; }
        public string id { get; private set; }

        //填充文本
        public string populateText { get; private set; }
        //填充文本长度
        public int populateLength { get; private set; }
        //渲染的位置
        public Vector3 startPosition { get; private set; }
        public float width { get; private set; }
        public float height { get; private set; }
        public int fontSize { get; private set; }
        //在显示范围内的
        public bool valid { get; private set; } = false;
        //按钮回调方法
        public UnityAction callback { get; private set; } = null;
        //用于处理Hyperlink类型的节点(startIndex/endIndex/boundList)
        public int startIndex;
        public int endIndex
        {
            get
            {
                return startIndex + populateLength;
            }
        }
        public List<Vector4> boundList { get; private set; }

        public TagData(string params_str, int size)
        {
            var param = new TagParams(params_str);
            this.id = param.id;
            this.fontSize = size;
            this.type = param.type;
            switch (this.type)
            {
                case TagType.Image:
                    populateText = string.Format("<quad Size={0}, Width={1}>", size.ToString(), IMAGE_SCALE.ToString());
                    width = size * IMAGE_SCALE;
                    height = size * IMAGE_SCALE;
                    break;
                case TagType.Emoji:
                    populateText = string.Format("<quad Size={0}, Width={1}>", size.ToString(), EMOJI_SCALE.ToString());
                    width = size * EMOJI_SCALE;
                    height = size * EMOJI_SCALE;
                    break;
                case TagType.Button:
                    populateText = string.Format("<quad Size={0}, Width={1}>", size.ToString(), BUTTON_SCALE.ToString());
                    width = size * BUTTON_SCALE;
                    height = size;
                    break;
                case TagType.Hyperlink:
                    if (string.IsNullOrEmpty(param.contetnColor))
                        populateText = param.content;
                    else
                        populateText = string.Format("<color={1}>{0}</color>", param.content, param.contetnColor);
                    break;
            }
            this.populateLength = populateText?.Length ?? 0;
            //Callback
            if (!string.IsNullOrEmpty(param.gameName) && !string.IsNullOrEmpty(param.methodName))
            {
                string game_name = param.gameName, method_name = param.methodName;
                string[] args = param.methodsAgrs;
                if (args != null && args.Length > 0)
                    callback = () =>
                    {
                        var obj = GameObject.Find(game_name);
                        if (obj != null)
                            obj.SendMessage(method_name, args);
                        else
                            Debug.LogWarning(string.Format("Not Found GameObject:{0}\tId={1}", game_name, this.id));
                    };
                else
                    callback = () =>
                    {
                        var obj = GameObject.Find(game_name);
                        if (obj != null)
                            obj.SendMessage(method_name);
                        else
                            Debug.LogWarning(string.Format("Not Found GameObject:{0}\tId={1}", game_name, this.id));
                    };
            }
        }

        public void SetStartPosition(Vector3 position)
        {
            //偏移值 可以根据项目情况微调
            float offset_y = (this.height - this.fontSize) * 0.5f;
            position.Set(position.x, position.y - offset_y, position.z);
            startPosition = position;
        }

        public void SetValid(bool valid)
        {
            this.valid = valid;
        }

        public void AddBound(Vector4 bound)
        {
            if (boundList == null)
            {
                boundList = new List<Vector4>();
            }
            boundList.Add(bound);
        }

        public void ClearBound()
        {
            boundList?.Clear();
        }


        public override string ToString()
        {
            return string.Format("type={0},id={1},populateText={2}({3}),width={4},height={5},fontSize={6},valid={7}",
                type,
                id,
                populateText,
                populateLength,
                width,
                height,
                fontSize,
                valid);
        }
    }
    internal class TagParams
    {
        //基础属性
        public string id;
        public TagType type;
        //Callback
        public string gameName;         //接受回调的对象名称
        public string methodName;       //回调方法名
        public string[] methodsAgrs;    //方法参数
        //文本内容
        public string contetnColor;
        public string content;

        //匹配条件
        static Regex regexContent = new Regex(@">(.+?)</tag>", RegexOptions.Singleline);
        static Regex regexHeader = new Regex(@"^<(.+?)(/>|>)", RegexOptions.Singleline);

        public TagParams(string tag)
        {
            //Header
            MatchCollection matchs = regexHeader.Matches(tag);
            if (matchs.Count > 0)
            {
                var header = matchs[0].Groups[1].Value;
                foreach (var field in header.Replace(" ", "").Split(','))
                {
                    var arr = (field ?? "").Split('=');
                    if (arr != null && arr.Length == 2)
                    {
                        switch (arr[0]?.ToLower())
                        {
                            case "tag":
                                switch (arr[1]?.ToLower())
                                {
                                    case "image":
                                        this.type = TagType.Image;
                                        break;
                                    case "emoji":
                                        this.type = TagType.Emoji;
                                        break;
                                    case "button":
                                        this.type = TagType.Button;
                                        break;
                                    case "hyperlink":
                                        this.type = TagType.Hyperlink;
                                        break;
                                    default:
                                        Debug.LogWarning("未知识别类型");
                                        break;
                                }
                                break;
                            case "id":
                                id = arr[1];
                                break;
                            case "color":
                                if (arr[1] != null && arr[1].Length == 7 && arr[1].StartsWith("#"))
                                {
                                    contetnColor = arr[1];
                                }
                                break;
                            case "callback":
                                arr = (arr[1] ?? "").Split('&');
                                if (arr.Length >= 2)
                                {
                                    gameName = arr[0];
                                    methodName = arr[1];
                                    if (arr.Length > 2)
                                    {
                                        methodsAgrs = new string[arr.Length - 2];
                                        Array.Copy(arr, 2, methodsAgrs, 0, methodsAgrs.Length);
                                    }
                                }
                                break;
                        }
                    }
                }
            }
            //Content
            matchs = regexContent.Matches(tag);
            if (matchs.Count > 0)
            {
                content = matchs[0].Groups[1].Value;
            }
        }
    }

    #endregion

    #region 资源缓存管理器/节点池管理器/节点池/节点对象
    internal class AssetCacheMgr : MonoBehaviour
    {
        private static AssetCacheMgr instance;

        //缓存池
        private Dictionary<string, Sprite> dictImage = new Dictionary<string, Sprite>();
        private Dictionary<string, Sprite> dictButton = new Dictionary<string, Sprite>();
        private Dictionary<string, GameObject> dictEmoji = new Dictionary<string, GameObject>();

        public static AssetCacheMgr Instance { get { return instance; } }
        public static AssetCacheMgr GetInstance()
        {
            if (instance == null)
            {
                var obj = new GameObject("[CacheAssetMgr]");
                DontDestroyOnLoad(obj);
                instance = obj.AddComponent<AssetCacheMgr>();
            }
            return instance;
        }

        public Sprite LoadImage(string id)
        {
            id = id ?? string.Empty;
            if (dictImage.ContainsKey(id))
            {
                return dictImage[id];
            }
            var sprite = Resources.Load<Sprite>(string.Format("Textures/Icons/{0}", id));
            dictImage.Add(id, sprite);
            return sprite;
        }
        public Sprite LoadButton(string id)
        {
            id = id ?? string.Empty;
            if (dictButton.ContainsKey(id))
            {
                return dictButton[id];
            }
            var sprite = Resources.Load<Sprite>(string.Format("Emoji/Button/{0}", id));
            dictButton.Add(id, sprite);
            return sprite;
        }
        public GameObject LoadEmoji(string id)
        {
            id = id ?? string.Empty;
            if (dictEmoji.ContainsKey(id))
            {
                return dictEmoji[id];
            }
            var prefab = Resources.Load<GameObject>(string.Format("Emoji/Face/Face_{0}", id));
            dictEmoji.Add(id, prefab);
            return prefab;
        }
    }
    internal class ObjectPoolMgr : MonoBehaviour
    {
        private static ObjectPoolMgr instance;

        public ObjectPool<NodeImage> ImagePool { get; private set; }
        public ObjectPool<NodeButton> ButtonPool { get; private set; }
        public ObjectPool<NodeEmoji> EmojiPool { get; private set; }
        public ObjectPool<NodeHyperlink> HyperlinkPool { get; private set; }

        private float tick = 0f;

        public static ObjectPoolMgr Instance { get { return instance; } }
        public static ObjectPoolMgr GetInstance()
        {
            if (instance == null)
            {
                var obj = new GameObject("[ObjectPoolMgr]");
                DontDestroyOnLoad(obj);
                instance = obj.AddComponent<ObjectPoolMgr>();
            }
            return instance;
        }

        void Awake()
        {
            InitPools();
        }
        void FixedUpdate()
        {
            tick -= Time.fixedDeltaTime;
            if (tick < 0)
            {
                tick = 60f;
                ImagePool.Tick();
                ButtonPool.Tick();
                EmojiPool.Tick();
                HyperlinkPool.Tick();
            }
        }
        void InitPools()
        {
            //Image
            ImagePool = new ObjectPool<NodeImage>();
            ImagePool.CreateEvent += (tag_data) =>
            {
                //"Emoji/Prefab/Image";
                var obj = new GameObject("Image" + ImagePool.Count);
                obj.AddComponent<Image>();
                var node = new NodeImage(obj);
                node.SetParent(transform);
                return node;
            };
            ImagePool.ActiveEvent += item =>
            {
                item.Used = true;
                item.SetActive(true);
                item.SetParent(null);
            };
            ImagePool.ReleaseEvent += item =>
            {
                item.Used = false;
                item.Release();
                item.SetActive(false);
                item.SetParent(transform);
            };
            //Button
            ButtonPool = new ObjectPool<NodeButton>();
            ButtonPool.CreateEvent += (tag_data) =>
            {
                //"Emoji/Prefab/Button";
                var obj = new GameObject("Button" + ButtonPool.Count);
                obj.AddComponent<Image>();
                obj.AddComponent<Button>();
                var node = new NodeButton(obj);
                node.SetParent(transform);
                return node;
            };
            ButtonPool.ActiveEvent += item =>
            {
                item.Used = true;
                item.SetActive(true);
                item.SetParent(null);
            };
            ButtonPool.ReleaseEvent += item =>
            {
                item.Used = false;
                item.Release();
                item.SetActive(false);
                item.SetParent(transform);
            };
            //DummyImage
            HyperlinkPool = new ObjectPool<NodeHyperlink>();
            HyperlinkPool.CreateEvent += (tag_data) =>
            {
                //"Emoji/Prefab/DummyImage";
                var obj = new GameObject("DummyImage" + HyperlinkPool.Count);
                obj.AddComponent<Image>().color = new Color();
                obj.AddComponent<Button>();
                var node = new NodeHyperlink(obj);
                node.SetParent(transform);
                return node;
            };
            HyperlinkPool.ActiveEvent += item =>
            {
                item.Used = true;
                item.SetActive(true);
                item.SetParent(null);
            };
            HyperlinkPool.ReleaseEvent += item =>
            {
                item.Used = false;
                item.Release();
                item.SetActive(false);
                item.SetParent(transform);
            };
            //Emoji
            EmojiPool = new ObjectPool<NodeEmoji>(true);
            EmojiPool.CreateEvent += (tag_data) =>
            {
                var prefab = AssetCacheMgr.GetInstance().LoadEmoji(tag_data.id);
                var node = new NodeEmoji(prefab != null ? Instantiate(prefab) : new GameObject("NULL:" + tag_data.id));
                node.SetParent(transform);
                return node;
            };
            EmojiPool.ActiveEvent += item =>
            {
                item.Used = true;
                item.SetActive(true);
                item.SetParent(null);
            };
            EmojiPool.ReleaseEvent += item =>
            {
                item.Used = false;
                item.Release();
                item.SetActive(false);
                item.SetParent(transform);
            };
        }
    }
    internal class ObjectPool<T> where T : Node
    {
        //事件接口
        public event Func<TagData, T> CreateEvent;
        public event Action<T> ReleaseEvent;
        public event Action<T> ActiveEvent;
        //计数器
        public int Count { get { return _listPool.Count; } }

        //节点池
        private List<T> _listPool = null;
        private Dictionary<string, List<T>> _dictPool = null;

        public ObjectPool(bool use_dict_pool = false)
        {
            if (use_dict_pool)
                _dictPool = new Dictionary<string, List<T>>();
            else
                _listPool = new List<T>();
        }
        private List<T> GetPool(string id)
        {
            if (_listPool != null)
            {
                return _listPool;
            }
            List<T> pool = null;
            _dictPool.TryGetValue(id, out pool);
            if (pool == null)
            {
                pool = new List<T>();
                _dictPool.Add(id, pool);
            }
            return pool;
        }
        public void Release(T item)
        {
            if (item != null && !item.IsDestroy())
            {
                //回收事件
                ReleaseEvent?.Invoke(item);
                //处理列表
                List<T> pool = GetPool(item.tagData.id);
                if (!pool.Contains(item))
                {
                    pool.Add(item);
                }
            }
        }
        public void Release(IEnumerable<T> lst)
        {
            foreach (var item in lst)
            {
                Release(item);
            }
        }
        public T Get(TagData data)
        {
            T item = default(T);

            List<T> pool = GetPool(data.id);
            while (pool.Count > 0 && (item == null || item.IsDestroy()))
            {
                item = pool[0];
                pool.RemoveAt(0);
            }
            //创建节点/创建事件
            if (item == null || item.IsDestroy())
            {
                item = CreateEvent(data);
            }
            //Get事件
            ActiveEvent?.Invoke(item);

            return item;
        }

        public void Clear()
        {
            _listPool?.Clear();
            _dictPool?.Clear();
        }
        public void Tick()
        {
            if (_listPool != null)
            {
                for (int i = _listPool.Count - 1; i >= 0; i--)
                {
                    if (_listPool[i].IsDestroy())
                        _listPool.RemoveAt(i);
                }
            }
            if (_dictPool != null)
            {
                foreach (var lst in _dictPool.Values)
                {
                    for (int i = lst.Count - 1; i >= 0; i--)
                    {
                        if (lst[i].IsDestroy())
                            lst.RemoveAt(i);
                    }
                }
            }
        }
    }

    internal class NodeButton : Node
    {
        public Image image { get; private set; }
        public Button button { get; private set; }
        public NodeButton(GameObject obj) : base(obj)
        {
            this.image = obj.GetComponent<Image>();
            this.button = obj.GetComponent<Button>();
        }
        public override void SetData(TagData data)
        {
            base.SetData(data);
            image.sprite = AssetCacheMgr.GetInstance().LoadButton(data.id);
            button.onClick.RemoveAllListeners();
            if (data.callback != null)
                button.onClick.AddListener(data.callback);
        }
        public override void Release()
        {
            button.onClick.RemoveAllListeners();
        }
    }
    internal class NodeHyperlink : Node
    {
        public Button button { get; private set; }

        public NodeHyperlink(GameObject obj) : base(obj)
        {
            this.button = obj.GetComponent<Button>();
        }
        public override void SetData(TagData data)
        {
            base.SetData(data);
            button.onClick.RemoveAllListeners();
            if (data.callback != null)
                button.onClick.AddListener(data.callback);
        }
        public void SetBound(Vector4 bound)
        {
            transform.sizeDelta = new Vector2(bound.z - bound.x, bound.w - bound.y);
            transform.anchoredPosition3D = new Vector3(bound.x, bound.y, 0);
        }
        public override void Release()
        {
            button.onClick.RemoveAllListeners();
        }
    }
    internal class NodeEmoji : Node
    {
        public NodeEmoji(GameObject obj) : base(obj)
        {
        }
    }
    internal class NodeImage : Node
    {
        private Image image;
        public NodeImage(GameObject obj) : base(obj)
        {
            this.image = obj.GetComponent<Image>();
        }
        public override void SetData(TagData data)
        {
            base.SetData(data);
            image.sprite = AssetCacheMgr.GetInstance().LoadImage(data.id);
        }
    }
    internal class Node
    {
        public bool Used = false;
        public TagData tagData { get; private set; }
        public GameObject gameObject { get; private set; }
        public RectTransform transform { get; private set; }

        public Node(GameObject obj)
        {
            gameObject = obj;
            transform = obj.GetComponent<RectTransform>();
        }

        public bool IsDestroy()
        {
            return gameObject == null;
        }
        public bool IsNull()
        {
            return gameObject == null || tagData == null;
        }
        public virtual void SetActive(bool active)
        {
            if (IsDestroy())
            {
                throw new System.Exception("这个对象已被销毁");
            }
            gameObject.SetActive(active);
        }
        public virtual void SetParent(Transform parent)
        {
            if (IsDestroy())
            {
                throw new System.Exception("这个对象已被销毁");
            }
            this.transform.SetParent(parent);
            this.transform.localScale = Vector3.one;
            this.transform.localRotation = Quaternion.identity;
            this.transform.pivot = Vector2.zero;
            this.transform.anchorMin = Vector2.zero;
            this.transform.anchorMax = Vector2.zero;
        }
        public virtual void SetData(TagData data)
        {
            if (IsDestroy())
            {
                throw new System.Exception("这个对象已被销毁");
            }
            tagData = data;
            transform.sizeDelta = new Vector2(data.width, data.height);
            transform.anchoredPosition3D = data.startPosition;
        }
        public virtual void Release()
        {
        }
    }

    #endregion
}

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值