自我介紹
廣東雙非一本的大三小白,計科專業,想在製作畢設前夯實基礎,畢設做出一款屬於自己的遊戲!
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;
測試效果