Unity UGUI循环缩放卡牌展示

现在的商业项目需要的功能,本来打算在网上搜搜,但是没有发现有,所以自己花了点时间自己写了一个,实现了卡牌的循环缩放修改Alpha。

Unity UGUI循环缩放卡牌展示

 

namespace UIFramework
{
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.Events;
    using UnityEngine.EventSystems;
    using System.Collections.Generic;
    using DG.Tweening;

    /// <summary>
    /// 卡片选择器
    /// </summary>
    public class CardCollection : MonoBehaviour, IDragHandler, IEndDragHandler, IPointerClickHandler
    {
        class Card
        {
            public GameObject root = null;
            public GameObject page = null;
            public GameObject pageOn = null;
            public GameObject pageOff = null;
        }


        /// <summary>
        /// 需要动态生成的模板
        /// </summary>
        public GameObject template = null;
        /// <summary>
        /// 模板的页签
        /// </summary>
        public GameObject pageTemplate = null;
        /// <summary>
        /// 相对于页签的开启预制路径
        /// </summary>
        public string pageOn = "On";
        /// <summary>
        ///  相对于页签的关闭预制路径
        /// </summary>
        public string pageOff = "Off";
        /// <summary>
        /// 每一个目标的的位置差
        /// </summary>
        public Vector2 offset = new Vector2(100, 0);
        /// <summary>
        /// 移动速度
        /// </summary>
        public float moveSpeed = 0.01f;
        /// <summary>
        /// 是否循环
        /// </summary>
        public bool IfLoop = false;
        /// <summary>
        /// 相邻卡片scale差值
        /// </summary>
        public float ReduceScale = 0.2f;
        /// <summary>
        /// 改变Alpha通道
        /// </summary>
        public bool ChangeAlpha = true;
        /// <summary>
        /// 改变通道
        /// </summary>
        public bool ChangeColor = true;
        /// <summary>
        /// 自动改变顶部
        /// </summary>
        public bool AutoChangeTop = false;
        /// <summary>
        /// 使用曲线
        /// </summary>
        public bool UseCurve = false;
        /// <summary>
        /// 尺寸曲线
        /// </summary>
        public AnimationCurve ScaleCurve;
        /// <summary>
        /// 位置曲线
        /// </summary>
        public AnimationCurve PosCurve;
        /// <summary>
        /// 关闭拖动
        /// </summary>
        [HideInInspector]
        public bool disableDrag = false;

        /// <summary>
        /// 顶端改变事件
        /// </summary>
        public class TopChangeEvent : UnityEvent<GameObject, int> { }

        /// <summary>
        /// 顶部改变事件功能
        /// </summary>
        public TopChangeEvent onTopChanged = new TopChangeEvent();

        #region PRIVATES
        bool draging = false;
        float current = 0;
        RectTransform top = null;
        List<Card> cards = new List<Card>();
        bool autoChangeDirRight = true;
        float lastAutoChangeTime = 0f;
        List<GameObject> cache = new List<GameObject>();
        #endregion

        void Awake()
        {
            template.SetActive(false);
        }

        void Start()
        {
            lastAutoChangeTime = Time.realtimeSinceStartup;
        }

        void OnEnable()
        {
            lastAutoChangeTime = Time.realtimeSinceStartup;
        }

        void Update()
        {
            if (draging || !AutoChangeTop || top == null)
                return;
            if (Time.realtimeSinceStartup - lastAutoChangeTime > 5f)
            {
                int top_index = int.Parse(top.name);
                int to_index = top_index;
                if (autoChangeDirRight)
                {
                    if (top_index + 1 >= transform.childCount + 1)
                    {
                        to_index = top_index - 1;
                        autoChangeDirRight = false;
                    }
                    else
                    {
                        to_index = top_index + 1;
                    }
                }
                else
                {
                    if (top_index - 1 <= 0)
                    {
                        to_index = top_index + 1;
                        autoChangeDirRight = true;
                    }
                    else
                    {
                        to_index = top_index - 1;
                    }
                }
                DOTween.To(x => current = x, current, to_index - 1, 0.2f).OnUpdate(AutoRefresh);
                lastAutoChangeTime = Time.realtimeSinceStartup;
            }
        }     


