Unity實現仿3D輪轉圖效果

本文實例為大傢分享瞭Unity實現仿3D輪轉圖效果的具體代碼,供大傢參考,具體內容如下

一、效果演示

二、實現思路

——獲取位置:可以將每個item的運行軌跡看作一個橢圓,分為四段進行觀察,四個黑點視為四個item,觀察四個黑點的位置,比例值為0.125和0.375的位置相同,比例值為0.625和0.875的位置相同,比例值為0.375和0.625的位置相反,可得結論
[0,0.25]:軌跡總長度*當前比例值
(0.25,0.5]:軌跡總長度 * (0.5 – 當前比例值)
(0.5,0.75]:軌跡總長度 * (0.5 – 當前比例值)
(0.75,1]:軌跡總長度 * (當前比例值 – 1)

——獲取縮放值:可以將每個item的運行軌跡看作一個橢圓,分為四段進行觀察,四個黑點視為四個item,觀察四個黑點的位置,比例值為0時縮放值應為最大,比例值為0.5時縮放值應為最小,可得結論
[0-0.5]:縮放最大值 – 比例值 * (縮放最大值 – 縮放最小值) * 2
(0.5-1]:縮放最大值 – (1 – 比例值) * (縮放最大值 – 縮放最小值) * 2

——獲取層級:使用UGUI的自然層級進行排序(越靠下越後渲染),拷貝一份列表item數據列表按照縮放值從小到大的順序排序,再通過SetSiblingIndex依次設置層級

三、使用

——常規使用
SetData:傳入item預制體和列表中item個數
OnSetItem:綁定設置item的方法
SetList:設置列表的顯示

using UnityEngine;
using UnityEngine.UI;
 
public class Test : MonoBehaviour
{
    public GameObject prefab;
 
    public Rotary3DList rotary3DList;
 
    private void Start()
    {
        rotary3DList.SetData(prefab, 5);
        rotary3DList.OnSetItem = SetItem;
        rotary3DList.SetList();
    }
 
    void SetItem(Rotary3DList.ListItemData listItemData)
    {
        listItemData.go.GetComponent<Text>().txt = listItemData.index.ToString();
    }
}

——MoveToIndex:移動到某一個下標位置,isScroll表示是否滑動到指定位置
——GetListItemData:獲取到某個下標的item數據
——CenterIndex:當前中心點item下標

四、代碼實現

using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Linq;
 
