NGUI 摇奖滚轮

效果图:

  

  

优缺点:

  优点:

  1.一条曲线完美解决旋转问题

  2. 解决了超速的问题,现在速度再快也不会乱了

  3.快速停止的时候进行了进度区分,后面的会等前面的停了再停

  缺点:

  1.停止节奏上会有细微差距,导致音频节奏不好,可以通过其他方式解决

代码结构:

简述:

  ScrollElement:控制元素的图片切换,并记录自己是第几个进入Rect的号码

  ScrollRect: 负责管理自己的旋转状态、曲线,堆排序等

  (堆排序的目的是 单帧没办法判断有多少个元素从底部到达了顶部,这些元素都需要从列表中拿元素值,为了保证数据顺序,需要进行一次排序)

  ScrollManager: 管理所有ScrollRect,并且与其他模块接口

代码:

/***************************************
Editor: Tason
Version: v1.0
Last Edit Date: 2018-XX-XX 
Tel: 328791554@qq.com
Function Doc: 
旋转元素
***************************************/

using UnityEngine;

namespace DYHT
{
    public class MyScrollRectElement : MonoBehaviour
    {
        //入队列的序号
        private int m_realIndex;
        public int RealIndex
        {
            get { return m_realIndex; }
            set { m_realIndex = value; }
        }

        //当前序号
        public int m_currentElementId;

        public UISprite m_sprite;

        public string[] m_spritesNameList;

        //打开第几组图片
        public void SetDisplayID(int _id)
        {
            //安全校验
            if (_id < 0 || _id > m_spritesNameList.Length - 1)
                return;

            m_currentElementId = _id;


            m_sprite.spriteName = m_spritesNameList[_id];
        }
    }
}
View Code
/***************************************
Editor: Tason
Version: v1.1
Last Edit Date: 2018-XX-XX 
Tel: 328791554@qq.com
Function Doc: 
执行顺序

注意:
1.速度参数不要超过目标帧数,速度不要过快,就是1帧m_offsetValue 变化量不能超过1,按照30帧算 就是
0.0333f * scrollSpeed  < 1 所以 scroll speed 就要小于30。 
2.元素个数请务必保持偶数!!
3.位置曲线的 末节点 Key-Value值 Key(0.9999)Value(1) 

 *       ┌-┐       ┌-┐
 *   ┌--┘ ┴-------┘ ┴--┐~~
 *   │          神         │ ~~
 *   │         ---         │
 *   ████───████ │      ~~~~~
 *   │                     │
 *   │        /           │~~~
 *   │                     │
 *   └--┐         ┌------┘
 *       │         │                ~~~~~~
 *       │         │         ~~~       
 *       │         │                        ~~~
 *       │         └-----------------┐
 *       │                            │
 *       │                            ├-┐
 *       │                            ┌-┘
 *       │                            │
 *       └-┐  ┐  ┌-------┬--┐  ┌┘~~~~~~
 *          │ -┤ -┤       │ -┤ -┤~~~~~
 *          └--┴--┘       └--┴--┘
 *                神兽保佑
 *               代码无BUG!
***************************************/

using UnityEngine;
using System.Collections.Generic;

namespace DYHT
{
    public class MyScrollRect : MonoBehaviour
    {
        #region Variable

        //滚动的n个阶段   
        [System.Serializable]
        public enum ScrollState
        {
            Init,               //初始化
            Idle,               //空闲
            StartAni,           //起步动画阶段 
            AddAndKeep,         //加速&保持最高速阶段
            ReduceSpeed,        //减速阶段
            SetFinal,           //设置最终显示值阶段
            EndAni,             //最终动画播放阶段
        }
        [Header("显示部分")]
        public ScrollState m_currentState;

        //当前旋转器序号
        public int m_index;

        //动画结束委托
        public delegate void EndScrollCallBack(int _index);
        public EndScrollCallBack endScrollCallBack;

        //一条位移曲线 “     的曲线”
        [Header("可调参数")]
        public AnimationCurve m_positionCurve;

        //模拟列表
        private List<int> m_simulationList;

        //最终选择列表
        private List<int> m_finialList;

        //元素个数(必须为偶数且大于等于2)
        public int m_elementCount = 4;

        //模拟的元素个数
        public int m_simulationCount = 16;

        //模拟列表下标计数
        private int m_simulationTempIndex;

