3D轮转图主要有两种实现模式,第一种是以摄像机第三人称观察整个3D轮转图结构,
第二种是摄像机在轮转图的圆心观察3D轮转图,两者的区别主要在于当拖动鼠标时,第一种方式当鼠标向左划,轮转图做顺时针移动,第二种方式则作逆时针移动,其次就是摄像机所处位置不同
第一步,首先创建一个空物体,空物体作为生成的轮转图item项的父对象
第二步创建一个预制体,该预制体就是3D轮转图上的Item
接下来首先写轮转图的框架:
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Cyclogram : MonoBehaviour
{
//纹理数组
public Texture[] textures;
//预制体
public GameObject prefab;
//确定轮转图item的个数
public int num;
//轮转图半径
public float r;
//轮转图减速度
public float dec = 5f;
// Start is called before the first frame update
//每一份的角度
float ang;
//预制体的集合
List<GameObject> list = new List<GameObject>();
//对应预制体的transform集合,主要是为了取位置
List<Transform> sorts = new List<Transform>();
void Start()
{
//求出对应item数下的每一份的角度
ang = 2 * Mathf.PI / num;
Move();
}
float allAng = 0;//默认总角度为0
public void OnDrag(float dis)
{
//拖拽时移动的角度等于距离除以半径
float moveAng = dis / r;
//总角度减去对应移动的角度
allAng -= moveAng;
Move();
}
//迟缓效果
public void Inertia(float dis)
{
//时间需要取正数,距离除去减速度就是时间
float time = Mathf.Abs(dis / dec);
//手写Dotween,主要参数时回调函数,起始位置,结束位置,时间
DT.To((a) =>
{
OnDrag(a);
}, dis, 0, time).OnComplete(() =>
{
//当dotween结束时先将集合位置排序,确认item位置的z轴和圆心的距离,取最小值
sorts.Sort((a, b) =>
{
if (a.position.z < b.position.z)
{
return -1;
}
else if (a.position.z == b.position.z)
{
return 0;
}
else
{
return 1;
}
});
//求出调整的位置和时间
float aligning = Mathf.Asin(sorts[0].position.x / r);
float aligningTime = Mathf.Abs(aligning * r / dec);
DT.To((a) =>
{
//通过dotween缓动效果将物体位置调整
allAng = a;
Move();
}, aligning, allAng + aligning, aligningTime);
});
}
private void Move()
{
for (int i = 0; i < num; i++)
{
//求出角度对应的X,Z轴
float x = Mathf.Sin(ang * i + allAng) * r;
float z = Mathf.Cos(ang * i + allAng) * r;
//如果集合数小于item份数,就生成预制体
if (list.Count <= i)
{
GameObject sphere = Instantiate(prefab);
sphere.transform.parent = transform;
sphere.GetComponent<Item>().cyclogram = this;
sphere.GetComponent<MeshRenderer>().material.mainTexture = textures[i];
list.Add(sphere);
sorts.Add(sphere.transform);
}
//安排item位置
list[i].transform.localPosition = new Vector3(x, 0, z);
//当前的弧度转换为度
list[i].transform.localEulerAngles = Vector3.up * ((i * ang + allAng) * Mathf.Rad2Deg);
}
}
// Update is called once per frame
void Update()
{
}
}
上述代码的主要作用是用来生成预制体,并且实现缓动和拖动效果,而具体效果的执行需要用到具体的预制体,预制体代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Item : MonoBehaviour
{
public Cyclogram cyclogram;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnMouseDrag()
{
//世界坐标转屏幕坐标,将目标位置提取出来
Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);
Vector3 next = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pos.z));
//将鼠标拖动后的距离传给对应的拖动函数
float dis = next.x - transform.position.x;
//调用轮转图的拖拽方法
cyclogram.OnDrag(dis);
}
private void OnMouseUp()//鼠标抬起时处理缓动效果
{
Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);
Vector3 next = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, pos.z));
float dis = next.x - transform.position.x;
//调用轮转图的缓动方法
cyclogram.Inertia(dis);
}
}
最后就是缓动效果中调用的手写Dotween了,代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DT : MonoBehaviour
{
public Action<float> action;
public float begin;
public float end;
public float time;
public GameObject dt;
float nowtime;
public Action complete;
public static DT To(Action<float> action, float begin, float end, float time)
{
//创建一个名为DT的空物体,将创过来的值接住
GameObject dt = new GameObject("DT");
DT dowteen = dt.AddComponent<DT>();
dowteen.action = action;
dowteen.begin = begin;
dowteen.end = end;
dowteen.time = time;
dowteen.nowtime = Time.time;
dowteen.dt = dt;
return dowteen;
}
// Update is called once per frame
void Update()
{
//在Update每帧调用,用起点和终点的差值确认item的位置
if (Time.time - nowtime < time)
{
float t = Time.time - nowtime;
float p = t / time;
float a = begin * (1 - p) + end * p;
action(a);
}
else
{
action(end);
//如果有后续的Complete函数,则继续执行
if (complete != null)
{
complete();
}
Destroy(gameObject);//删除空物体
}
}
public void OnComplete(Action complete)
{
this.complete = complete;
}
}