        /// <summary>
        /// 移动卡片到顶端
        /// </summary>
        /// <param name="card">Card's name</param>
        public void BringUp(string card)
        {
            for (int i = 0; i < cards.Count; ++i)
            {
                if (cards[i].root.name == card)
                {
                    current = i;
                    Refresh(true);
                    return;
                }
            }
        }
        /// <summary>
        /// 设置卡片名
        /// </summary>
        /// <param name="_index"></param>
        /// <param name="_name"></param>
        /// <returns></returns>
        public Transform SetCardName(int _index, string _name)
        {
            if (_index >= cards.Count)
                return null;
            cards[_index].root.name = _name;
            return cards[_index].root.transform;
        }
        /// <summary>
        /// 通过名字获取卡片
        /// </summary>
        /// <param name="_name"></param>
        /// <returns></returns>
        public Transform GetCardByName(string _name)
        {
            for (int i = 0; i < cards.Count; ++i)
            {
                if (cards[i].root.name == _name)
                {
                    return cards[i].root.transform;
                }
            }
            return null;
        }

        /// <summary>
        /// 移动到顶端
        /// </summary>
        /// <param name="offset">Offset</param>
        /// <param name="scale">Scale</param>
        /// <param name="duration">Animation duration.</param>
        public void Move(Vector2 offset, float scale, float duration)
        {
            if (top == null) return;

            for (int i = 0; i < transform.childCount - 1; ++i)
            {
                transform.GetChild(i).gameObject.SetActive(false);
            }

            if (pageTemplate != null)
                pageTemplate.transform.parent.gameObject.SetActive(false);

            disableDrag = true;
            top.DOAnchorPos(offset, duration).SetEase(Ease.Linear);
            top.DOScale(scale, duration).SetEase(Ease.Linear);
        }

        /// <summary>
        /// 返回
        /// </summary>
        public void MoveBack(float duration)
        {
            if (top == null) return;

            top.DOAnchorPos(Vector2.zero, duration).SetEase(Ease.Linear);
            top.DOScale(1, duration).SetEase(Ease.Linear).OnComplete(() =>
            {
                for (int i = 0; i < transform.childCount - 1; ++i) transform.GetChild(i).gameObject.SetActive(true);

                if (pageTemplate != null)
                    pageTemplate.transform.parent.gameObject.SetActive(true);

                disableDrag = false;
                Refresh(true);
            });
        }

        /// <summary>
        /// 插入一个新的卡片
        /// </summary>
        /// <param name="name">Card name for Find.</param>
        /// <returns>Card root GameObject.</returns>
        public GameObject Spawn(string name)
        {
            GameObject obj = null;
            if (cache.Count > 0)
            {
                obj = cache[cache.Count - 1];
                cache.RemoveAt(cache.Count - 1);
            }
            else
            {
                obj = Instantiate(template);
            }
            obj.name = name;
            obj.transform.SetParent(transform);
            obj.transform.localPosition = Vector3.zero;
            obj.transform.localScale = Vector3.one;
            obj.transform.rotation = Quaternion.identity;
            obj.SetActive(true);

            Card card = new Card();
            card.root = obj;

            if (pageTemplate != null)
            {
                GameObject page = Instantiate(pageTemplate);
                page.transform.SetParent(pageTemplate.transform.parent);
                page.transform.localPosition = Vector3.zero;
                page.transform.localScale = Vector3.one;
                page.transform.rotation = Quaternion.identity;
                page.SetActive(true);

                card.page = page;
                card.pageOn = page.transform.Find(pageOn).gameObject;
                card.pageOff = page.transform.Find(pageOff).gameObject;
            }

            cards.Add(card);
            //Refresh( true );
            return obj;
        }

