Unity實現透視滑動列表
本文實例為大傢分享瞭Unity實現透視滑動列表的具體代碼,供大傢參考,具體內容如下
1、目的
有時候,為瞭實現更好的美術效果,需要實現一些特殊的滑動列表,例如軌跡滑動,也有透視滑動。
註意:本文裡所展示的效果是未經測試的試驗版,如果用於實際項目中,應該還需要優化代碼和測試性能
2、思考
透視滑動列表可以有兩種方式來實現:
第一種方法是,通過shader實現,其核心原理是,定義一個中心點坐標(CenterX,CenterY),再定義一個透視系數_ OffsetPerspective,在vert函數中,對於每個頂點,計算其偏移值,距離自己定義的中心點越遠的頂點,其偏移值越大,簡單概括就是距離中心點越遠的頂點,就越把往中心點拉回去。實際算法就是:
OUT.worldPosition.x += (_CenterY + v.vertex.y – _OffsetX) / 1000 * (v.vertex.x – _CenterX) * _OffsetPerspective
這是我所使用的計算公式,其中,_OffsetX是自定義的偏移值,用於對所有頂點進行整體偏移;/ 1000是偏移值的縮放倍數,此值越小偏移程度越高。
註意:用這個方法做出來的透視滑動列表,主要的問題是一些交互控件的偏移問題,因為視覺效果偏移瞭,但是實際上可交互控件的位置沒有變,所以其點觸范圍是沒有透視效果時的范圍,這就會導致偏移的時候其點觸范圍不一樣,所以需要第二種方法。
第二種方法是使用純C#來實現,其核心原理是,通過遍歷所有子節點的Image和Text節點,同樣使用第一種方法的思路修改其VertexHelper中頂點的偏移值,再將修改完的頂點設置回VertexHelper中,然後重新生成一個Mesh,並將Mesh設置為CanvasRenderer的Mesh。為瞭得到正確的點觸范圍,還需要在節點中添加MeshCollider組件,通過代碼設置MeshCollider組件的Mesh屬性,並將Image或Text節點的Raycast Target屬性取消勾選,這樣就能保證最正確的點觸范圍瞭。
然後,因為滑動列表是可以滑動的,所以還需要滑動的時候一直通過上述方法動態修改節點,這裡使用滑動列表自帶的OnValueChange函數就行
註意:使用過多的MeshCollider對性能必定有消耗,不過Image和Text生成的Mesh都是比較簡單的,具體的性能消耗還是需要進行測試才能得出結果
3、自定義實現軌跡滑動
核心原理已經在上面說瞭,這裡直接上代碼:
using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PerspectiveScrollRect : MonoBehaviour { /// <summary> /// 中心點,中心點可以設置為不是原點 /// </summary> public Vector3 uiCenterPoint = Vector3.zero; /// <summary> /// 圖片的回拉像素,每距離中心點100個像素往回拉的距離 /// </summary> [Range(0,100)] public float perspective = 0; public ScrollRect scrollRect; /// <summary> /// 滑動列表節點 /// </summary> private RectTransform scrollRectRectTransform; private List<RectTransform> childRectTransformList = new List<RectTransform>(); private RectTransform rectTransform; private void Start() { scrollRectRectTransform = scrollRect.GetComponent<RectTransform>(); rectTransform = GetComponent<RectTransform>(); for(int i = 0;i < transform.childCount;i++) { if(transform.GetChild(i).gameObject.activeInHierarchy) { childRectTransformList.Add(transform.GetChild(i).GetComponent<RectTransform>()); } } scrollRect.onValueChanged.AddListener(UpdataChilds); UpdataChilds(Vector2.zero); } void UpdataChilds(Vector2 vector2) { ModifyMesh(new VertexHelper()); } public void ModifyMesh(VertexHelper vh) { if (!gameObject.activeInHierarchy || childRectTransformList.Count <= 0) { return; } foreach(var item in childRectTransformList) { float offset_left; float offset_right; Vector3 distanceVector = new Vector3(item.localPosition.x - scrollRectRectTransform.sizeDelta.x / 2 + rectTransform.anchoredPosition.x, item.localPosition.y, 0) - uiCenterPoint; //distanceVector.x小於0則證明當前節點在中心點右邊,設置的透視左右值需要反過來 if (distanceVector.x < 0) { offset_left = -perspective * distanceVector.x / 100f; offset_right = -perspective * distanceVector.x / 100f; } else { offset_left = -perspective * distanceVector.x / 100f; offset_right = -perspective * distanceVector.x / 100f; } Image[] images = item.GetComponentsInChildren<Image>(); Text[] texts = item.GetComponentsInChildren<Text>(); ModifyImagesInItem(offset_left, offset_right, images, item.sizeDelta.y); ModifyTextsInItem(offset_left, offset_right, texts, item.sizeDelta.y); } } public void ModifyImagesInItem(float offset_left, float offset_right, Image[] images, float itemHeight) { VertexHelper vh = new VertexHelper(); for (int i = 0; i < images.Length; i++) { Graphic graphic = images[i]; vh.Clear(); graphic.OnPopulateMesh_Public(vh); var vertexs = new List<UIVertex>(); vh.GetUIVertexStream(vertexs); UIVertex vt; float ratio0; float ratio1; float ratio2; float ratio3; float graphicPosY = Mathf.Abs(graphic.rectTransform.localPosition.y); vt = vertexs[0]; ratio0 = (Mathf.Abs(vt.position.y) + graphicPosY) / itemHeight; vt.position += new Vector3(offset_left * ratio0, 0, 0); vh.SetUIVertex(vt, 0); vt = vertexs[1]; ratio1 = (Mathf.Abs(vt.position.y) + graphicPosY) / itemHeight; vt.position += new Vector3(offset_left * ratio1, 0, 0); vh.SetUIVertex(vt, 1); vt = vertexs[2]; ratio2 = (Mathf.Abs(vt.position.y) + graphicPosY) / itemHeight; vt.position += new Vector3(offset_right * ratio2, 0, 0); vh.SetUIVertex(vt, 2); vt = vertexs[4]; ratio3 = (Mathf.Abs(vt.position.y) + graphicPosY) / itemHeight; vt.position += new Vector3(offset_right * ratio3, 0, 0); vh.SetUIVertex(vt, 3); Mesh mesh = new Mesh(); vh.FillMesh(mesh); graphic.canvasRenderer.SetMesh(mesh); MeshCollider meshCollider = graphic.GetComponent<MeshCollider>(); if(meshCollider != null) { meshCollider.sharedMesh = mesh; } } } public void ModifyTextsInItem(float offset_left, float offset_right, Text[] texts, float itemHeight) { VertexHelper vh = new VertexHelper(); for (int i = 0; i < texts.Length; i++) { Graphic graphic = texts[i]; vh.Clear(); graphic.OnPopulateMesh_Public(vh); var vertexs = new List<UIVertex>(); vh.GetUIVertexStream(vertexs); UIVertex vt; float ratio; float graphicPosY = Mathf.Abs(graphic.rectTransform.localPosition.y); int vert_index = 0; for (int j = 0; j < vertexs.Count; j++) { //剔除不必要的頂點 if((j - 3) % 6 == 0 || (j - 5) % 6 == 0) { continue; } if((j - 0) % 6 == 0 || (j - 1) % 6 == 0) { vt = vertexs[j]; ratio = (Mathf.Abs(vt.position.y) + graphicPosY) / itemHeight; vt.position += new Vector3(offset_left * ratio, 0, 0); vh.SetUIVertex(vt, vert_index); vert_index++; } if((j - 2) % 6 == 0 || (j - 4) % 6 == 0) { vt = vertexs[j]; ratio = (Mathf.Abs(vt.position.y) + graphicPosY) / itemHeight; vt.position += new Vector3(offset_right * ratio, 0, 0); vh.SetUIVertex(vt, vert_index); vert_index++; } } Mesh mesh = new Mesh(); vh.FillMesh(mesh); graphic.canvasRenderer.SetMesh(mesh); MeshCollider meshCollider = graphic.GetComponent<MeshCollider>(); if (meshCollider != null) { meshCollider.sharedMesh = mesh; } } } }
因為需要獲取到Image組件或Text組件的頂點輔助類VertexHelper,所以需要通過Graphic類的OnPopulateMesh函數來獲取VertexHelper類,但是OnPopulateMesh函數是受保護的函數,因為需要在Graphic類中添加一個公用的函數OnPopulateMesh_Public:
public void OnPopulateMesh_Public(VertexHelper toFill) { OnPopulateMesh(toFill); } protected virtual void OnPopulateMesh(VertexHelper vh) { var r = GetPixelAdjustedRect(); var v = new Vector4(r.x, r.y, r.x + r.width, r.y + r.height); Color32 color32 = color; vh.Clear(); vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(0f, 0f)); vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(0f, 1f)); vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(1f, 1f)); vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(1f, 0f)); vh.AddTriangle(0, 1, 2); vh.AddTriangle(2, 3, 0); }
4、問題
一、首先就是性能方面的消耗,因為滑動時是會一直有計算頂點的消耗的
二、還有就是初始化的問題,在Start函數中已經調用過一次修改頂點的函數瞭,但是在Unity編輯器模式下的實際效果並沒有顯示出來,需要手動滑一下才會顯示出正確的效果。即使在Start函數中調用兩次也是會出現這個問題
5、最終效果
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。