效果展示
今天做一个2D轮转的抽奖效果,我们需要先写一个2D轮转图。整体思路为动态生成一定数量的Image,通过改变Image的位置和缩放大小来展示他的轮转效果。这块需要一个预制体来进行动态生成。
先定义周长L,半径r,每个角弧度ang,移动的总弧度,还需要定义一个间距值
public float l; //周长
float r; //半径
float ang; //每个角弧度
float allang; //移动的总弧度
public float space = 10; //间距
现在求出周长,半径和角弧度
周长 = 数量 * (预制体的宽 + 间距)
半径 = 周长 / (2π)注意:2π为360度
角度 = 2π / 数量
l= num *(prefab.rectTransform.sizeDelta.x + space);
r = l/(2*Mathf.PI);
ang = 2 * Mathf.PI / num;
接下来定义两个集合
List<GameObject> list = new List<GameObject>();
List<Transform> sort = new List<Transform>();
在这块我们先生成一定数量的预制体,计算出他的位置和缩放大小,同时获取到预制体上的Text组件,给他赋值为i 。这块用到了一个最大值和最小值,是用来决定他的缩放效果的。我们将最大值默认为1,最小值默认为0.5,这样我们的预制体的缩放范围就是固定的,将在视觉上给我们一个前后空间的感觉。
for (int i = 0; i < num; i++)
{
if(list.Count <= i)
{
list.Add(Instantiate(prefab.gameObject ,transform));
sort.Add(list[i].transform);
list[i].GetComponentInChildren<TextMeshProUGUI>().text = i+"";
}
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;
}
下面我们给他封装一个排序方法,来控制他的渲染顺序。
我们将距离相机近的最后渲染,远的先进行渲染,这样就会出现效果图的情况,否则他将是下面的这种情况。(错误示范)
public void Sort()
{
//排序
sort.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 < sort.Count; i++)
{
sort[i].SetSiblingIndex(i);
}
}
上述完成之后我们开始做拖动的效果,这块我们需要继承两个接口IDragHandler和IEndDragHandler
拖拽中和拖拽结束。
现在我们思考一下拖拽中和拖拽结束的时候需要实现一个什么效果呢?
拖拽中时,我们需要让他一直处于转动的状态,结束拖拽时,需要整体有一个惯性效果,让他继续进行转动,而在惯性效果结束时,要让他最后得到的那个Image处于我们屏幕的中心线,就是对齐效果。
拖拽中时,我们直接调用我们之前写好的方法就可以了,就是上面提到的生成预制体那段代码
public void OnDrag(PointerEventData eventData)
{
if (dt!=null)
{
Destroy(dt.gameObject);
}
if (dt0 != null)
{
Destroy(dt0.gameObject);
}
allang -= eventData.delta.x / r;
Move();
}
结束拖拽时,我们拿到他移动的距离,计算出需要的时间,利用Dotween做出缓动效果。
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).OnComplete(() =>
{
float moveang = Mathf.Asin( sort[num - 1].localPosition.x / r);
float movetime = Mathf.Abs(moveang * r / dec);
dt0 = DT.To((b) =>
{
allang = b;
Move();
}, allang, allang + moveang, movetime).OnComplete(() =>
{
});
});
}
这时,我们的乱转图就实现了,但是呢,我们在这个基础上做一个抽奖的效果,我们在输入框输入想转到的预制体编号,点击按钮,让轮转图实现这个效果。
首先我们需要拿到输入框的内容,这个就是我们最后需要转到的目标点。调用前边的排序方法。
求出当前的长度和位置。
现在求出默认的位置和方向n0,、反方向的长度n1为 num - Mathf.Abs(n0),这块需要用到绝对值哦,因为他需要判断是顺时针还是逆时针旋转,方便后续进行计算。
通过比较拿到反方向的方向n2,我们只要拿到长度较小的那个方向就可以了,这块为n3
最后计算移动的角度,反正弦拿到集合中最后一个数据的x/r + n3 * ang
移动时间 用距离 / 减速度,利用DoTween 做出效果就可以了。
//目标点
int next = int.Parse(input.text);
//启动排序
Sort();
//当前的长度和位置
int id = list.IndexOf(sort[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(sort[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).OnComplete(() =>
{
});
整体代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using TMPro;
public class Chart2D : MonoBehaviour,IDragHandler,IEndDragHandler
{
public int num = 14;
public float space = 10; //间距
public Image prefab;
public float max = 1;
public float min = 0.5f;
public float dec = 10;
public float l; //周长
float r; //半径
float ang; //每个角弧度
float allang; //移动的总弧度
public TMP_InputField input;
public Button btn;
List<GameObject> list = new List<GameObject>();
List<Transform> sort = new List<Transform>();
DT dt;
DT dt0;
public void Move()
{
for (int i = 0; i < num; i++)
{
if(list.Count <= i)
{
list.Add(Instantiate(prefab.gameObject ,transform));
sort.Add(list[i].transform);
list[i].GetComponentInChildren<TextMeshProUGUI>().text = i+"";
}
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();
}
public void OnDrag(PointerEventData eventData)
{
if (dt!=null)
{
Destroy(dt.gameObject);
}
if (dt0 != null)
{
Destroy(dt0.gameObject);
}
allang -= eventData.delta.x / r;
Move();
}
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).OnComplete(() =>
{
float moveang = Mathf.Asin( sort[num - 1].localPosition.x / r);
float movetime = Mathf.Abs(moveang * r / dec);
dt0 = DT.To((b) =>
{
allang = b;
Move();
}, allang, allang + moveang, movetime).OnComplete(() =>
{
});
});
}
public void Sort()
{
//排序
sort.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 < sort.Count; i++)
{
sort[i].SetSiblingIndex(i);
}
}
// Start is called before the first frame update
void Start()
{
l= num *(prefab.rectTransform.sizeDelta.x + space);
r = l/(2*Mathf.PI);
ang = 2 * Mathf.PI / num;
Move();
btn.onClick.AddListener(OnBtn);
}
// Update is called once per frame
void Update()
{
}
public void OnBtn()
{
Debug.Log(input.text);
//目标点
int next = int.Parse(input.text);
//启动排序
Sort();
//当前的长度和位置
int id = list.IndexOf(sort[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(sort[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).OnComplete(() =>
{
});
}
}