效果图:
优缺点:
优点:
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]; } } }
/*************************************** 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 } }
/*************************************** 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; } } } }
资源索引:
这个暂时不提供,有公司的美术资源和框架,后面离开公司以后再发上来