        //总高度
        private float m_verticalLength;

        //卡片的实际间隔
        public float m_cellInterval = 120;

        //起始位置
        private float m_startPosition;

        //当前偏移值
        private float m_offsetValue;

        //取号序列记录
        private int m_keepScrollTemp;

        //目标值
        private float m_targetValue;

        //一条加速度曲线 末尾一定要是1
        public AnimationCurve m_scrollAddSpeedCurve;

        //当前速度 - 最大速度 - 最小速度 - 耗时 - 计时器 - 减速阶段目标速度
        public float m_currentSpeed = 0;
        public float m_maxSpeed = 5;
        public float m_minSpeed = 1;
        public float m_addSpeedTime = 0.6f;
        public float m_subSpeedTime = 0.6f;
        private float m_addTimer = 0;
        private float m_subTimer = 0;

        //元素间曲线间隔
        private float m_curvalInterval;

        //最终展示的图片索引
        private List<int> m_finalDisplayArray;

        //设置初始播放阶段动画偏移值 - 曲线 - 总时间 - 计时器 - 目标值 - 原值
        public float m_startOffsetValue = 0.15f;
        public AnimationCurve m_startAniCurve;
        public float m_startAniTime = 0.1f;
        private float m_startAniTimer = 0;
        private float m_startTargetValue;
        private float m_startOrginalValue;

        //设置最终播放阶段动画偏移值 - 曲线 - 总时间 - 计时器 - 目标值 - 原值
        public float m_finialOffsetValue = 0.1f;
        public AnimationCurve m_finialAniCurve;
        public float m_finialAniTime = 0.3f;
        private float m_finialAniTimer = 0;
        private float m_finialTargetValue;
        private float m_finialOrginalValue;


        //开始动画结束后进入速停的标记
        private bool m_isStartEndToQuickStop = false;

        #endregion

        #region Quote

        //最大堆
        private MaxHeap<MyScrollRectElement> m_maxHeap;

        //存放的元素
        [HideInInspector]
        public List<MyScrollRectElement> m_elements;

        #endregion

        #region SystemFunction
        private void Awake()
        {
            //开堆
            m_elements          = new List<MyScrollRectElement>();
            m_finalDisplayArray = new List<int>();
            m_simulationList    = new List<int>();
            m_finialList        = new List<int>();
            m_maxHeap           = new MaxHeap<MyScrollRectElement>(JudgeFunc0);
            //m_sortElements      = new List<MyScrollRectElement>();
        }
        
        void Update()
        {
            float temp;
            switch (m_currentState)
            {
                case ScrollState.StartAni:
                    if (m_startAniTimer + Time.deltaTime >= m_startAniTime)
                    {
                        m_offsetValue = m_startTargetValue;
                        ChangePosition();
                        m_currentState = ScrollState.AddAndKeep;
                        m_currentSpeed = 0;
                        m_addTimer = 0;
                    }
                    else
                    {
                        m_startAniTimer += Time.deltaTime;
                        ChangePosition();
                    }
                    break;
                case ScrollState.AddAndKeep:
                    if (m_currentSpeed < m_maxSpeed)
                    {
                        m_addTimer += Time.deltaTime;
                        m_currentSpeed = JWTools.Remap(0, m_addSpeedTime, 0, m_maxSpeed, m_addTimer);
                        m_currentSpeed = m_currentSpeed > m_maxSpeed ? m_maxSpeed : m_currentSpeed;
                    }

                    temp = Time.deltaTime * m_currentSpeed;
                    temp -= Mathf.Floor(temp);
                    m_offsetValue += temp;
                    ChangePosition();

                    if (m_isStartEndToQuickStop)
                    {
                        m_isStartEndToQuickStop = false;
                        StopScroll();
                    }
                    break;
                case ScrollState.ReduceSpeed:
                    if (m_currentSpeed > m_minSpeed)
                    {
                        m_subTimer = (m_subTimer + Time.deltaTime) > m_subSpeedTime ? m_subSpeedTime : (m_subTimer + Time.deltaTime);
                        m_currentSpeed = JWTools.Remap(0, m_subSpeedTime, m_maxSpeed, m_minSpeed, m_subTimer);
                        m_currentSpeed = m_currentSpeed < m_minSpeed ? m_minSpeed : m_currentSpeed;
                    }
                    else
                    {
                        StopScroll();
                    }

                    temp = Time.deltaTime * m_currentSpeed;
                    temp -= Mathf.Floor(temp);
                    m_offsetValue += temp;

                    m_offsetValue += Time.deltaTime * m_currentSpeed;
                    ChangePosition();
                    break;
                case ScrollState.SetFinal:
                    temp = Time.deltaTime * m_currentSpeed;
                    temp -= Mathf.Floor(temp);
                    m_offsetValue += temp;
                    ChangePosition();
                    break;
                case ScrollState.EndAni:
                    if (m_finialAniTimer + Time.deltaTime >= m_finialAniTime)
                    {
                        m_offsetValue = m_finialTargetValue;
                        ChangePosition();
                        m_currentState = ScrollState.Idle;
                        
                        if (endScrollCallBack != null)
                            endScrollCallBack(m_index);
                    }
                    else
                    {
                        m_finialAniTimer += Time.deltaTime;
                        ChangePosition();
                    }
                    break;
                default:
                    break;
            }
        }

