UGUI 3D轮转图的两种实现方式

方法一:通过添加相机,添加画布,切换相机实现轮转效果。

缺点:耗费性能,不灵活

方法二:通过代码控制图片的位置和缩放比例来实现2D模拟3D轮转播放的效果

1.RotationDiagramItem.cs

using DG.Tweening;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using static RotationDiagram2D;

public class RotationDiagramItem : MonoBehaviour,IDragHandler,IEndDragHandler
{
    public int posID;
    //_image 是一个私有字段,存储实际的 Image 组件。
    private Image _image;
    //image 是一个属性,用于访问 _image,并确保在访问之前 _image 已被初始化。
    private Image image
    {
        get
        {
            if (_image == null)
            {
                _image = GetComponent<Image>();
            }
            return _image;
        }
    }

    public float dragOffSetX;
    //Action<float> 是一个内置的委托类型,表示可以指向一个接收一个 float 参数并且不返回任何值的方法。
    private Action<float> moveAction;
    private float aniTime = 1;
    private RectTransform _rect;
    private RectTransform rect
    {
        get
        {
            if (_rect == null)
            {
                _rect = GetComponent<RectTransform>();
            }
            return _rect;
        }
    }

    public void SetParent(Transform parent)
    {
        transform.SetParent(parent);
    }

    public void SetSprite(Sprite sprite)
    {
        //通过属性获得sprite
        image.sprite = sprite;
        
    }

    public void SetPosData(ItemPosData data)
    {
        rect.DOAnchorPos(Vector2.right * data.X, aniTime);
        rect.DOScale(Vector3.one * data.scaletimes, aniTime);
        //这里设置一个协程的原因是,我们先设置了前面的层级,生成后面的层级的时候,可能会比前面高,造成前后图形的层级穿插
        StartCoroutine(Wait(data));
    }

    private IEnumerator Wait(ItemPosData data)
    {
        yield return new WaitForSeconds(aniTime*0.5f);
        rect.SetSiblingIndex(data.order);
    }

    public void OnDrag(PointerEventData eventData)
    {
        //eventData.delta=与上次事件相比该鼠标的相对移动。
        dragOffSetX += eventData.delta.x;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        //该方法中调用了 moveAction(dragOffSetX),这将触发 Change 方法,并传递当前的 dragOffSetX 作为参数 offSetX。
        //在 OnEndDrag 方法中,当 moveAction(dragOffSetX); 被调用时,实际上是在调用 Change(dragOffSetX);
        moveAction(dragOffSetX);
        dragOffSetX = 0;
    }

    //AddMoveListener 是一个公共方法,接受一个 Action<float> 类型的参数 onmove。这个方法的功能是将传入的 onmove 方法赋值给 moveAction。
    //通过这个方法,外部代码可以注册自己的方法来处理移动事件。
    public void AddMoveListener(Action<float> onmove)
    {
        //Action<float> moveAction是一个内置的委托类型,表示可以指向一个接收一个 float 参数并且不返回任何值的方法。
        //任何接收 float 类型参数并返回 void 的方法都可以赋给 moveAction
        moveAction = onmove;
    }

    //改变图片的位置posid
    public void ChangeId(int totalItemNum,int symbol)
    {
        int id = posID;
        id += symbol;
        if (id<0)
        {
            id += totalItemNum;
        }
        posID = id % totalItemNum;
    }
}

