Unity ScrollRect實(shí)現(xiàn)軌跡滑動(dòng)效果
本文實(shí)例為大家分享了Unity ScrollRect實(shí)現(xiàn)軌跡滑動(dòng)效果的具體代碼,供大家參考,具體內(nèi)容如下
以下內(nèi)容是根據(jù)Unity 2020.1.01f版本進(jìn)行編寫的
1、目的
工作中遇到有需要實(shí)現(xiàn)軌跡滑動(dòng)的滑動(dòng)列表,通常的做法是計(jì)算貝塞爾曲線得出軌跡,但是我覺得計(jì)算貝塞爾曲線太麻煩了,或許有沒有更簡(jiǎn)單的方法。
2、思考
軌跡滑動(dòng)可以分兩種情況:
第一種是軌跡滑動(dòng)是比較簡(jiǎn)單的橫向(或縱向)滑動(dòng),其中軌跡不會(huì)蜿蜒盤旋,也不涉及列表格子之間的重疊關(guān)系,這時(shí)可以分區(qū)間來(lái)對(duì)Y軸(或X軸)進(jìn)行設(shè)置以達(dá)到格子沿著軌跡滑動(dòng)的效果
例如,軌跡是這樣的波浪型的,其中,軌跡點(diǎn)的數(shù)量可以無(wú)限增加,點(diǎn)越多軌跡可以設(shè)置得越平滑,但是消耗的性能也越多:
像這樣,類似波浪型的軌跡,可以用一個(gè)Vector列表記錄下每個(gè)點(diǎn)的位置和順序,然后根據(jù)每個(gè)格子所在的位置,先由小到大循環(huán)判斷在哪個(gè)區(qū)間,例如格子3在pos_5和pos_6中間,所以第三個(gè)格子的區(qū)間就是5,然后通過(guò)pos_5和pos_6兩個(gè)點(diǎn)相減,得到一個(gè)從pos_5指向pos_6的向量vector,通過(guò)vector3.distance函數(shù)得到pos_5和pos_6兩個(gè)點(diǎn)的橫向距離distanceX1,然后再次通過(guò)vector3.distance函數(shù)得到pos_5與格子3得橫向距離distanceX2,那么,格子3的位置 = pos_5的位置 + distanceX2 / distanceX1 * vector,得到位置后再把位置設(shè)置回去就可以了
第二種就是更為復(fù)雜的軌跡滑動(dòng),例如蜿蜒盤旋式的軌跡,其中還包括有層級(jí)關(guān)系
例如,軌跡是蜿蜒盤旋的,同樣是軌跡點(diǎn)越多,曲線越平滑,也越消耗性能:
像這樣,蜿蜒盤旋的軌跡,就不能使用第一種方法了,因?yàn)橛行┪恢茫粋€(gè)X值對(duì)應(yīng)下來(lái)有多個(gè)Y值,無(wú)法區(qū)分當(dāng)前需要的是哪個(gè)Y值,所以,需要使用另一種方法
我們可以通過(guò)獲取所有的軌跡點(diǎn),計(jì)算出每?jī)蓚€(gè)相鄰軌跡點(diǎn)之間的距離,通過(guò)距離確定格子應(yīng)該在哪個(gè)區(qū)間內(nèi)
例如,格子3的位置是95,假設(shè)pos_1和pos_2的距離為50,pos_2和pos_3的距離為也是50,那么pos_1到pos_3的總距離就是100,所以格子3應(yīng)該在區(qū)間pos_2和pos_3中間。接下來(lái)計(jì)算實(shí)際位置,因?yàn)榇藭r(shí)格子的X軸和Y軸都會(huì)變化,同樣無(wú)法使用第一種方法來(lái)計(jì)算格子的位置,需要通過(guò)格子的位置減去前面區(qū)間距離的和,在例子中,就是格子3的位置減去pos_1到pos_2的距離,即95 – 50 = 45,再通過(guò)剩余的距離除以當(dāng)前區(qū)間的距離得出比例ratio,然后就是通過(guò)當(dāng)前的兩個(gè)區(qū)間點(diǎn)相減得到pos_2指向pos_3的向量vector,那么格子3的位置就是,pos_2的位置 + ratio * vector
最后,還有層級(jí)的問(wèn)題,也就是哪個(gè)格子遮擋哪個(gè)格子,一般是后面的格子遮擋前面的格子,如果需要前面的遮擋后面的格子,只需要?jiǎng)討B(tài)修改層級(jí)就行,unity提供了這個(gè)函數(shù):Transform.SetAsFirstSibling()
在實(shí)際使用中,我發(fā)現(xiàn)使用這種方法后,content的長(zhǎng)度有時(shí)候會(huì)過(guò)長(zhǎng),有時(shí)候會(huì)過(guò)短,所以需要確定好格子的數(shù)量,設(shè)置好content的正確長(zhǎng)度
注意:以上都是最基礎(chǔ)的思想,因?yàn)榛瑒?dòng)時(shí)改變的是content節(jié)點(diǎn),格子的位置實(shí)際上是不變的,或者是由代碼設(shè)置的,所以實(shí)際代碼中還需要考慮scrollrect的寬高以及content節(jié)點(diǎn)的滑動(dòng)值(即posX值,滑動(dòng)時(shí)此值會(huì)變化)
3、自定義實(shí)現(xiàn)軌跡滑動(dòng)
using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; [RequireComponent(typeof(ScrollRect))] public class CurveSliding : MonoBehaviour { public Transform posListRoot; public float contentwidth; //必須自己設(shè)置的content實(shí)際長(zhǎng)度 public bool isChangeHierarchy = false; private ScrollRect scrollRect; private RectTransform content; private List<Transform> posTransformList = new List<Transform>(); private List<RectTransform> item_transformList = new List<RectTransform>(); private List<float> posX_List = new List<float>(); private List<float> distanceList = new List<float>(); private float scrollRectWidth; private float scrollRectHeight; private float posTotalDistance; private float spacing = 0; private void Start() { scrollRect = GetComponent<ScrollRect>(); content = scrollRect.content.transform as RectTransform; HorizontalLayoutGroup contentLayoutGroup = content.GetComponentsInChildren<HorizontalLayoutGroup>(true)[0]; if(contentLayoutGroup != null && contentLayoutGroup.name == content.name) { spacing = contentLayoutGroup.spacing; } scrollRectWidth = scrollRect.GetComponent<RectTransform>().sizeDelta.x; scrollRectHeight = scrollRect.GetComponent<RectTransform>().sizeDelta.y; for (int i = 0; i < content.childCount; i++) { RectTransform item_transform = content.GetChild(i) as RectTransform; if (item_transform.gameObject.activeInHierarchy) { item_transformList.Add(item_transform); posX_List.Add(item_transform.localPosition.x); } else { continue; } } float totalDistance = 0; for(int i = 0;i < posListRoot.childCount;i++) { Transform posTransform = posListRoot.GetChild(i); if (i > 0) { Transform previousPosTransform = posListRoot.GetChild(i - 1); totalDistance += Vector3.Distance(posTransform.localPosition, previousPosTransform.localPosition); } posTransformList.Add(posTransform); distanceList.Add(totalDistance); } posTotalDistance = distanceList[distanceList.Count - 1]; content.sizeDelta = new Vector2(contentwidth, content.sizeDelta.y); OnValueChange(Vector2.zero); scrollRect.onValueChanged.AddListener(OnValueChange); } public void OnValueChange(Vector2 vector) { for(int i = 0;i < item_transformList.Count;i++) { float localPosX = posX_List[i]; float posX = localPosX + content.anchoredPosition.x; //如果當(dāng)前節(jié)點(diǎn)的位置 - content的X軸偏移值 > 滑動(dòng)列表的寬度,則說(shuō)明當(dāng)前item在可視范圍外 if (posX > posTotalDistance + 200 || posX < 0) { continue; } int index = -1; foreach(var totalDistance in distanceList) { if(posX < totalDistance) { break; } else { index++; } } //如果index+1小于位置列表的數(shù)量,則其在位置區(qū)間內(nèi),否則應(yīng)該在位置區(qū)間外 if (index + 1 < posListRoot.childCount && index + 1 > 0) { float ratio = (posX - distanceList[index]) / (distanceList[index + 1] - distanceList[index]); Vector3 newPos = posTransformList[index].localPosition - ratio * (posTransformList[index].localPosition - posTransformList[index + 1].localPosition); item_transformList[i].localPosition = new Vector3(newPos.x + scrollRectWidth/2 - content.anchoredPosition.x, -scrollRectHeight / 2 + newPos.y, 0); } else if(index <= -1) { item_transformList[i].localPosition = new Vector3(item_transformList[i].localPosition.x, -scrollRectHeight / 2 + posListRoot.GetChild(0).localPosition.y, 0); } else if(index >= posListRoot.childCount - 1) { if (i < 1) { continue; } RectTransform previousItem_RectTransform = item_transformList[i - 1]; item_transformList[i].localPosition = new Vector3(previousItem_RectTransform.localPosition.x + spacing + previousItem_RectTransform.sizeDelta.x/2 + item_transformList[i].sizeDelta.x/2, -scrollRectHeight / 2 + posListRoot.GetChild(posListRoot.childCount - 1).localPosition.y, 0); } if (isChangeHierarchy) { item_transformList[i].SetAsFirstSibling(); } } } }
4、兩種方法的優(yōu)缺點(diǎn)
1)、 兩種方法都不能使用排序組件,因?yàn)榕判蚪M件會(huì)控死格子的位置,通過(guò)代碼也無(wú)法修改
2)、 第二種方法比第一種方法更復(fù)雜,同時(shí)消耗的性能也更多,但是能實(shí)現(xiàn)更復(fù)雜的效果
3)、第二種方法需要設(shè)置conten的長(zhǎng)度,即格子的數(shù)量無(wú)法動(dòng)態(tài)變化,是一大缺點(diǎn)
5、最終效果
第一種:
第二種:
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#使用StreamReader和StreamWriter類讀寫操作文件
這篇文章介紹了C#使用StreamReader和StreamWriter類讀寫操作文件的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05利用Distinct()內(nèi)置方法對(duì)List集合的去重問(wèn)題詳解
這篇文章主要給大家介紹了關(guān)于利用Distinct()內(nèi)置方法對(duì)List集合的去重問(wèn)題的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06C#中后臺(tái)post請(qǐng)求常用的兩種方式總結(jié)
這篇文章主要介紹了C#中后臺(tái)post請(qǐng)求常用的兩種方式總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06C# Winform 子窗體訪問(wèn)父級(jí)窗體的控件和屬性
本文主要介紹兩種子窗體訪問(wèn)父窗體控件和屬性的方法,大家可以參考一下,本人比較偏向第二種,把父窗體作為屬性傳遞,一勞永逸,想訪問(wèn)父窗體的什么控件屬性都可以。2016-05-05C#實(shí)現(xiàn)獲取電腦硬件顯卡信息的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何使用C#實(shí)現(xiàn)獲取電腦硬件顯卡信息,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01C#?winform實(shí)現(xiàn)多語(yǔ)言切換功能
這篇文章主要為大家詳細(xì)介紹了如何使用C#?winform實(shí)現(xiàn)多語(yǔ)言切換功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以了解下2024-02-02Unity3D實(shí)現(xiàn)人物轉(zhuǎn)向與移動(dòng)
這篇文章主要為大家詳細(xì)介紹了Unity3D實(shí)現(xiàn)人物轉(zhuǎn)向與移動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-01-01