        /// <summary>
        /// 通过名字移除卡片
        /// </summary>
        /// <param name="_name"></param>
        public void Remove(string _name)
        {
            for (int i = 0; i < cards.Count; ++i)
            {
                GameObject obj = cards[i].root;
                if (obj.name == _name)
                {
                    obj.transform.SetParent(transform.parent, false);
                    cache.Add(obj);
                    obj.SetActive(false);
                    cards.RemoveAt(i);
                    lastAutoChangeTime = Time.realtimeSinceStartup;
                    if (top.gameObject == obj)
                    {
                        top = null;
                        Refresh();
                    }
                    break;
                }
            }
        }

        /// <summary>
        /// 获取当前卡片
        /// </summary>
        /// <returns>Card GameObject</returns>
        public GameObject GetTop()
        {
            if (top == null)
                return null;
            return top.gameObject;
        }

        /// <summary>
        /// 清除所有的卡片
        /// </summary>
        public void Clear()
        {
            for (int i = 0; i < cards.Count; ++i)
            {
                Destroy(cards[i].root);
                Destroy(cards[i].page);
            }

            cards.Clear();
            disableDrag = false;
            draging = false;
        }
        /// <summary>
        /// 将卡片移入缓存隐藏
        /// </summary>
        public void ClearToCache()
        {
            for (int i = 0; i < cards.Count; ++i)
            {
                GameObject obj = cards[i].root;
                obj.transform.SetParent(transform.parent, false);
                cache.Add(obj);
                obj.SetActive(false);
            }
            cards.Clear();
        }

        /// <summary>
        /// 拖动实现
        /// </summary>
        /// <param name="ev">Drag event data.</param>
        public void OnDrag(PointerEventData ev)
        {
            if (disableDrag) return;
            draging = true;
            lastAutoChangeTime = Time.realtimeSinceStartup;
            current -= moveSpeed * ev.delta.x;
            Refresh();
        }

        /// <summary>
        ///  拖动结束
        /// </summary>
        /// <param name="ev">End drag event data</param>
        public void OnEndDrag(PointerEventData ev)
        {
            if (disableDrag) return;
            float to = Mathf.RoundToInt(current);
            if (to != current)
            {
                DOTween.To(x => current = x, current, to, 0.2f).OnUpdate(HandRefresh);
            }
            draging = false;
            lastAutoChangeTime = Time.realtimeSinceStartup;
        }

        /// <summary>
        ///  点击事件
        /// </summary>
        /// <param name="ev">Pointer click event data</param>
        public void OnPointerClick(PointerEventData ev)
        {
            if (draging) return;

            RectTransform enter = ev.rawPointerPress.GetComponent<RectTransform>();
            if (enter == top) return;

            for (int i = 0; i < cards.Count; ++i)
            {
                if (enter.gameObject == cards[i].root || enter.IsChildOf(cards[i].root.transform))
                {
                    DOTween.To(x => current = x, current, i, 0.2f).OnUpdate
                    (
                        () =>
                        {
                            Refresh(false, false);
                        }
                    ).onComplete = () =>
                    {
                        Refresh(false);
                    };
                    ResetAutoChangeTime();
                    break;
                }
            }
        }
        /// <summary>
        /// 重置自动改变时间
        /// </summary>
        public void ResetAutoChangeTime()
        {
            lastAutoChangeTime = Time.realtimeSinceStartup;
        }
        /// <summary>
        /// 刷新
        /// </summary>
        /// <param name="_auto"></param>
        /// <param name="_invoke"></param>
        void Refresh(bool _auto = false, bool _invoke = true)