/// <summary>
/// 仿3D輪轉圖組件
/// </summary>
[AddComponentMenu("LFramework/UI/Rotary3DList", 50)]
[RequireComponent(typeof(Image))]
public class Rotary3DList : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    /// <summary>
    /// 列表item數據
    /// </summary>
    public class ListItemData
    {
        public int index;
        public GameObject go;
        public float targetValue;//目標位置長度值
        public float tempValue;//臨時位置長度值(每次拖拽結束後才更新數值)
    }
 
    /// <summary>
    /// 輪轉類型
    /// </summary>
    public enum RotaryType
    {
        Horizontal,
        Vertical,
    }
 
    public RotaryType rotaryType;//輪轉類型
    public float spacing;//間隔
    public float maxScale = 1;//最大縮放值
    public float minScale = 0.5f;//最小縮放值
    public float t = 0.1f;//緩動插值
    public Action<ListItemData> OnSetItem;//設置item
    public Action<PointerEventData> OnDragBegin;//拖拽開始
    public Action<PointerEventData> OnDragging;//拖拽中
    public Action<PointerEventData> OnDragEnd;//拖拽結束
 
    //中心item下標
    public int CenterIndex
    {
        get { return GetCenterItemIndex(); }
    }
 
    int m_ItemCount;//列表item總數量
    float m_TotalValue;//總長度值
    float m_DeltaValue;//長度值增量
    GameObject m_Prefab;
    RectTransform m_ItemContainer;
    List<ListItemData> m_ListItemDataList = new List<ListItemData>();
 
    bool m_InDrag;
    float m_BeginPos;
    List<float> m_InitValueList = new List<float>();//初始每個item的位置長度值
 
    /// <summary>
    /// 設置數據
    /// </summary>
    public void SetData(GameObject prefab, int itemCount)
    {
        m_ItemContainer = GetComponent<RectTransform>();
        m_ItemCount = itemCount;
        m_Prefab = prefab;
        m_DeltaValue = rotaryType == RotaryType.Horizontal
            ? (spacing + m_Prefab.GetComponent<RectTransform>().rect.width)
            : (spacing + m_Prefab.GetComponent<RectTransform>().rect.height);
        m_TotalValue = m_DeltaValue * m_ItemCount;
 
        InitData();
    }
 
    /// <summary>
    /// 初始化數據
    /// </summary>
    void InitData()
    {
        float tempValue = 0;
        for (int i = 0; i < m_ItemCount; i++)
        {
            ListItemData data = new ListItemData();
            data.index = i;
            data.go = Instantiate(m_Prefab, transform, m_ItemContainer);
            data.targetValue = tempValue;
            data.tempValue = tempValue;
            m_ListItemDataList.Add(data);
            m_InitValueList.Add(tempValue);
 
            tempValue += m_DeltaValue;
        }
        m_InitValueList.Add(m_TotalValue);
    }
 
    /// <summary>
    /// 設置列表
    /// </summary>
    public void SetList()
    {
        foreach (var data in m_ListItemDataList)
        {
            OnSetItem?.Invoke(data);
        }
 
        UpdateItem(true);
    }
 
    /// <summary>
    /// 移動到某個下標位置
    /// </summary>
    public void MoveToIndex(int index, bool isScroll = true)
    {
        if (index < 0
            || index >= m_ItemCount)
        {
            Debug.LogError("下標超出范圍,index : " + index);
            return;
        }
 
        int indexOffset = CenterIndex - index;
        foreach (var data in m_ListItemDataList)
        {
            float tempValue = data.tempValue + m_DeltaValue * indexOffset < 0
                ? data.tempValue + m_DeltaValue * indexOffset + m_TotalValue
                : data.tempValue + m_DeltaValue * indexOffset;
            float targetValue = tempValue % m_TotalValue;
            data.targetValue = targetValue;
            data.tempValue = targetValue;
        }
 
        UpdateItem(!isScroll);
    }
 
    public void OnBeginDrag(PointerEventData eventData)
    {
        m_InDrag = true;
        m_BeginPos = rotaryType == RotaryType.Horizontal
            ? eventData.position.x
            : eventData.position.y;
        OnDragBegin?.Invoke(eventData);
    }
 
    public void OnDrag(PointerEventData eventData)
    {
        OnDragging?.Invoke(eventData);
 
        float endPos = rotaryType == RotaryType.Horizontal
            ? eventData.position.x
            : eventData.position.y;
        float offset = endPos - m_BeginPos;
 
        //計算item數據
        if (offset > 0)
        {
            foreach (var data in m_ListItemDataList)
            {
                float tempValue = (data.tempValue + offset) % m_TotalValue;
                data.targetValue = tempValue;
            }
        }
        else if (offset < 0)
        {
            foreach (var data in m_ListItemDataList)
            {
                float tempValue = data.tempValue + offset < 0
                    ? m_TotalValue - Mathf.Abs(data.tempValue + offset) % m_TotalValue
                    : (data.tempValue + offset) % m_TotalValue;
                data.targetValue = tempValue;
            }
        }
    }
 
    public void OnEndDrag(PointerEventData eventData)
    {
        m_InDrag = false;
        OnDragEnd?.Invoke(eventData);
 
        foreach (var data in m_ListItemDataList)
        {
            float nearlyValue = GetNearlyValue(data.targetValue);
            data.targetValue = nearlyValue;
            data.tempValue = nearlyValue;
        }
    }
 
    private void Update()
    {
        UpdateItem(false);
    }
 
    /// <summary>
    /// 更新item
    /// </summary>
    void UpdateItem(bool isForce)
    {
        //拖拽中-實時更新
        if (m_InDrag)
        {
            foreach (var data in m_ListItemDataList)
            {
                float ratio = data.targetValue / m_TotalValue;
 
                //更新位置
                float pos = GetPos(ratio);
                Vector2 targetPos = rotaryType == RotaryType.Horizontal
                    ? new Vector2(pos, 0)
                    : new Vector2(0, pos);
                data.go.transform.localPosition = targetPos;
 
                //更新縮放值
                float scale = GetScale(ratio);
                Vector2 targetScale = Vector3.one * scale;
                data.go.transform.localScale = targetScale;
            }
        }
        //非拖拽中-緩動更新
        else
        {
            foreach (var data in m_ListItemDataList)
            {
                float ratio = data.targetValue / m_TotalValue;
 
                //更新位置
                float pos = GetPos(ratio);
                Vector2 targetPos = rotaryType == RotaryType.Horizontal
                    ? new Vector2(pos, 0)
                    : new Vector2(0, pos);
                float targetPosOffset = rotaryType == RotaryType.Horizontal
                    ? data.go.transform.localPosition.x - targetPos.x
                    : data.go.transform.localPosition.y - targetPos.y;
                data.go.transform.localPosition = Vector2.Lerp(data.go.transform.localPosition, targetPos, isForce ? 1 : t);
                if (Mathf.Abs(targetPosOffset) <= 0.01f)
                {
                    data.go.transform.localPosition = targetPos;
                }
 
                //更新縮放值
                float scale = GetScale(ratio);
                Vector2 targetScale = Vector3.one * scale;
                float targetScaleOffset = data.go.transform.localScale.x - targetScale.x;
                data.go.transform.localScale = Vector2.Lerp(data.go.transform.localScale, targetScale, isForce ? 1 : t);
                if (Mathf.Abs(targetScaleOffset) <= 0.01f)
                {
                    data.go.transform.localScale = targetScale;
                }
            }
        }
 
        //更新層級
        var listItemDataList = m_ListItemDataList.OrderBy(data => GetScale(data.targetValue / m_TotalValue)).ToList();
        for (int i = 0; i < m_ItemCount; i++)
        {
            listItemDataList[i].go.transform.SetSiblingIndex(i);
        }
    }
 
    /// <summary>
    /// 得到位置
    /// </summary>
    float GetPos(float ratio)
    {
        if (ratio < 0
            && ratio > 1)
        {
            Debug.LogError("比例值錯誤,比例值必須為[0-1],ratio : " + ratio);
            return 0;
        }
 
        if (ratio >= 0 && ratio <= 0.25f)
        {
            return m_TotalValue * ratio;
        }
        else if (ratio > 0.25f && ratio <= 0.75f)
        {
            return m_TotalValue * (0.5f - ratio);
        }
        else
        {
            return m_TotalValue * (ratio - 1);
        }
    }
 
    /// <summary>
    /// 得到縮放值
    /// </summary>
    float GetScale(float ratio)
    {
        if (ratio < 0
               && ratio > 1)
        {
            Debug.LogError("比例值錯誤,比例值必須為[0-1],ratio : " + ratio);
            return 0;
        }
 
        float v = (maxScale - minScale) * 2;
        if (ratio >= 0 && ratio <= 0.5f)
        {
            return maxScale - ratio * v;
        }
        else
        {
            return maxScale - (1 - ratio) * v;
        }
    }
 
    /// <summary>
    /// 得到距離最近的位置長度值
    /// </summary>
    float GetNearlyValue(float curValue)
    {
        float minDis = Mathf.Abs(curValue - m_InitValueList.First());
        float nearlyValue = m_InitValueList.First();
        foreach (var value in m_InitValueList)
        {
            float tempDis = Mathf.Abs(curValue - value);
            if (tempDis < minDis)
            {
                minDis = tempDis;
                nearlyValue = value == m_TotalValue
                    ? m_InitValueList.First()
                    : value;
            }
        }
        return nearlyValue;
    }
 
    /// <summary>
    /// 得到中心item的下標
    /// </summary>
    int GetCenterItemIndex()
    {
        int index = 0;
        float minDis = Mathf.Min(Mathf.Abs(m_ListItemDataList[0].targetValue - m_InitValueList.First()), Mathf.Abs(m_ListItemDataList[0].targetValue - m_InitValueList.Last()));
        foreach (var data in m_ListItemDataList)
        {
            float tempDis = Mathf.Min(Mathf.Abs(data.targetValue - m_InitValueList.First()), Mathf.Abs(data.targetValue - m_InitValueList.Last()));
            if (tempDis < minDis)
            {
                minDis = tempDis;
                index = data.index;
            }
        }
        return index;
    }
 
    /// <summary>
    /// 得到列表item數據
    /// </summary>
    public ListItemData GetListItemData(int index)
    {
        if (index < 0
           || index >= m_ItemCount)
        {
            Debug.LogError("下標超出范圍,index : " + index);
            return null;
        }
 
        return m_ListItemDataList[index];
    }
}

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: