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,開啟的動畫