        {
            if (disableDrag) return;

            int count = cards.Count;
            if (IfLoop)
            {

                if (current > count)
                {
                    current = 0;
                }
                if (current < -1)
                {
                    current = count - 1;
                }
            }
            else
                current = Mathf.Clamp(current, 0, count - 1);

            for (int i = 0; i < count; ++i)
            {
                float delta = i - current;
                if (IfLoop)
                {
                    if (delta < 0)
                    {
                        float cacheNum = Mathf.Abs(delta);
                        if (cacheNum > (count / 2) - 1)
                        {
                            delta = count - cacheNum;
                        }
                    }
                    else
                    {
                        float cacheNum = delta;
                        if (cacheNum > (count / 2) - 1)
                        {
                            delta = cacheNum - count;
                        }
                    }
                }

                float scale = 1;
                float step = step = 1 - Mathf.Abs(delta) * ReduceScale; ;
                if (!UseCurve)
                {
                    scale = Mathf.Lerp(0, 1, step);
                }
                else
                {
                    scale = ScaleCurve.Evaluate(Mathf.Abs(delta));
                }
                bool isTop = Mathf.Abs(delta) < 0.5f;

                Card card = cards[i];
                RectTransform trans = card.root.GetComponent<RectTransform>();
                trans.localScale = Vector3.one * scale;
                if (!UseCurve)
                {
                    trans.anchoredPosition = offset * delta;
                }
                else
                {
                    if (delta > 0)
                        trans.anchoredPosition = offset * PosCurve.Evaluate(Mathf.Abs(delta));
                    else if (delta < 0)
                        trans.anchoredPosition = -offset * PosCurve.Evaluate(Mathf.Abs(delta));
                    else
                        trans.anchoredPosition = Vector2.zero;
                }

                int id = trans.gameObject.GetInstanceID();
                if (pageTemplate != null)
                {
                    card.pageOn.SetActive(isTop);
                    card.pageOff.SetActive(!isTop);
                }

                if (isTop)
                {
                    trans.SetSiblingIndex(count - 1);
                    //if (top != trans)
                    {
                        if (_invoke)
                            onTopChanged.Invoke(trans.gameObject, i);
                    }
                    top = trans;
                }
                else
                {
                    trans.SetSiblingIndex(count - 1 - Mathf.CeilToInt(Mathf.Abs(delta)));
                }

                if (ChangeColor)
                {
                    Image[] imgs = trans.GetComponentsInChildren<Image>();
                    foreach (var img in imgs)
                        img.color = Color.Lerp(Color.gray, Color.white, step);
                }

                Button[] btns = trans.GetComponentsInChildren<Button>();
                foreach (var btn in btns)
                {
                    if (btn.interactable)
                        btn.enabled = isTop;
                }
            }

            SetAlpha();
        }
        /// <summary>
        /// 自动刷新
        /// </summary>
        void AutoRefresh()
        {
            Refresh(true);
        }
        /// <summary>
        /// 拖动刷新
        /// </summary>
        void HandRefresh()
        {
            Refresh(false);
        }
        /// <summary>
        /// 设置Alpha
        /// </summary>
        private void SetAlpha()
        {
            if (!ChangeAlpha)
                return;
            int topIndex = 0;
            int count = cards.Count;
            for (int i = 0; i < count; ++i)
                if (cards[i].root.GetComponent<RectTransform>() == top)
                    topIndex = i;

            for (int i = 0; i < count; ++i)
            {
                float alpha = 1.0f;
                if (topIndex > i && topIndex - i == 1)
                    alpha = 0.7f;
                else if (topIndex < i && i - topIndex == 1)
                    alpha = 0.7f;
                else if (topIndex != i)
                    alpha = 0.5f;

                Card card = cards[i];
                CanvasGroup canvasGroup = card.root.GetComponent<CanvasGroup>();
                if (canvasGroup == null)
                    canvasGroup = card.root.AddComponent<CanvasGroup>();
                canvasGroup.alpha = alpha;
            }
        }     
    }
}