在我们制作一些场景选关或者是做一些选择类似的UI时,我们会觉得想制作一种3D效果的选择方式,3D轮转图往往就是最恰当的一种,下面我将会介绍3D轮转图的具体原理及实现:
一、效果演示
这里是一个简单的3D轮转图效果,实现了物体滑动,自动对齐,惯性,按钮点击选中
效果转速比较快,可能看不太清
效果演示
二、轮转图实现思路及操作
我们要想实现3D轮转图,就要先确认实现的思路,首先,3D轮转图其实就是多个物体围绕着圆心摆放,然后我们滑动物体的时候,去改变3D轮转图下子物体的位置(localPosition)。
我们先在场景中创建一个空对象,然后创建一个名为Cyclogram的轮转图的脚本,将脚本挂载到空对象(图1),在场景中创建一个Cube,把Cube制成预制体(图2)
图1
图2
Cyclogram脚本如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class Cyclogram : MonoBehaviour
{
//轮转图物体预制体
public GameObject prefab;
//轮转图物体个数
public int num = 5;
//轮转图半径
public int r = 5;
public float cutspeed = 10;//减速度
public float moveAng = 0;//移动后的弧度
//存放轮转图物体的集合
List<GameObject> list = new List<GameObject>();
//存放轮转图物体位置的集合
List<GameObject> sorts = new List<GameObject>();
//轮转图滑动一次的弧度
float ang;
Tweener tweener;
// Start is called before the first frame update
void Start()
{
//求出对应item数下的每一份的角度
ang = 2 * Mathf.PI / num;
OnMove();
}
public void OnMove()
{
for (int i = 0; i < num; i++)
{
//计算顶点位置
//求出角度对应的X,Z轴
float x = Mathf.Sin(ang * i + moveAng) * r;
float z = Mathf.Cos(ang * i + moveAng) * r;
//如果集合数小于item份数,就生成预制体
if (list.Count<=i)
{
GameObject cell = Instantiate(prefab, transform);
list.Add(cell);
sorts.Add(cell);
}
//item位置
list[i].transform.localPosition = new Vector3(x, 0, z);
}
}
//滑动方法
public void OnDrag(float dis)
{
tweener.Kill();
//拖拽时移动的角度等于距离除以半径
float dragAng = dis / r;
//总角度减去对应移动的角度
moveAng -= dragAng;
OnMove();
}
//实现惯性方法
public void Inertia(float dis)
{
//时间需要取正数,距离除去减速度就是时间
float time = Mathf.Abs(dis / cutspeed);
tweener=DOTween.To((a) =>
{
float dragAng = dis / r;
moveAng -= dragAng;
OnMove();
}, dis, 0, time).OnComplete(()=>
{
//找的z最小的
//当dotween结束时先将集合位置排序,确认item位置的z轴和圆心的距离,取最小值
sorts.Sort((a, b) =>
{
if (a.transform.localPosition.z<b.transform.localPosition.z)
{
return -1;
}
else if(a.transform.localPosition.z==b.transform.localPosition.z)
{
return 0;
}
else
{
return 1;
}
});
//求出调整的位置和时间
float alignAng = Mathf.Asin(sorts[0].transform.localPosition.x / r);
float alignDis = alignAng * r;
float alignTime = Mathf.Abs(alignDis / cutspeed);
DOTween.To((b) =>
{
//通过dotween缓动效果将物体位置调整
moveAng = b;
OnMove();
},moveAng,moveAng+alignAng,alignTime);
});
}
public void Align(int n)
{
sorts.Sort((a, b) =>
{
if (a.transform.localPosition.z < b.transform.localPosition.z)
{
return -1;
}
else if (a.transform.localPosition.z == b.transform.localPosition.z)
{
return 0;
}
else
{
return 1;
}
});
//找到z最小的在list里的下标
int id = list.IndexOf(sorts[0]);
int zheng = id - n;//正
//反方向长度=总长度-正方向长度
int fan = num - Mathf.Abs(zheng);//反
//反方向正负一定与正方向相反
fan = zheng < 0 ? fan : -fan;
//取正反方向距离短的一个
int interval = Mathf.Abs(zheng) < Mathf.Abs(fan) ? zheng : fan;
float intervalAng = interval * ang;
float alignAng = Mathf.Asin(sorts[0].transform.localPosition.x / r)+intervalAng;
float alignDis = alignAng * r;
float alignTime = Mathf.Abs(alignDis / cutspeed);
DOTween.To((b) =>
{
moveAng = b;
OnMove();
}, moveAng, moveAng + alignAng, alignTime);
}
// Update is called once per frame
void Update()
{
}
}
以上代码通过简单计算获得轮转图的半径,周长,每份的弧度等数据。
注(这里在实现效果时用了DOTween效果,我用的是插件,可以手写)
这里需要再创建一个预制体脚本,实现滑动效果,创建一个名为MoveCell的脚本挂载到之前创建好的预制体上
MoveCell代码如下:
//鼠标滑动
private void OnMouseDrag()
{
//世界坐标转屏幕坐标,将目标位置提取出来
Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);
Vector3 mouse = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pos.z));
//将鼠标拖动后的距离传给对应的拖动函数
float dis = mouse.x - transform.position.x;
Debug.Log(dis);
transform.parent.GetComponent<Cyclogram>().OnDrag(dis);
}
//鼠标松开
private void OnMouseUp()
{
Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);
Vector3 mouse = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pos.z));
float dis = mouse.x - transform.position.x;
Debug.Log(dis);
transform.parent.GetComponent<Cyclogram>().Inertia(dis);
}
这样,一个有对齐效果和惯性效果的3D轮转图效果就实现好了。
这些代码都只是实现了基本的功能,还有很大的不足,大家参考一下就好