早在之前简介了一下绘制的原理,通过网格可以绘制一些简单的图形,但是上手较难,将轮转图和通过顶点绘制弧形网格结合就是本次的目的.
首先需要搞定绘制弧形的网格,代码和注释如下:
float n = 15; //定义一个变量n表示图形的个数
float l; //定义一个变量l表示周长
float r; //定义一个变量r表示半径
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; //计算x坐标
float z = Mathf.Cos(i * sunang) * r - Mathf.Cos(0) * r; //计算z坐标
float y = 113; //可替换
float y0 = -113;
float uvx = (float)(i + 5) / 10; //计算纹理坐标x值
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;
}
首先是确立绘制图形的总个数,其中的 l 半径,y和y0都是根据当时图片的分辨率等比计算的结果,可以替换,绘制的顶点数也可是根据所需图形的顶点制定.
这个脚本也可以单独显示,可以看到生成好的一片弧形Mash网格
解决好了独立的一个弧形网格就该使用轮转图了,轮转图和轮播图不同,轮播图更多用于网页上图片的循环展示
将上文生成的一片弧形Mash挂载到预制体上,同时需要响应轮转图的事件,这里可以使用Unity
的事件系统 IDragHandler, IBeginDragHandler, IEndDragHandler
或者是OnMouseDrag() OnMouseUp()
来解决,事件系统也可以用于3D轮转图,而OnMouse拖拽事件的触发条件比较宽松,两个方法各有优势
在这里挂载上的脚本采用OnMouse()方法
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);
}
在这也使用了世界坐标专屏幕坐标的方法获取鼠标位置,再传给轮转图的移动方法和移动之后的处理
最后是Control脚本
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>();
void Start()
{
ang = 2 * Mathf.PI / num; // 根据数量计算角度
Move();
}
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; // 根据角度和半径计算z坐标值
if (list.Count <= i)
{
GameObject sphere = Instantiate(prefab);
sphere.transform.parent = transform;
sphere.GetComponent<CyclogramItem>().cyclogram = this; // 获取并设置CyclogramItem组件的cyclogram属性为当前对象
sphere.GetComponent<MeshRenderer>().material.mainTexture = textures[i];
list.Add(sphere);
sortList.Add(sphere.transform);
}
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(() =>
{
//这一部分可以添加对齐后的代码,比如对齐后会显示角色立绘,角色的详情面板以及模型等,
//可以使用脚本也可以直接添加
});
});
}
代码中的对齐和偏移都是自制类Dotween类实现的,也可以直接使用Dotween动画插件进行动画效果,避免重复造轮子,-——
以下为自制DotWeen的部分应用,定义了动画的开始,结束时间,对应触发的委托和计时
public Action<float> action;
public float bagin;
public float end;
public float time;
public Action complete;
float nowtime;
public static DotweenHuan To(Action<float> action, float bagin, float end, float time)
{
GameObject dt = new GameObject("DotweenHuan");
DotweenHuan dotween = dt.AddComponent<DotweenHuan>();
dotween.action = action;
dotween.bagin = bagin;
dotween.end = end;
dotween.time = time;
dotween.nowtime = Time.time;
return dotween;
}
private void Update()
{
if (Time.time - nowtime < time)
{
float t = Time.time - nowtime;
float p = t / time;
float a = bagin * (1 - p) + end * p;
action(a);
}
else
{
action(end);
if (complete !=null)
{
complete();
}
Destroy(gameObject);
}
}
public void OnComplete(Action complete)
{
this.complete = complete;
}
完成以上准备后就可以实现对应开篇提及效果,提及效果因为OnMouse等原因并不完美,可以改进