【U3D/UGUI】3.2DImage製作仿3D輪轉圖

自我介紹

廣東雙非一本的大三小白,計科專業,想在製作畢設前夯實基礎,畢設做出一款屬於自己的遊戲!

2DImage製作仿3D輪轉圖

這一章其實不難,涉及的數學知識也非常簡單,但是實現過程中有很多收穫!

主要的還是兩個腳本:

  • RotationDiagram2D.cs 這是需要掛載到一個物體上的
  • RotationDiagramItem.cs 並不需要掛載到任何物體上,由 RotationDiagram2D 組件自動生成gameobject對象並掛載

RotationDiagram2D.cs

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

public class RotationDiagram2D : MonoBehaviour
{
    public Vector2 ItemSize;
    public Sprite[] ItemSprites;
    public float Offset;
    public float ScaleTimeMin;
    public float ScaleTimeMax;

    List<RotationDiagramItem> _items = new List<RotationDiagramItem>();
    List<ItemPosData> _posData = new List<ItemPosData>();

    private void Start()
    {
        CreateItem();
        CaculateData();
        SetItemData();
    }

    private GameObject CreateTemplate()
    {
        GameObject item = new GameObject("Template");
        item.AddComponent<RectTransform>().sizeDelta = ItemSize;
        item.AddComponent<Image>();
        item.AddComponent<RotationDiagramItem>();
        return item;
    }

    private void CreateItem()
    {
        GameObject template = CreateTemplate();
        RotationDiagramItem itemTemp = null;
        foreach (Sprite sprite in ItemSprites)
        {
            itemTemp = Instantiate(template).GetComponent<RotationDiagramItem>();
            itemTemp.SetParent(this.transform);
            itemTemp.SetSprite(sprite);
            itemTemp.AddMoveListener(Change);
            _items.Add(itemTemp);
        }

        Destroy(template);
    }

    private void Change(float offsetX)
    {
        int symbol = offsetX > 0 ? 1 : -1;
        Change(symbol);
    }

    private void Change(int symbol)
    {
        foreach (RotationDiagramItem item in _items)
        {
            item.ChangeId(symbol, _items.Count);
        }

        for (int i = 0; i < _posData.Count; i++)
        {
            _items[i].SetPosData(_posData[_items[i].PosId]);
        }
    }

    private void CaculateData()
    {
        List<ItemData> itemDatas = new List<ItemData>();

        float length = (ItemSize.x + Offset) * _items.Count;
        float radioOffset = 1 / (float)_items.Count;

        float radio = 0;
        for (int i = 0; i < _items.Count; i++)
        {
            ItemData itemData = new ItemData();
            itemData.PosId = i;
            itemDatas.Add(itemData);

            _items[i].PosId = i;

            ItemPosData data = new ItemPosData();
            data.X = GetX(radio, length);
            data.ScaleTimes = GetScaleTimes(radio, ScaleTimeMax, ScaleTimeMin);

            radio += radioOffset;
            _posData.Add(data);
        }

        itemDatas = itemDatas.OrderBy(u => _posData[u.PosId].ScaleTimes).ToList();

        for (int i = 0; i < itemDatas.Count; i++)
        {
            _posData[itemDatas[i].PosId].Order = i;
        }
    }

    private void SetItemData()
    {
        for (int i = 0; i < _posData.Count; i++)
        {
            _items[i].SetPosData(_posData[i]);
        }
    }

    private float GetX(float radio, float length)
    {
        if (radio > 1 || radio < 0) throw new System.Exception("當前比例必須是[0,1]");
        if (radio >= 0 && radio < 0.25f)
        {
            return length * radio;
        }
        else if (radio >= 0.25f && radio < 0.75f)
        {
            return length * (0.5f - radio);
        }
        else
        {
            return length * (radio - 1);
        }
    }

    public float GetScaleTimes(float radio, float max, float min)
    {
        if (radio > 1 || radio < 0) throw new System.Exception("當前比例必須是[0,1]");

        float scaleOffset = (max - min) / 0.5f;
        if (radio < 0.5f)
        {
            return max - scaleOffset * radio;
        }
        else
        {
            return max - scaleOffset * (1 - radio);
        }
    }

}

public class ItemPosData
{
    public float X;
    public float ScaleTimes;
    public int Order;
}

public struct ItemData
{
    public int PosId;
    public int OrderId;
}

RotationDiagramItem.cs

using DG.Tweening;
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class RotationDiagramItem : MonoBehaviour, IDragHandler, IEndDragHandler
{
    public int PosId;

    private Action<float> _moveAction;
    private float _offsetX;
    private float _aniTime = 1;
    private Image _image;
    public Image Image
    {
        get
        {
            if (_image == null) _image = GetComponent<Image>();
            return _image;
        }
    }

    private RectTransform _rect;
    public RectTransform Rect
    {
        get
        {
            if (_rect == null) _rect = GetComponent<RectTransform>();
            return _rect;
        }
    }

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

    public void SetSprite(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);
        transform.SetSiblingIndex(data.Order);
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        _moveAction?.Invoke(_offsetX);
        _offsetX = 0;
    }

    public void OnDrag(PointerEventData eventData)
    {
        _offsetX += eventData.delta.x;
    }

    public void AddMoveListener(Action<float> onMove)
    {
        _moveAction += onMove;
    }

    public void ChangeId(int symbol, int totalItemNum)
    {
        int id = this.PosId;
        id += symbol;
        if (id < 0)
        {
            id += totalItemNum;
        }
        this.PosId = id % totalItemNum;
    }
}

筆記:

  • 解決層級關係:
    • 利用 transform.SetSiblingIndex(data.Order); 來修改父物體下子物體的位置
    • 排序(獲取父物體下子物體的order)則用到 itemDatas.OrderBy(u => _posData[u.PosId].ScaleTimes).ToList();
  • 本來 ItemPosData 是用struct的,但是用結構體的話 _posData[index].Order = i; 會報錯,所以後面改成了class,原因:
    • 具體原因參考:https://www.cnblogs.com/timeObjserver/p/5931299.html
    • 簡單來說:因爲結構體是值類型(存儲在棧上),List[]內部調用的是 public T this[int index] { get; set; } (get方法的調用是使用棧來進行的)返回的是值的臨時拷貝,所以修改(= i)的內容也僅僅只是在臨時拷貝里進行修改,並無任何意義,編譯器自動報錯

如果想要ItemPosData保持struct,也不是沒有辦法

//_posData[itemDatas[i].PosId].Order = i;       //報錯

var tmp = _posData[itemDatas[i].PosId];
tmp.Order = i;
_posData[itemDatas[i].PosId] = tmp;

測試效果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章