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

原文地址:https://www.cnblogs.com/yzxhz/p/12987140.html