unity中3d图表有很多,虽然功能齐全,效果很好,但是都很臃肿,往往项目只需要一个柱状图,可能需要把整个插件一个不落下的都导入到工程,
因此,自己写一套简易的就好了!
先上代码:
---图表基类----
ChartBase
using System.Collections.Generic; using UnityEngine; public class ChartBase : MonoBehaviour { public Material mat;//mesh材质 /// <summary> /// 所有的左面坐标 /// </summary> protected List<Vector3> leftPoints = new List<Vector3>(); /// <summary> /// 所有的前面坐标 /// </summary> protected List<Vector3> forwardPoints = new List<Vector3>(); /// <summary> /// 所有的底面坐标 /// </summary> protected List<Vector3> bottomPoints = new List<Vector3>(); /// <summary> /// 所有的后面坐标 /// </summary> protected List<Vector3> backPoints = new List<Vector3>(); /// <summary> /// 所有的上面坐标 /// </summary> protected List<Vector3> upPoints = new List<Vector3>(); /// <summary> /// 所有的右面坐标 /// </summary> protected List<Vector3> rightPoints = new List<Vector3>(); /// <summary> /// 所有的顶点添加偏移 /// </summary> protected List<PointsInfo> verticesAddOffset = new List<PointsInfo>(); /// <summary> /// 索引 /// </summary> protected List<int[]> triangles = new List<int[]>(); /// <summary> /// 单个物体的点位信息 /// </summary> [HideInInspector] public List<PointsInfo> pointsInfos = new List<PointsInfo>(); protected List<Mesh> meshs = new List<Mesh>(); /// <summary> /// 所有的生成的父物体 /// </summary> protected List<GameObject> allChartParentObj = new List<GameObject>(); /// <summary> /// 所有的生成的子物体 /// </summary> protected List<GameObject> allChartItemObj = new List<GameObject>(); /// <summary> /// 生成点位中心 /// </summary> [Header("生成点位中心")] public Vector3 center = Vector3.zero; protected string meshName; public virtual void Awake() { SetMeshName(); } public virtual void SetMeshName() { meshName = gameObject.name; } private void OnEnable() { CreateMesh(); SetMeshInfo(); ApplyValue(); ShowAnim(); } /// <summary> /// 展示动画 /// </summary> public virtual void ShowAnim() { for (int i = 0; i < pointsInfos.Count; i++) { for (int j = 0; j < pointsInfos[i].points.Length; j++) { int tempI = i; int tempJ = j; StartCoroutine(DoFloatValue(0, pointsInfos[i].points[j].y, 1, (value) => { pointsInfos[tempI].points[tempJ].y = value; })); } } } protected System.Collections.IEnumerator DoFloatValue(float startValue, float endValue, float time, System.Action<float> ChangeAction) { float tempF = startValue; float offsetValue = (endValue - startValue) / (time / 0.02f); bool addOrReduce = offsetValue >= 0; while (true) { yield return new WaitForFixedUpdate(); tempF += offsetValue; ChangeAction(tempF); if ((addOrReduce && tempF >= endValue) || (!addOrReduce && tempF <= endValue)) { ChangeAction(endValue); break; } } } public virtual void Update() { CreateMesh(); SetMeshInfo(); ApplyValue(); } /// <summary> /// 创建mesh /// </summary> public virtual void CreateMesh() { if (pointsInfos.Count != meshs.Count) { DeleteAllItemObjs(); GameObject objParent = new GameObject(); objParent.name = meshName; objParent.transform.position = Vector3.zero; allChartParentObj.Add(objParent); for (int i = 0; i < pointsInfos.Count; i++) { GameObject itemObj = new GameObject(); itemObj.transform.parent = objParent.transform; itemObj.name = meshName+"_Child_"+i.ToString(); Mesh mesh = new Mesh(); meshs.Add(mesh); itemObj.AddComponent<MeshFilter>().mesh = mesh; itemObj.AddComponent<MeshRenderer>(); itemObj.GetComponent<MeshRenderer>().material = mat; allChartItemObj.Add(itemObj); } } } protected void DeleteAllItemObjs() { for (int i = 0; i < allChartParentObj.Count; i++) { DestroyImmediate(allChartParentObj[i]); } allChartParentObj.Clear(); allChartItemObj.Clear(); meshs.Clear(); } /// <summary> /// 设置顶点信息 /// </summary> public virtual void SetMeshInfo() { triangles.Clear(); verticesAddOffset.Clear(); for (int i = 0; i < pointsInfos.Count; i++) { Vector3[] vertices = GetPointsVector3s(pointsInfos[i]); verticesAddOffset.Add(new PointsInfo(SetVerticesOffset(vertices, pointsInfos[i]), Vector3.one)); triangles.Add(GetTrianglesVector3s(vertices)); } } public virtual void ApplyValue() { for (int i = 0; i < meshs.Count; i++) { meshs[i].Clear(); meshs[i].vertices = verticesAddOffset[i].points; meshs[i].triangles = triangles[i]; meshs[i].RecalculateNormals();//重置法线 meshs[i].RecalculateBounds(); //重置范围 } } /// <summary> /// 根据顶点和位移差获取最终位置 /// </summary> /// <param name="vertices"></param> /// <param name="index"></param> /// <returns></returns> public virtual Vector3[] SetVerticesOffset(Vector3[] vertices, PointsInfo _points) { Vector3[] tempV3 = new Vector3[vertices.Length]; for (int i = 0; i < vertices.Length; i++) { //Vector3 tempDir =Vector3.Normalize( vertices[i] - center); tempV3[i] = new Vector3(vertices[i].x, 0, vertices[i].z); int indexX = (int)vertices[i].x; if (_points.size.y == vertices[i].y) { tempV3[i] += new Vector3(0, _points.points[indexX].y, 0); } tempV3[i] += new Vector3(_points.points[indexX].x, 0, _points.points[indexX].z); } return tempV3; } /// <summary> /// 来自大佬的方法,求直线上的投影点 /// </summary> /// <param name="P"> 直线外的点</param> /// <param name="A">直线上点</param> /// <param name="B">直线上点</param> /// <returns></returns> protected Vector3 LinePointProjection(Vector3 P, Vector3 A, Vector3 B) { Vector3 v = B - A; return A + v * (Vector3.Dot(v, P - A) / Vector3.Dot(v, v)); } /// <summary> /// 获取正方体的每个面顶点坐标 (先生成最左面的第一竖列顶点,然后依次推导出后面的点) /// </summary> /// <param name="count"></param> /// <returns></returns> protected virtual Vector3[] GetPointsVector3s(PointsInfo _points) { List<Vector3> tempPoints = new List<Vector3>(); leftPoints.Clear(); forwardPoints.Clear(); bottomPoints.Clear(); backPoints.Clear(); upPoints.Clear(); rightPoints.Clear(); leftPoints.Add(new Vector3(0, 0, 0));//底面点 leftPoints.Add(new Vector3(0, 0, _points.size.z));//底面点 leftPoints.Add(new Vector3(0, _points.size.y, _points.size.z)); leftPoints.Add(new Vector3(0, _points.size.y, 0)); int midCount = _points.points.Length - 1; for (int i = 0; i < midCount; i++) { forwardPoints.Add(leftPoints[0] + new Vector3(_points.size.x * i, 0, 0));//底面点 forwardPoints.Add(leftPoints[3] + new Vector3(_points.size.x * i, 0, 0)); forwardPoints.Add(leftPoints[3] + new Vector3(_points.size.x * (i + 1), 0, 0)); forwardPoints.Add(leftPoints[0] + new Vector3(_points.size.x * (i + 1), 0, 0));//底面点 } for (int i = 0; i < midCount; i++) { bottomPoints.Add(leftPoints[1] + new Vector3(_points.size.x * i, 0, 0));//底面点 bottomPoints.Add(leftPoints[0] + new Vector3(_points.size.x * i, 0, 0));//底面点 bottomPoints.Add(leftPoints[0] + new Vector3(_points.size.x * (i + 1), 0, 0));//底面点 bottomPoints.Add(leftPoints[1] + new Vector3(_points.size.x * (i + 1), 0, 0));//底面点 } for (int i = 0; i < midCount; i++) { backPoints.Add(leftPoints[2] + new Vector3(_points.size.x * i, 0, 0)); backPoints.Add(leftPoints[1] + new Vector3(_points.size.x * i, 0, 0));//底面点 backPoints.Add(leftPoints[1] + new Vector3(_points.size.x * (i + 1), 0, 0));//底面点 backPoints.Add(leftPoints[2] + new Vector3(_points.size.x * (i + 1), 0, 0)); } for (int i = 0; i < midCount; i++) { upPoints.Add(leftPoints[3] + new Vector3(_points.size.x * i, 0, 0)); upPoints.Add(leftPoints[2] + new Vector3(_points.size.x * i, 0, 0)); upPoints.Add(leftPoints[2] + new Vector3(_points.size.x * (i + 1), 0, 0)); upPoints.Add(leftPoints[3] + new Vector3(_points.size.x * (i + 1), 0, 0)); } rightPoints.Add(leftPoints[3] + new Vector3(_points.size.x * midCount, 0, 0)); rightPoints.Add(leftPoints[2] + new Vector3(_points.size.x * midCount, 0, 0)); rightPoints.Add(leftPoints[1] + new Vector3(_points.size.x * midCount, 0, 0));//底面点 rightPoints.Add(leftPoints[0] + new Vector3(_points.size.x * midCount, 0, 0));//底面点 tempPoints.AddRange(leftPoints); tempPoints.AddRange(forwardPoints); tempPoints.AddRange(bottomPoints); tempPoints.AddRange(backPoints); tempPoints.AddRange(upPoints); tempPoints.AddRange(rightPoints); return tempPoints.ToArray(); } /// <summary> /// 设置三角面索引 /// </summary> /// <param name="vertices"></param> /// <returns></returns> protected int[] GetTrianglesVector3s(Vector3[] vertices) { List<int> all = new List<int>(); for (int i = 0; i < vertices.Length; i++) { if (i % 4 == 0) //每个面四个顶点单独设置 { SetIndex(all, i); } } return all.ToArray(); } /// <summary> /// 通过每个面设置三角形索引 /// </summary> /// <param name="ls"></param> /// <param name="i"></param> protected void SetIndex(List<int> ls, int i) { ls.Add(i); ls.Add(i + 1); ls.Add(i + 2); ls.Add(i); ls.Add(i + 2); ls.Add(i + 3); } } [System.Serializable] public partial class PointsInfo { public Vector3[] points; /// <summary> /// 三个轴向的尺寸 /// </summary> public Vector3 size; public PointsInfo(Vector3[] _points, Vector3 _size) { points = _points; size = _size; } } [System.Serializable] public class Indexes { public int[] index; public Indexes(int[] _index) { index = _index; } }
2020.06.03更新了播放初始化动画
ShowAnim();
此类基本就是实现了柱状图。
基本思路开始:
绘制网格,连成长方体,形成柱状图主体。
核心方法是绘制长方体,要诀:
每个面单独绘制,每个面都是由两个以上偶数个三角面组成,绘制面的点都是顺时针连接。
unity坐标系是左手坐标系,所以坐标应该是如下图所示
我这里使用的是由左面往右绘制,先绘制左边的面
leftPoints.Add(new Vector3(0, 0, 0));//底面点 leftPoints.Add(new Vector3(0, 0, size.z));//底面点 leftPoints.Add(new Vector3(0, size.y, size.z)); leftPoints.Add(new Vector3(0, size.y, 0));
然后绘制前面:这里使用
int midCount = count - 1;
为了之后绘制折线图的时候分段。
for (int i = 0; i < midCount; i++) { forwardPoints.Add(leftPoints[0] + new Vector3(unit.x * i, 0, 0));//底面点 forwardPoints.Add(leftPoints[3] + new Vector3(unit.x * i, 0, 0)); forwardPoints.Add(leftPoints[3] + new Vector3(unit.x * (i + 1), 0, 0)); forwardPoints.Add(leftPoints[0] + new Vector3(unit.x * (i + 1), 0, 0));//底面点 }
之后几个面依次画出。。。。。。。。。。。。
然后设置下三角面的连接索引:
/// <summary> /// 设置三角面索引 /// </summary> /// <param name="vertices"></param> /// <param name="count"></param> /// <returns></returns> protected int[] GetTrianglesVector3s(Vector3[] vertices, int count) { List<int> all = new List<int>(); for (int i = 0; i < vertices.Length; i++) { if (i % 4 == 0) //每个面四个顶点单独设置 { SetIndex(all, i); } } return all.ToArray(); } /// <summary> /// 通过每个面设置三角形索引 /// </summary> /// <param name="ls"></param> /// <param name="i"></param> protected void SetIndex(List<int> ls, int i) { ls.Add(i); ls.Add(i + 1); ls.Add(i + 2); ls.Add(i); ls.Add(i + 2); ls.Add(i + 3); }
每个面四个顶点,每个面的连接顺序都是: 0,1,2, 0,2,3 这样的两个三角面组成。
最后在这里区分柱状图与折线图的方法:
/// <summary> /// 根据顶点和位移差获取最终位置 /// </summary> /// <param name="vertices"></param> /// <param name="index"></param> /// <returns></returns> public override Vector3[] SetVerticesOffset(Vector3[] vertices, PointsInfo _points) { Vector3[] tempV3 = new Vector3[vertices.Length]; for (int i = 0; i < vertices.Length; i++) { tempV3[i] = new Vector3(vertices[i].x, 0, vertices[i].z); int indexX = (int)(vertices[i].x / _points.size.x); if (_points.size.y == vertices[i].y) { tempV3[i] += new Vector3(0, _points.points[indexX].y, 0); } tempV3[i] += new Vector3(_points.points[indexX].x, 0, _points.points[indexX].z); } return tempV3; }
上面的方法获取到所有的顶点后对顶点进行位置偏移,以便实现一个mesh中实现多个长方体。
int indexX = (int)(vertices[i].x / _points.size.x);
这个地方拿到了X轴索引,也就是上图中的(0,3,2,1)点索引为0 。。。。 (4,7,6,5)点索引为1
这个地方获取到y轴上表面点也就是上面图中的3,2,6,7点,给高度差实现不同长方体有不同的高度
tempV3[i] += new Vector3(offsets[index].points[indexX].x, 0, offsets[index].points[indexX].z);
这地方对X轴和Z轴进行偏移,实现不同长方体有不同位置
基本思路结束!!!
在此基础上再绘制折线图就容易许多了:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class LineChart : ChartBase { List<Vector3> fowardLine = new List<Vector3>(); List<Vector3> backLine = new List<Vector3>(); List<Vector3> shouldMovePoss = new List<Vector3>(); List<Vector3> shouldMoveBackPoss = new List<Vector3>(); /// <summary> /// 底部Y轴坐标延展到0 /// </summary> [Header("底部Y轴坐标是否延展到0")] public bool bottomExtension = false; public override Vector3[] SetVerticesOffset(Vector3[] vertices, PointsInfo _points) { shouldMovePoss.Clear(); shouldMoveBackPoss.Clear(); fowardLine.Clear(); backLine.Clear(); Vector3[] tempV3 = new Vector3[vertices.Length]; for (int i = 0; i < vertices.Length; i++) { tempV3[i] = vertices[i]; int indexX = int.Parse((vertices[i].x / _points.size.x).ToString()); tempV3[i] += _points.points[indexX]; if (_points.points[indexX].y == tempV3[i].y)//底部点 { if (tempV3[i].z == _points.points[0].z)//区分前后面的点 { if (!fowardLine.Contains(tempV3[i])) fowardLine.Add(tempV3[i]); } else { if (!backLine.Contains(tempV3[i])) backLine.Add(tempV3[i]); } } } if (!bottomExtension) { GetShouldPos(fowardLine, _points.size.y, false); GetShouldPos(backLine, _points.size.y, true); for (int i = 0; i < tempV3.Length; i++) { for (int j = 1; j < fowardLine.Count - 1; j++) { if (tempV3[i] == fowardLine[j]) { tempV3[i] = shouldMovePoss[j - 1]; } } for (int j = 1; j < backLine.Count - 1; j++) { if (tempV3[i] == backLine[j]) { tempV3[i] = shouldMoveBackPoss[j - 1]; } } } } else { SetPosYToZero(fowardLine, false); SetPosYToZero(backLine, true); for (int i = 0; i < tempV3.Length; i++) { for (int j = 0; j < fowardLine.Count; j++) { if (tempV3[i] == fowardLine[j]) { tempV3[i] = shouldMovePoss[j]; } } for (int j = 0; j < backLine.Count; j++) { if (tempV3[i] == backLine[j]) { tempV3[i] = shouldMoveBackPoss[j]; } } } } return tempV3; } private void GetShouldPos(List<Vector3> vector3s, float yOffset, bool isBack) { for (int i = 1; i < vector3s.Count - 1; i++) { ///上方的对应点 Vector3 upPoints = vector3s[i] + new Vector3(0, yOffset, 0); Vector3 midDir = ((vector3s[i - 1] - vector3s[i]).normalized + (vector3s[i + 1] - vector3s[i]).normalized); float angle = Vector3.Angle((vector3s[i - 1] - vector3s[i]).normalized, (vector3s[i + 1] - vector3s[i]).normalized); Debug.DrawLine(upPoints, upPoints + midDir); Vector3 shouldMovePos = vector3s[i]; if (midDir != Vector3.zero) { shouldMovePos = LinePointProjection(vector3s[i], upPoints, upPoints + midDir); } if (isBack) { shouldMoveBackPoss.Add(shouldMovePos); } else { shouldMovePoss.Add(shouldMovePos); } } } private void SetPosYToZero(List<Vector3> vector3s, bool isBack) { for (int i = 0; i < vector3s.Count; i++) { if (isBack) { shouldMoveBackPoss.Add(new Vector3(vector3s[i].x, 0, vector3s[i].z)); } else { shouldMovePoss.Add(new Vector3(vector3s[i].x, 0, vector3s[i].z)); } } } }
2020.06.03更新:bool控制底部是否延伸到0,开启的动画