2.RotationDiagram2D.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class RotationDiagram2D : MonoBehaviour
{
    public Sprite[] itemSprites;
    //此 RectTransform 相对于锚点之间距离的大小。
    //如果锚点在一起,则 sizeDelta 与大小相同。如果锚点处于父项四个角的各个角中,则 sizeDelta 是矩形与其父项相比更大或更小的程度。
    public Vector2 itemSize;
    //存储items图片的列表,存图片
    private List<RotationDiagramItem> itemList;
    //存储轮转图结构体数据的列表,存图片位置数据
    private List<ItemPosData> posDataList;
    //辅助列表
    private List<ItemData> itemDataList;
    //缩放比例
    public float ScaleTimesMin;
    public float ScaleTimesMax;
    //图片之间的间隔
    public float offset;
    
    private void Start()
    {
        itemList = new List<RotationDiagramItem>();
        posDataList = new List<ItemPosData>();
        itemDataList = new List<ItemData>();
        CreateItem();
        Calculate();
        SetItemData();
    }

    //创建模板
    public GameObject CreateTemplate()
    {
        GameObject item = new GameObject("Template");
        //AddComponent 用于在运行时向一个 GameObject 添加一个新的组件。这通常用于需要动态添加功能的场景,例如当角色获得一个新的能力时。
        //GetComponent 用于获取一个已经存在于 GameObject 上的组件。如果该组件不存在,返回值将为 null。这个方法通常用于访问和操作游戏对象
        item.AddComponent<RectTransform>().sizeDelta = itemSize;
        item.AddComponent<Image>();
        item.AddComponent<RotationDiagramItem>();
        return item;
    }

    //创建物体过程:resource-prefab-实例化-gameobj
    public void CreateItem()
    {
        GameObject template = CreateTemplate();
        RotationDiagramItem itemTemp = null;
        foreach (Sprite sprite in itemSprites)
        {
            //生成物体,把template实例化,并未获得组件
            itemTemp = Instantiate(template).GetComponent<RotationDiagramItem>();

            itemTemp.SetParent(transform);
            itemTemp.SetSprite(sprite);
            //这个方法允许你添加一个监听器(如 Change 方法),该监听器会在调用 moveAction 时被触发。
            //itemTemp.AddMoveListener(Change); 这一行将 Change 方法注册到 moveAction 委托中。此时,moveAction 被设置为指向 Change 方法。
            itemTemp.AddMoveListener(Change);
            itemList.Add(itemTemp);
        }
        Destroy(template);
    }

    //offSetX的正负代表了图片移动的方向,正的代表向x正方向移动,负的代表向x的负方向移动
    //这是一个监听器,该监听器会在调用 moveAction 时被触发。
    //Change 方法接收一个 float 类型的参数offSetX。这个参数就是传递给它的 dragOffSetX 值。
    private void Change(float dragOffSetX)
    {
        //假设 dragOffSetX 的值为 5.0f,当用户结束拖动时:OnEndDrag 被调用。
        //moveAction(5.0f); 实际上调用了 Change(5.0f);。在 Change 方法中,offSetX 将等于 5.0f。
        int symbol = dragOffSetX > 0 ? 1 : -1;
        Change(symbol);
    }
    //重载指的是在同一个类中,允许定义多个同名但参数不同(参数类型、数量或顺序不同)的方法。这些方法可以执行不同的操作,具体取决于传入的参数
    //重写指的是在派生类中重新定义从基类继承的方法。通过重写,派生类可以提供对基类方法的特定实现
    private void Change(int symbol)
    {
        foreach (RotationDiagramItem item in itemList)
        {
            
            item.ChangeId(itemList.Count, symbol);
        }

        for (int i = 0; i < itemList.Count; i++)
        {
            //把posdatalist的数据按照itemlist的posid的顺序给到setposdata,修改好数据,并且更换显示顺序,给到itemlist进行显示
            //这里为什么不需要再次进行排序呢,因为主函数里面已经执行过了排序函数,把排序号的itemlist的posid与posdatalist的id对应起来,
            //并且把order顺序给到了posdatalistt,只需要调用setposdata对posdatalist按照order顺序进行排序就好了
            //说白了,其实itemlist就是一个辅助的列表,这个辅助列表通过scaletimes来把混乱的posdatalist正确赋予order顺序
            //posDataList[itemList[i].posID]这个的作用就是取posdatalist的一个元素,这个元素的下标和itemlist的posid一样,我认为posdatalist的下标就是posid,因为还没有排序
            itemList[i].SetPosData(posDataList[itemList[i].posID]);
        }
    }


    //存储轮转图的位置和缩放,结构体
    public class ItemPosData
    {
        public float X;
        public float scaletimes;
        public int order;
    }

    public struct ItemData
    {
        public int posID;
        public int orderID;
    }

    //获取图片的x坐标,radio是比例,length是椭圆总长度,radio是在椭圆上的范围0-1
    public float GetX(float radio, float length)
    {
        if (radio > 1 || radio < 0)
        {
            Debug.LogError("比例必须在0-1");
            return 0;
        }

        if (radio >= 0 && radio < 0.25)
        {
            return length * radio;
        }
        else if (radio >= 0.25 && radio < 0.75)
        {
            return length * (0.5f - radio);
        }
        else
        {
            return length * (radio - 1f);
        }

    }

    //计算对应radio上的对应的缩放比例
    public float GetScaleTimes(float radio, float max, float min)
    {
        if (radio > 1 || radio < 0)
        {
            Debug.LogError("比例必须在0-1");
            return 0;
        }
        float scaleoffset = (max - min) / 0.5f;
        if (radio < 0.5)
        {
            return max - scaleoffset * radio;
        }
        else
        {
            return max - scaleoffset * (1 - radio);
        }
    }

    //计算椭圆总长度
    public void Calculate()
    {
        float length = (offset + itemSize.x) * itemList.Count;
        //平均分配图片的位置,每个图片应该的比例位置
        float radioOffset = 1 / (float)itemList.Count;
        float radio = 0;
        for (int i = 0; i < itemList.Count; i++)
        {
            //记录位置顺序
            ItemData itemData = new ItemData();
            itemData.posID = i;
            itemDataList.Add(itemData);

            itemList[i].posID = i;
            ItemPosData data = new ItemPosData();
            data.X = GetX(radio, length);
            data.scaletimes = GetScaleTimes(radio, ScaleTimesMax, ScaleTimesMin);
            radio += radioOffset;
            posDataList.Add(data);
        }
        //对itemdatalist进行排序,这里的u是一个引用,代表着itemdatalist里面的每一个元素,
        //这里的u.posID其实就是下标,因为u代表着每一个元素,所以posDatas[u.posID]代表着和itemdatalist表中有一样posID的元素
        //如果这里没有u.posID,那么就需要通过for循环来遍历所有的posDatas中的元素,说白了就是一个下标的作用
        itemDataList = itemDataList.OrderBy(u => posDataList[u.posID].scaletimes).ToList();

        for (int i = 0; i < itemDataList.Count; i++)
        {
            ///在 C# 中,结构体(struct)是值类型,当你通过索引访问结构体的成员时,你会得到一个结构体的副本。
            ///因为结构体是值类型的特性,当你试图修改这个副本的属性时,实际上你并没有改变原始结构体的内容,
            ///而是修改了一个临时的副本。因此,直接对结构体列表中的某个元素的属性进行赋值会导致编译错误。
            ///解决方法就是把ItemPosData结构体类型改成类
            posDataList[itemDataList[i].posID].order = i;
        }
    }
    public void SetItemData()
    {
        for (int i = 0; i < posDataList.Count; i++)
        {
            itemList[i].SetPosData(posDataList[i]);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值