今天介绍3D轮转图的制作,上边这个效果实在3D轮转图的基础上做成了曲面屏展示。
制作轮转图通俗来说就是以一个点为中心点,生成Cube围绕他进行对应的公转和自转。
首先需要定义半径r和数量num。之后求出他的角度。(2*mathf.pi 为360度)
public int num;
public float r;
float ang ;
ang = 2 * Mathf.PI / num;
在之后我封装了一个方法来计算位置,同时,生成对应数量的Cube。
public void Move()
{
for (int i = 0; i < num; i++)
{
float x = Mathf.Sin(i * ang + allAng) * r;
float z = Mathf.Cos(i * ang + allAng) * r;
if(list.Count <= i)
{
GameObject sphere = Instantiate(prefab);
sphere.transform.parent = transform;
sphere.GetComponent<CyclogramItem>().cyclogram = this;
sphere.GetComponent<MeshRenderer>().material.mainTexture = textures[i];
list.Add(sphere);
sortList.Add(sphere.transform);
sphere.name = i+"";
}
list[i].transform.localPosition = new Vector3(x, 0, z);
//当前的弧度转换为度
list[i].transform.localEulerAngles = Vector3.up * ((i * ang + allAng) * Mathf.Rad2Deg);
}
}
在上述代码中提到了两个集合,如下。这两个集合是用来存储生成的预制体 GameObject 类型和Transform类型。 list是方便记录数据,而sortlist 见名知意是用来排序。那么哪里需要用到排序呢?
List<GameObject> list = new List<GameObject>();
List<Transform> sortList = new List<Transform>();
当我们在用鼠标拖拽其中的一个Cube时,生成的预制体集合会跟随这个拖拽动作进行一个公转的状态,而我们的sortlist在此处就有了用处。当公转的状态停止时,他会有一个具体相机最近的GameObject ,相当于就是我们抽奖选中的物品。类似这样的一个效果。那么我们只需要给list集合里面所有GameObject的Transform,position.z做一个排序就好了。要拿到距离相机最近,就相当于他的z是最小的。那么整体逻辑知道后,我们看看代码是怎么实现的。代码中的DT等同于我们的Dotween插件。
public void Inertia(float dis)
{
float time =Mathf.Abs( dis / dec);
DT.To((a) =>
{
OnDrag(a);
}, dis, 0, time).OnComplete(() =>
{
sortList.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(sortList[0].localPosition.x / r);
float aligntimer = Mathf.Abs( aligning * r / 200);
DT.To((a) =>
{
allAng = a;
Move();
}, allAng, allAng + aligning, aligntimer).OnComplete(() =>
{
int index = int.Parse(sortList[0].name);
if(index<8)
{
print(index + 8);
img.sprite = Resources.Load<Sprite>("character_"+ (index + 8));
}
else
{
print(index - 8);
img.sprite = Resources.Load<Sprite>("character_" + (index - 8));
}
});
});
}
现在我们需要考虑拖拽的触发,这块直接上代码。只需要拿到物体的屏幕坐标,同时将他的屏幕坐标的z值,和鼠标位置的x和y值记录,将这个x和物体的x做差值,拿到物体移动的距离就好了。之后代用我们上边写好的Move()方法就可以让他进行旋转了。
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);
}
现在将两个脚本代码进行展示
Cyclogram 脚本需要挂在一个空对象下面
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
public class Cyclogram : MonoBehaviour
{
public Image img;
public Texture[] textures;
public GameObject prefab;
public int num;
public float r;
float ang ;
public float dec = 5f;
List<GameObject> list = new List<GameObject>();
List<Transform> sortList = new List<Transform>();
// Start is called before the first frame update
void Start()
{
ang = 2 * Mathf.PI / num;
Move();
img.sprite = Resources.Load<Sprite>("character_0");
}
float allAng = 0;
public void OnDrag(float dis)
{
float moveang = dis / r;
allAng -= moveang;
Move();
}
public void Move()
{
for (int i = 0; i < num; i++)
{
float x = Mathf.Sin(i * ang + allAng) * r;
float z = Mathf.Cos(i * ang + allAng) * r;
if(list.Count <= i)
{
GameObject sphere = Instantiate(prefab);
sphere.transform.parent = transform;
sphere.GetComponent<CyclogramItem>().cyclogram = this;
sphere.GetComponent<MeshRenderer>().material.mainTexture = textures[i];
list.Add(sphere);
sortList.Add(sphere.transform);
sphere.name = i+"";
}
list[i].transform.localPosition = new Vector3(x, 0, z);
//当前的弧度转换为度
list[i].transform.localEulerAngles = Vector3.up * ((i * ang + allAng) * Mathf.Rad2Deg);
}
}
public void Inertia(float dis)
{
float time =Mathf.Abs( dis / dec);
DT.To((a) =>
{
OnDrag(a);
}, dis, 0, time).OnComplete(() =>
{
sortList.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(sortList[0].localPosition.x / r);
float aligntimer = Mathf.Abs( aligning * r / 200);
DT.To((a) =>
{
allAng = a;
Move();
}, allAng, allAng + aligning, aligntimer).OnComplete(() =>
{
int index = int.Parse(sortList[0].name);
if(index<8)
{
print(index + 8);
img.sprite = Resources.Load<Sprite>("character_"+ (index + 8));
}
else
{
print(index - 8);
img.sprite = Resources.Load<Sprite>("character_" + (index - 8));
}
});
});
}
// Update is called once per frame
void Update()
{
}
}
CyclogramItem 需要给预制体添加
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CyclogramItem : 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);
}
}
将轮转图的大体思路讲完之后,我们看看怎么去绘制曲面
这块我们需要将15张图片拼接成一个圆环,那么每张图片也是一个弧形状态。所以我们需要定义数量num ,半径r ,然后求出他的周长和半径。
float n = 15; //个数
float l; //周长
float r; //半径
拿到周长和半径方便求出整体的角度和每个图片的弧度。
l = 150 * n;
r = l / (2 * Mathf.PI);
float ang = 2 * Mathf.PI / n;
float sunang = 2 * Mathf.PI / (n * 10);
现在我们开始进行绘制这个圆环。这块同之前的绘制方式,不再过多叙述。
VertexHelper vh = new VertexHelper();
for (int i = -5; i <= 5; i++)
{
float x = Mathf.Sin(i * sunang) * r - Mathf.Sin(0) * r;
float z = Mathf.Cos(i * sunang) * r - Mathf.Cos(0) * r;
float y = 113;
float y0 = -113;
float uvx = (float)(i + 5) / 10;
vh.AddVert(new Vector3(x, y, z), Color.white, new Vector2(uvx, 1));
vh.AddVert(new Vector3(x, y0, z), Color.white, new Vector2(uvx, 0));
if (i < 5)
{
int j = i + 5;
vh.AddTriangle(j * 2 + 1, j * 2, (j + 1) * 2);
vh.AddTriangle(j * 2 + 1, (j + 1) * 2, (j + 1) * 2 + 1);
}
}
最后还差一步Mesh赋值。整体代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CreateCamber : MonoBehaviour
{
float n = 15; //个数
float l; //周长
float r; //半径
// Start is called before the first frame update
void Start()
{
l = 150 * n;
r = l / (2 * Mathf.PI);
float ang = 2 * Mathf.PI / n;
float sunang = 2 * Mathf.PI / (n * 10);
VertexHelper vh = new VertexHelper();
for (int i = -5; i <= 5; i++)
{
float x = Mathf.Sin(i * sunang) * r - Mathf.Sin(0) * r;
float z = Mathf.Cos(i * sunang) * r - Mathf.Cos(0) * r;
float y = 113;
float y0 = -113;
float uvx = (float)(i + 5) / 10;
vh.AddVert(new Vector3(x, y, z), Color.white, new Vector2(uvx, 1));
vh.AddVert(new Vector3(x, y0, z), Color.white, new Vector2(uvx, 0));
if (i < 5)
{
int j = i + 5;
vh.AddTriangle(j * 2 + 1, j * 2, (j + 1) * 2);
vh.AddTriangle(j * 2 + 1, (j + 1) * 2, (j + 1) * 2 + 1);
}
}
Mesh mesh = new Mesh();
vh.FillMesh(mesh);
GetComponent<MeshFilter>().mesh = mesh;
GetComponent<MeshCollider>().sharedMesh = mesh;
// transform.LookAt(transform.parent.transform);
}
}