自我介绍
广东双非一本的大三小白,计科专业,想在制作毕设前夯实基础,毕设做出一款属于自己的游戏!
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;
测试效果