Unity實現(xiàn)透視滑動列表
本文實例為大家分享了Unity實現(xiàn)透視滑動列表的具體代碼,供大家參考,具體內(nèi)容如下
1、目的
有時候,為了實現(xiàn)更好的美術(shù)效果,需要實現(xiàn)一些特殊的滑動列表,例如軌跡滑動,也有透視滑動。
注意:本文里所展示的效果是未經(jīng)測試的試驗版,如果用于實際項目中,應(yīng)該還需要優(yōu)化代碼和測試性能
2、思考
透視滑動列表可以有兩種方式來實現(xiàn):
第一種方法是,通過shader實現(xiàn),其核心原理是,定義一個中心點坐標(biāo)(CenterX,CenterY),再定義一個透視系數(shù)_ OffsetPerspective,在vert函數(shù)中,對于每個頂點,計算其偏移值,距離自己定義的中心點越遠(yuǎn)的頂點,其偏移值越大,簡單概括就是距離中心點越遠(yuǎn)的頂點,就越把往中心點拉回去。實際算法就是:
OUT.worldPosition.x += (_CenterY + v.vertex.y - _OffsetX) / 1000 * (v.vertex.x - _CenterX) * _OffsetPerspective
這是我所使用的計算公式,其中,_OffsetX是自定義的偏移值,用于對所有頂點進(jìn)行整體偏移;/ 1000是偏移值的縮放倍數(shù),此值越小偏移程度越高。
注意:用這個方法做出來的透視滑動列表,主要的問題是一些交互控件的偏移問題,因為視覺效果偏移了,但是實際上可交互控件的位置沒有變,所以其點觸范圍是沒有透視效果時的范圍,這就會導(dǎo)致偏移的時候其點觸范圍不一樣,所以需要第二種方法。
第二種方法是使用純C#來實現(xiàn),其核心原理是,通過遍歷所有子節(jié)點的Image和Text節(jié)點,同樣使用第一種方法的思路修改其VertexHelper中頂點的偏移值,再將修改完的頂點設(shè)置回VertexHelper中,然后重新生成一個Mesh,并將Mesh設(shè)置為CanvasRenderer的Mesh。為了得到正確的點觸范圍,還需要在節(jié)點中添加MeshCollider組件,通過代碼設(shè)置MeshCollider組件的Mesh屬性,并將Image或Text節(jié)點的Raycast Target屬性取消勾選,這樣就能保證最正確的點觸范圍了。
然后,因為滑動列表是可以滑動的,所以還需要滑動的時候一直通過上述方法動態(tài)修改節(jié)點,這里使用滑動列表自帶的OnValueChange函數(shù)就行
注意:使用過多的MeshCollider對性能必定有消耗,不過Image和Text生成的Mesh都是比較簡單的,具體的性能消耗還是需要進(jìn)行測試才能得出結(jié)果
3、自定義實現(xiàn)軌跡滑動
核心原理已經(jīng)在上面說了,這里直接上代碼:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PerspectiveScrollRect : MonoBehaviour
{
/// <summary>
/// 中心點,中心點可以設(shè)置為不是原點
/// </summary>
public Vector3 uiCenterPoint = Vector3.zero;
/// <summary>
/// 圖片的回拉像素,每距離中心點100個像素往回拉的距離
/// </summary>
[Range(0,100)]
public float perspective = 0;
public ScrollRect scrollRect;
/// <summary>
/// 滑動列表節(jié)點
/// </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則證明當(dāng)前節(jié)點在中心點右邊,設(shè)置的透視左右值需要反過來
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函數(shù)來獲取VertexHelper類,但是OnPopulateMesh函數(shù)是受保護(hù)的函數(shù),因為需要在Graphic類中添加一個公用的函數(shù)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函數(shù)中已經(jīng)調(diào)用過一次修改頂點的函數(shù)了,但是在Unity編輯器模式下的實際效果并沒有顯示出來,需要手動滑一下才會顯示出正確的效果。即使在Start函數(shù)中調(diào)用兩次也是會出現(xiàn)這個問題
5、最終效果

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#創(chuàng)建Windows Service(Windows 服務(wù))的方法步驟
本文介紹了如何用C#創(chuàng)建、安裝、啟動、監(jiān)控、卸載簡單的Windows Service 的內(nèi)容步驟和注意事項,具有一定的參考價值,感興趣的可以了解一下2023-11-11
C#線性漸變畫刷LinearGradientBrush用法實例
這篇文章主要介紹了C#線性漸變畫刷LinearGradientBrush用法,實例分析了線性漸變畫刷LinearGradientBrush的相關(guān)使用技巧,需要的朋友可以參考下2015-06-06
C#基于正則表達(dá)式實現(xiàn)獲取網(wǎng)頁中所有信息的網(wǎng)頁抓取類實例
這篇文章主要介紹了C#基于正則表達(dá)式實現(xiàn)獲取網(wǎng)頁中所有信息的網(wǎng)頁抓取類,結(jié)合完整實例形式分析了C#正則網(wǎng)頁抓取類與使用技巧,需要的朋友可以參考下2017-05-05