        #endregion

        #region Other
        public void Init(int _index, IList<int> _l)
        {
            m_index = _index;

            //安全校验
            if (_l == null || _l.Count < m_elementCount || m_elementCount < 2)
            {
                WDebug.WDebugLog("Error Initialization!");
                return;
            }

            if (m_maxSpeed > Application.targetFrameRate)
                WDebug.WDebugLog("Error : ScroolSpeed is too quick!");

            //变量初始化
            m_verticalLength = m_cellInterval * m_elementCount;
            m_curvalInterval = 1.0f / m_elementCount;
            m_offsetValue = 0f;
            m_startPosition = -m_elementCount / 2 * m_cellInterval;
            m_targetValue = 0;
            m_simulationTempIndex = 0;
            m_currentState = ScrollState.Init;

            //生成元素
            m_elements.Clear();
            for (int i = 0; i < m_elementCount; ++i)
            {
                AddElements(i, _l[i]);
            }

            //添加模拟列表
            m_simulationList.Clear();
            for (int i = 0; i < m_simulationCount; ++i)
            {
                m_simulationList.Add(Random.Range(0, Const_DYHT.m_elementCount));
            }

            //修正一次位置
            ChangePosition();
        }

        //设置元素 参0 当前序列编号 参1 初始图片下标
        public void AddElements(int _num, int _picIndex)
        {
            //对象池生成一个实例
            GameObject obj = JWABObjectPool.Instance.Spawn("ScrollElement") as GameObject;
            obj.name = string.Format("Select{0}", _num);
            obj.transform.SetParent(transform);
            obj.transform.localScale = Vector3.one;
            obj.transform.localPosition = new Vector3(0, 0, 0);

            obj.GetComponent<MyScrollRectElement>().SetDisplayID(_picIndex);

            //放入队列
            m_elements.Add(obj.transform.GetComponent<MyScrollRectElement>());
            obj.transform.GetComponent<MyScrollRectElement>().RealIndex = _num;

            //由于NGUI Panel关联的特性,还需要重新激活一次物体渲染
            obj.SetActive(false);
            obj.SetActive(true);
        }

