Unity制作3D圖表組件------柱狀圖於折線圖


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

 

 

 

 

工程鏈接:https://github.com/wtb521thl/ChartTest


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM