昨天我们一起学习了3D轮转图的绘制,相信大家都有所收获。除了3D轮转图,我们在项目开发得分过程中,用到的更多的是2D轮转图。2D轮转图可以用更少的性能消耗来展现出基本相同的效果,更加收到开发人员的青睐。
2D轮转图与3D轮转图的绘制原理基本是一致的,只不过3D轮转图用的预制体是3D物体,它的效果也是在3D世界中展示出来的。2D轮转图则将其转化为了UI形式的。其中只是发生了一下小的变化,下面我们来一起看一下。
实现思路:
首先,需要准备可以用于制作轮转图的图片,并将其制作成预制体。在接下来的开发过程中,我们会通过一个自己定义的预制体数组来管理这些预制体生成的轮转图的最基本单位。
然后,需要计算出轮转的圆心、半径和图片之间的夹角。将所有图片按照圆弧排列,同时通过计算每个图片的位置和比例实现轮转效果。通过列表管理每个图片,并按照缩放比例排序。
接下来,监听拖拽行为,计算拖拽的距离并移动图片。拖拽结束后,通过动画的方式实现轮转到指定位置的效果。可以通过计算需要旋转的图片数量,选择旋转的方向并计算出旋转的距离和时间,最终以动画的方式完成轮转效果。
在实现这个功能过程中,使用DOTween插件可以大大简化动画效果的处理,提高了开发效率。同时,通过简单的数学计算,实现了轮转效果。如果我们没法使用DOTween插件的话可以参考一下昨天文章里的手写DOTween,也可以实现相同的效果。
下面是代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class Cyclogram2D : MonoBehaviour,IDragHandler,IEndDragHandler
{
public int num = 14; //数量
public Image prefab; //预制体
public float space = 10;//间距
public float max = 1;
public float min = 0.5f;
public float dec = 10;//减速度
float c; //周长
float r; //半径
float ang; //每个角的弧度
float allang = 0; //移动的总弧度
List<GameObject> list = new List<GameObject>();
List<Transform> sortList = new List<Transform>();
DT dt;
DT dt0;
// Start is called before the first frame update
void Start()
{
c = num * (prefab.rectTransform.sizeDelta.x + space);
r = c / (2 * Mathf.PI);
ang = 2 * Mathf.PI / num;
Move();
}
/// <summary>
/// 移动
/// </summary>
public void Move()
{
for (int i = 0; i < num; i++)
{
if (list.Count<=i)
{
list.Add(Instantiate(prefab.gameObject, transform));
sortList.Add(list[i].transform);
list[i].GetComponentInChildren<TextMeshProUGUI>().text = i.ToString();
}
float x = Mathf.Sin(i * ang + allang) * r;
float z = Mathf.Cos(i * ang + allang) * r;
float p = (z + r) / (r + r);
float scale = max * (1 - p) + min * p;
list[i].transform.localPosition = new Vector3(x, 0, 0);
list[i].transform.localScale = Vector3.one * scale;
}
Sort();
}
/// <summary>
/// 排序
/// </summary>
public void Sort()
{
sortList.Sort((a, b) =>
{
if (a.localScale.z < b.localScale.z)
{
return -1;
}
else if (a.localScale.z == b.localScale.z)
{
return 0;
}
else
{
return 1;
}
});
for (int i = 0; i < sortList.Count; i++)
{
sortList[i].SetSiblingIndex(i);
}
}
public void OnDrag(PointerEventData eventData)
{
if (dt!=null)
{
Destroy(dt.gameObject);
}
if (dt0!=null)
{
Destroy(dt0.gameObject);
}
allang -= eventData.delta.x / r;
Move();
}
/// <summary>
/// 结束拖拽时惯性和对齐都一起写了
/// </summary>
/// <param name="eventData"></param>
public void OnEndDrag(PointerEventData eventData)
{
float dis = eventData.delta.x;
float time = Mathf.Abs(dis / dec);
dt= DT.To((a) =>
{
allang -= a / r;
Move();
},dis,0,time).OnComlete(()=>
{
float moveang = Mathf.Asin(sortList[num - 1].localPosition.x / r);
float movetime = Mathf.Abs(moveang * r / dec);
dt0 = DT.To((b) =>
{
allang = b;
Move();
},allang,allang+moveang,movetime).OnComlete(()=>
{
});
});
}
}
下面这一段代码是在上面的基础上,新增了一个在输入框中输入数字,轮转图可以自己转到对应的位置的功能。同样的,它也有移动,惯性对齐等
实现思路:
在上面的基础上,我们求出我们要去的位置,然后再得到我们现在要去的位置,通过DoTween来实现平滑移动过去。在这个过程中,可能会出现一个问题,当我们输入一个数字的时候,可能轮转图自己知道最终位置是哪里,但是不知道到底是该顺时针转过去,还是该逆时针转过去。这个时候我们就该求最短路径了。
求最短路径的方法:
我们首先得到轮转图默认的长度和方向,然后求出反方向的长度和反方向的方向,最后,我们对两者进行比较,的到最短的那条路线,让我们的轮转图走那条路线 过去。
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using TMPro;
public class Cyclogram2D : MonoBehaviour,IDragHandler,IEndDragHandler
{
public TMP_InputField inputField;
public int num = 14; //数量
public Image prefab; //预制体
public float space = 10;//间距
public float max = 1;
public float min = 0.5f;
public float dec = 10;//减速度
float c; //周长
float r; //半径
float ang; //每个角的弧度
float allang = 0; //移动的总弧度
List<GameObject> list = new List<GameObject>();
List<Transform> sortList = new List<Transform>();
DT dt;
DT dt0;
// Start is called before the first frame update
void Start()
{
c = num * (prefab.rectTransform.sizeDelta.x + space);
r = c / (2 * Mathf.PI);
ang = 2 * Mathf.PI / num;
Move();
}
/// <summary>
/// 移动
/// </summary>
public void Move()
{
for (int i = 0; i < num; i++)
{
if (list.Count<=i)
{
list.Add(Instantiate(prefab.gameObject, transform));
sortList.Add(list[i].transform);
list[i].GetComponentInChildren<TextMeshProUGUI>().text = i.ToString();
}
float x = Mathf.Sin(i * ang + allang) * r;
float z = Mathf.Cos(i * ang + allang) * r;
float p = (z + r) / (r + r);
float scale = max * (1 - p) + min * p;
list[i].transform.localPosition = new Vector3(x, 0, 0);
list[i].transform.localScale = Vector3.one * scale;
}
Sort();
}
/// <summary>
/// 排序
/// </summary>
public void Sort()
{
sortList.Sort((a, b) =>
{
if (a.localScale.z < b.localScale.z)
{
return -1;
}
else if (a.localScale.z == b.localScale.z)
{
return 0;
}
else
{
return 1;
}
});
for (int i = 0; i < sortList.Count; i++)
{
sortList[i].SetSiblingIndex(i);
}
}
public void OnDrag(PointerEventData eventData)
{
if (dt!=null)
{
Destroy(dt.gameObject);
}
if (dt0!=null)
{
Destroy(dt0.gameObject);
}
allang -= eventData.delta.x / r;
Move();
}
/// <summary>
/// 结束拖拽时惯性和对齐都一起写了
/// </summary>
/// <param name="eventData"></param>
public void OnEndDrag(PointerEventData eventData)
{
float dis = eventData.delta.x;
float time = Mathf.Abs(dis / dec);
dt= DT.To((a) =>
{
allang -= a / r;
Move();
},dis,0,time).OnComlete(()=>
{
float moveang = Mathf.Asin(sortList[num - 1].localPosition.x / r);
float movetime = Mathf.Abs(moveang * r / dec);
dt0 = DT.To((b) =>
{
allang = b;
Move();
},allang,allang+moveang,movetime).OnComlete(()=>
{
});
});
}
// Update is called once per frame
void Update()
{
}
//按钮的方法
public void OnBtn()
{
Debug.Log(inputField.text);
//要去的位置
int next = int.Parse(inputField.text);
Sort();
int id = list.IndexOf(sortList[num - 1].gameObject);
//默认的长度和方向
int n0 = id - next;
//反方向的长度
int n1 = num - Mathf.Abs(n0);
//反方向的方向
int n2 = n0 > 0 ? -n1 : n1;
//取长度较小的方向
int n3 = Mathf.Abs(n0) < Mathf.Abs(n2) ? n0 : n2;
float moveang = Mathf.Asin(sortList[num - 1].localPosition.x / r) + n3 * ang;
float movetime = Mathf.Abs(moveang * r / dec);
dt0 = DT.To((b) =>
{
allang = b;
Move();
}, allang, allang + moveang, movetime).OnComlete(() =>
{
});
}
}