        //修改位置 & 检测是否需要修正图片
        void ChangePosition()
        {
            switch (m_currentState)
            {
                case ScrollState.Init:
                    for (int i = 0; i < m_elements.Count; ++i)
                    {
                        Vector3 nP = new Vector3(0, m_startPosition + m_positionCurve.Evaluate(m_offsetValue + (m_elements[i].RealIndex + 1) * m_curvalInterval) * m_verticalLength, 0);
                        m_elements[i].transform.localPosition = nP;
                    }
                    m_currentState = ScrollState.Idle;
                    break;
                case ScrollState.StartAni:
                    //修正位置
                    for (int i = 0; i < m_elements.Count; ++i)
                    {
                        if (m_startAniTime != 0)
                            m_offsetValue = Mathf.Lerp(m_startOrginalValue, m_startTargetValue, m_startAniCurve.Evaluate(m_startAniTimer / m_startAniTime));
                        else
                            m_offsetValue = m_startTargetValue;
                        m_elements[i].transform.localPosition = new Vector3(0, m_startPosition + m_positionCurve.Evaluate(m_offsetValue + (m_elements[i].RealIndex + 1) * m_curvalInterval) * m_verticalLength, 0);
                    }
                    break;
                case ScrollState.ReduceSpeed:
                    //修正位置
                    for (int i = 0; i < m_elements.Count; ++i)
                    {
                        Vector3 nP = new Vector3(0, m_startPosition + m_positionCurve.Evaluate(m_offsetValue + (m_elements[i].RealIndex + 1) * m_curvalInterval) * m_verticalLength, 0);
                        m_elements[i].transform.localPosition = nP;
                    }
                    break;
              
                case ScrollState.AddAndKeep:
                case ScrollState.SetFinal:
                    //是否需要修正值
                    bool correctValueFlag = false;

                    //引入最大堆排序 Step0 清空最大堆
                    m_maxHeap.Clear();

                    //修正位置
                    for (int i = 0; i < m_elements.Count; ++i)
                    {
                        Vector3 nP = new Vector3(0, m_startPosition + m_positionCurve.Evaluate(m_offsetValue + (m_elements[i].RealIndex + 1) * m_curvalInterval) * m_verticalLength, 0);

                        //逻辑判断(★★★)
                        if (nP.y > m_elements[i].transform.localPosition.y)
                        {
                            //入堆前设置当前值
                            m_elements[i].transform.localPosition = nP;
                            if (m_currentState == ScrollState.AddAndKeep)
                            {
                                //切换图片
                                if (m_simulationList.Count > 0)
                                {
                                    int n = (m_simulationTempIndex++) % m_simulationList.Count;

                                    m_elements[i].SetDisplayID(m_simulationList[n]);
                                }
                                else
                                    WDebug.WDebugLog("Error!");
                            }
                            else if (m_currentState == ScrollState.SetFinal)
                            {
                                //入堆
                                m_maxHeap.Push(m_elements[i]);
                            }
                        }
                        else
                            m_elements[i].transform.localPosition = nP;
                    }

                    //最大堆排序
                    while (m_maxHeap.Size > 0 && m_keepScrollTemp > 0)
                    {
                        //Debug.Log(m_index + "-" + m_keepScrollTemp + " ---> " + m_offsetValue + "");
                        if (m_keepScrollTemp > 1)
                        {
                            m_maxHeap.Pop().SetDisplayID(m_finialList[m_finialList.Count - m_keepScrollTemp--]);
                        }
                        else
                        {
                            m_maxHeap.Pop().SetDisplayID(m_finialList[m_finialList.Count - m_keepScrollTemp--]);

                            //修正位置 多向下走1/4的曲线距离
                            m_offsetValue -= m_offsetValue % m_curvalInterval + m_curvalInterval * m_maxHeap.Size - m_finialOffsetValue * m_curvalInterval;
                            //Debug.Log(m_index + " !!!! " + m_offsetValue);

                            //记录位置 准备最后一段动画
                            m_currentState = ScrollState.EndAni;
                            m_finialAniTimer = 0;
                            m_finialTargetValue = m_offsetValue - m_finialOffsetValue * m_curvalInterval;
                            m_finialOrginalValue = m_offsetValue;

                            //值修正
                            correctValueFlag = true;
                        }
                    }
                    //值修正
                    if (correctValueFlag)
                    {
                        correctValueFlag = false;
                        for (int i = 0; i < m_elements.Count; ++i)
                        {
                            m_elements[i].transform.localPosition = new Vector3(0, m_startPosition + m_positionCurve.Evaluate(m_offsetValue + (m_elements[i].RealIndex + 1) * m_curvalInterval) * m_verticalLength, 0);
                        }
                    }
                    break;
                case ScrollState.EndAni:
                    //修正位置
                    for (int i = 0; i < m_elements.Count; ++i)
                    {
                        if (m_finialAniTime != 0)
                            m_offsetValue = Mathf.Lerp(m_finialOrginalValue, m_finialTargetValue, m_finialAniCurve.Evaluate(m_finialAniTimer / m_finialAniTime));
                        else
                            m_offsetValue = m_finialTargetValue;
                        m_elements[i].transform.localPosition = new Vector3(0, m_startPosition + m_positionCurve.Evaluate(m_offsetValue + (m_elements[i].RealIndex + 1) * m_curvalInterval) * m_verticalLength, 0);
                    }
                    break;
            }
        }

