• 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

  • 相关阅读:
    微信小程序~事件绑定和冒泡
    为promise增加abort功能
    Object构造函数的方法 之 Object.freeze
    ES6新特性:JavaScript中内置的延迟对象Promise
    js 预编译
    什么是PWA?PWA的发展趋势
    CSS隐藏元素的方法及区别
    网页编码:UTF-8、GB2312
    Mixin 和 CSS Guards
    css自定义checkbox样式
  • 原文地址:https://www.cnblogs.com/yzxhz/p/12987140.html
Copyright © 2020-2023  润新知