        //设置最终List
        public void SetFinalList(List<int> _l)
        {
            m_finialList.Clear();
            for (int i = 0; i < _l.Count; ++i)
                m_finialList.Add(_l[i]);
        }

        //更新模拟列表 
        public void SetSimulationList(List<int> _l)
        {
            m_simulationList.Clear();
            for (int i = 0; i < _l.Count; ++i)
            {
                m_simulationList.Add(_l[i]);
            }
        }

        //开始旋转
        public void StartScroll()
        {
            //状态修改
            m_currentState = ScrollState.StartAni;

            //参数设置
            m_startAniTimer = 0;
            m_startOrginalValue = m_offsetValue;
            m_startTargetValue = m_startOrginalValue - m_startOffsetValue * m_curvalInterval;
        }
        //停止旋转
        public void StopScroll()
        {
            if (m_currentState == ScrollState.StartAni)
            {
                m_isStartEndToQuickStop = true;
            }
            else if (m_currentState == ScrollState.AddAndKeep || m_currentState == ScrollState.ReduceSpeed)
            {

                //参数设置
                if (m_currentState == ScrollState.AddAndKeep)
                    m_currentSpeed = m_maxSpeed;

                //状态修改
                m_currentState = ScrollState.SetFinal;

                m_addTimer = m_addSpeedTime;
                m_keepScrollTemp = m_finialList.Count;
            }
            else if (m_currentState == ScrollState.SetFinal)
            {
                m_currentSpeed = m_maxSpeed;
            }
        }

        //减速
        public void SubSpeed()
        {
            if (m_currentState == ScrollState.AddAndKeep)
            {
                //状态修改
                m_currentState = ScrollState.ReduceSpeed;

                //参数设置
                m_subTimer = 0;
            }
        }

        //最大堆比较方法
        private int JudgeFunc0(MyScrollRectElement e0, MyScrollRectElement e1)
        {
            if (e0.gameObject.transform.localPosition.y < e1.gameObject.transform.localPosition.y)
                return 1;
            else if (e0.gameObject.transform.localPosition.y == e1.gameObject.transform.localPosition.y)
                return 0;
            else
                return -1;
        }

        //添加完成回调函数
        public void AddEndCallBackFunc(EndScrollCallBack _func)
        {
            endScrollCallBack += _func;
        }

        //清空完成回调
        public void ClearEndCallBackFunc()
        {
            endScrollCallBack = null;
        }

        ////排序后的元素 用于隐藏元素
        //List<MyScrollRectElement> m_sortElements;

        ////隐藏几个元素 (从上到下排列 先排序再隐藏!)
        //public void HideElementsPre()
        //{
        //    m_maxHeap.Clear();

        //    for (int i = 0; i < m_elements.Count; ++i)
        //        m_maxHeap.Push(m_elements[i]);

        //    m_sortElements.Clear();

        //    for (int i = 0; i < m_elements.Count; ++i)
        //    {
        //        m_sortElements.Add(m_maxHeap.Pop());
        //    }
        //}

        //public void HideElement(int _index)
        //{
        //    if (_index < 0 || _index > m_sortElements.Count - 1)
        //        return;

        //    m_sortElements[_index].gameObject.SetActive(false);
        //}

        ////打开所有元素
        //public void ShowElements()
        //{
        //    for (int i = 0; i < m_elements.Count; ++i)
        //        m_elements[i].gameObject.SetActive(true);
        //}



        #endregion
    }
}
View Code
/***************************************
Editor: Tason
Version: v1.0
Last Edit Date: 2018-XX-XX 
Tel: 328791554@qq.com
Function Doc: 
旋转窗口管理器 
***************************************/

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace DYHT
{
    public class ScrollRectsManager : MonoBehaviour
    {
        #region variable
        [System.Serializable]
        public enum ScrollManagerState
        {
            Idle,       //停止状态
            Run,        //正常停止状态
            QuickStop   //快停
        }
        public ScrollManagerState m_currentState;

        //第几个在运行
        public int m_runIndex;
        #endregion

        #region Quote

        public MyScrollRect[] m_mr;

        #endregion


        #region Function

        private void Start()
        {
            //初始化
            List<int> temp = new List<int>();
            for (int i = 0; i < m_mr.Length; ++i)
            {
                temp.Clear();
                for (int n = 0; n < m_mr[0].m_elementCount; n++)
                {
                    int r = Random.Range(0, Const_DYHT.m_elementCount);
                    temp.Add(r);
                }

                if (m_mr[i] != null)
                {
                    m_mr[i].Init(i, temp);
                }
            }

            //参数设置
            m_currentState = ScrollManagerState.Idle;
            m_runIndex = -1;

            //事件添加
            for (int i = 0; i < m_mr.Length; ++i)
            {
                m_mr[i].ClearEndCallBackFunc();
                m_mr[i].AddEndCallBackFunc(ReportState);
            }
        }

        //滚完回调
        public void ReportState(int _index)
        {
            if (_index == m_mr.Length - 1)
            {
                m_currentState = ScrollManagerState.Idle;
                m_runIndex = -1;

                SceneService_DYHT.m_instance.m_bottomUIManager.OneTurnOver();

                return;
            }
            else
                m_runIndex = _index;

            if (m_currentState == ScrollManagerState.QuickStop)
            {
                //关闭所有协程
                StopAllCoroutines();

                //后续滚轮开始速停
                for (int i = _index + 1; i < m_mr.Length; ++i)
                    m_mr[i].StopScroll();
            }
        }

        #endregion
        //全部滚轮速停
        public void QuickStop()
        {
            if (m_currentState == ScrollManagerState.Run)
            {
                m_currentState = ScrollManagerState.QuickStop;
                if (m_runIndex == -1)
                {
                    for (int i = 0; i < m_mr.Length; ++i)
                        m_mr[i].StopScroll();
                }
                else
                    m_mr[((m_runIndex + 1) > (m_mr.Length - 1)) ? m_mr.Length - 1 : m_runIndex + 1].StopScroll();
            }
        }
        
        //设置滚轮最终选项 
        public void SetFinialValue(List<int> _data)
        {
            if (_data.Count == 15)
            {
                List<int> final0 = new List<int>();
                List<int> final1 = new List<int>();
                List<int> final2 = new List<int>();
                List<int> final3 = new List<int>();
                List<int> final4 = new List<int>();
                

                //这里注意顺序
                final0.Add(_data[2]);   final0.Add(_data[1]);   final0.Add(_data[0]);   final0.Add(0);
                final1.Add(_data[5]);   final1.Add(_data[4]);   final1.Add(_data[3]);   final1.Add(1);
                final2.Add(_data[8]);   final2.Add(_data[7]);   final2.Add(_data[6]);   final2.Add(2);
                final3.Add(_data[11]);  final3.Add(_data[10]);  final3.Add(_data[9]);   final3.Add(3);
                final4.Add(_data[14]);  final4.Add(_data[13]);  final4.Add(_data[12]);  final4.Add(4);


                m_mr[0].SetFinalList(final0);
                m_mr[1].SetFinalList(final1);
                m_mr[2].SetFinalList(final2);
                m_mr[3].SetFinalList(final3);
                m_mr[4].SetFinalList(final4);
            }
            else
                WDebug.WDebugLog("Error DataLength");
        }

        //设置旋转
        private IEnumerator SetSub(float _time, int _index)
        {
            yield return new WaitForSeconds(_time);
            m_mr[_index].SubSpeed();
        }

        //协程慢停
        public void NormalStop(float _t)
        {
            StopAllCoroutines();
            StartCoroutine(RealNormalStop(_t));
        }

        //真.协程慢停
        private IEnumerator RealNormalStop(float _t)
        {
            yield return new WaitForSeconds(_t);

            if (m_currentState != ScrollManagerState.Run)
                yield break;
            
            float m_intervalTime = 0.15f;
       
            for (int i = 0; i < m_mr.Length; ++i)
            {
                StartCoroutine(SetSub(m_intervalTime * i, i));
            }
        }

        //全部滚轮开始旋转
        public void AllStartScroll()
        {
            StopAllCoroutines();
            if (m_currentState == ScrollManagerState.Idle)
            {
                for (int i = 0; i < m_mr.Length; ++i)
                {
                    m_mr[i].StartScroll();
                }

                m_currentState = ScrollManagerState.Run;
                m_runIndex = -1;
            }
        }
    }
}
View Code

资源索引:

  这个暂时不提供,有公司的美术资源和框架,后面离开公司以后再发上来

原文地址:https://www.cnblogs.com/jwv5/p/9512382.html