Unity UI案例(绘制圆环)
- 2019 年 12 月 2 日
- 筆記
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/CJB_King/article/details/78861038
详细设计:
1.饼图的绘制过程:
1.1 获取以某个点为中心,固定半径的圆上的点,再结合原点绘制三角面,可生成扇形;

//计算圆上点的位置 Smooth代表圆的光滑程度,也就是饼图被分为多少等份 int Smooth = 100; float perRadian = Matfh.PI * 2 / Smooth;//得到每份所占弧度 然后根据某一块饼图所占的比例计算出它在圆周上相交的点,我们规定从饼图右侧中间位置开始,逆时针方向计算。见图1: //比如绘制19%比例的扇形图 float radius = x;//半径 float startRadian = 0; Vector2 startPoint = new Vector2( radius,0 ); for( int i = 0 ; i * perRadian < 0.19f * Mathf.PI * 2 ; i ++ ) { float endRadian = startRadian + perRadian; Vector2 endPoint = new Vector2(Matfh.Cos(startRadian),Mathf.Sin(endRadian)); startRadian = endRadian; startPoint = endPoint; //如上我们就可以利用startPoint点和endPoint和原点绘制出一个三角形,多个三角形的"积分"就是扇形了 }
1.4 实现饼图空心的原理是,绘制时不与原点组合三角面,原理见图2

绘制不在是与中心点直接构成三角形面进行绘制了,而是与小尺寸的圆的两个新交点构成了一个矩形,因此得按照两个三角形绘制
2. 文字的添加:
2.1 文字应该显示在折线的左端或者右端,计算出折线的左端或者右端的点的位置,就可以确定文字的位置,并且确定文字应该左对齐,还是右对齐

//如果需要绘制百分比的文字,则需要我们在绘制扇形的时候记录下扇形中点所对应的弧度,开始绘制的第一个其实弧度加上扇形弧度的1/2 float middleRadian = startRadian + radian / 2.0f; Vector2 point = new Vector2(Mathf.Cos(middleRadian),Mathf.Sin(middleRadian)) * radius; //这时我们还需要记录下它应该靠左显示还是靠右显示,所有相对于中心点(0,0)为正在右,反则在左。
2.2 折线的绘制应该在扇形弧度的中间值处取一点(就是在2.1中获取的中点),然后在同一方向的某个距离上取的第二个点,然后在第二个点的同一水平线上取第三个点来绘制折线
Vector2 secondPoint = new Vector2(Mathf.Cos(middleRadian),Mathf.Sin(middleRadian)) * (radius + brokenLineLenght); //第三个点可以根据是在左侧或者是右侧直接水平挪动一定的值即可,比如在右侧 Vector2 thridPoint = secondPoint + new Vector2(extralenght,0); //同理三个点,两两绘制直线即可
2.3 文字自适应的方法在函数图篇中已经讲解过,方法在代码中也有,不在赘述。
绘制圆的代码如下(有注释):
public class PieData { public float Percent; public Color colorF; public PieData() { } public PieData(float percent,Color color0) { Percent = percent/100.0f; colorF = color0; } public PieData(float percent) { Percent = percent / 100.0f; colorF = Color.white * percent; } } public class Pies { public IList<PieData> PieDatas = new List<PieData>(); public Pies() { } public Pies(IList<PieData> pieDatas) { PieDatas = pieDatas; } public void AddPieData(Pies pies) { foreach (PieData pieData in pies.PieDatas) PieDatas.Add(pieData); } } public class PipeGraphic : MaskableGraphic { private Pies pie = new Pies(); //private List<PieText> pieText = null; [Range(0, 15)]public float BoomDegree = 1.5f; [Range(20, 100)]public float Smooth = 100; [Header("Percent Text Setting")] public bool IsShowPercnet = true; [Range(20,150)] public float holoWidth = 25; [Range(3,100)] public float radius = 3; public void Inject(IList<float> percents,IList<Color> colors) { IList<PieData> piesdatas = new List<PieData>(); for(int i=0;i<percents.Count;i++) { piesdatas.Add(new PieData(percents[i],colors[i])); } Inject(piesdatas); } public void Inject(IList<PieData> pieData) { Inject(new Pies(pieData)); } public void Inject(Pies pies) { pie.AddPieData(pies); } protected override void OnPopulateMesh(VertexHelper vh) { if (pie == null) return; vh.Clear(); vh.AddUIVertexTriangleStream(DrawPie()); } private List<UIVertex> DrawPie() { if (pie == null || pie.PieDatas.Count <= 0) return new List<UIVertex>(); //if (IsShowPercnet) //pieText = new List<PieText>(); List<UIVertex> vertexs = new List<UIVertex>(); float perRadian = Mathf.PI * 2 / Smooth; //Smooth个圆弧,每一个所占比例 float totalRadian = 0; float boomRadian = BoomDegree * Mathf.PI / 180; //间隙所占圆的百分比 float pieRadianBase =Mathf.PI*2- boomRadian*pie.PieDatas.Count; //要绘制圆的总比例=2π-间隙所占百分比 for(int i=0;i<pie.PieDatas.Count;i++) { PieData data = pie.PieDatas[i]; float endRadian = totalRadian + pieRadianBase * data.Percent + boomRadian; //当前总的弧长+数据所占弧长+间隙弧长=一个数据的最大弧长 for(float r=totalRadian+boomRadian;r<endRadian;r+=perRadian) { //这里r是弧度对应圆心角度 Vector2 first = new Vector2(Mathf.Cos(r),Mathf.Sin(r))*holoWidth; //从圆的右边开始绘制(第一个点坐标在圆心的右边) Vector2 second = new Vector2(Mathf.Cos(r+perRadian),Mathf.Sin(r+perRadian))*holoWidth; Vector2 third = new Vector2(Mathf.Cos(r),Mathf.Sin(r))*radius; //半径*(角度的余弦,正弦)即是横纵坐标值 Vector2 four = new Vector2(Mathf.Cos(r+perRadian),Mathf.Sin(r+perRadian))*radius; vertexs.Add(GetUIVertex(first,data.colorF)); vertexs.Add(GetUIVertex(second,data.colorF)); vertexs.Add(GetUIVertex(third, data.colorF)); vertexs.Add(GetUIVertex(third,data.colorF)); vertexs.Add(GetUIVertex(second, data.colorF)); vertexs.Add(GetUIVertex(four,data.colorF));//(四个顶点)两个三角形拼接成一个矩形,坐标点顺序需要注意 } totalRadian = endRadian; } return vertexs; } private UIVertex GetUIVertex(Vector3 point,Color dataColor) { UIVertex vertex = new UIVertex { position = point, color = dataColor, uv0 = new Vector2(0, 0) }; return vertex; } }
调用方法:
public PipeGraphic pipeGraph; void Start() { pipeGraph.Inject(new Pies(new List<PieData>() { new PieData(25 ,Color.blue), new PieData(25 ,Color.red), new PieData(25 ,Color.green), new PieData(25 ,Color.yellow) })); }
接下来是绘制饼状数据图完整代码,调用方法同上:
//Pie Base Data ,use this data to build Pie Graph [System.Serializable] public class PieData { public float Percent; public Color Color; public PieData(){} public PieData(float percent,Color color0) { Percent = percent / 100.0f; Color = color0; } public PieData(float percent) { Percent = percent / 100.0f; // auto set color by percent Color = Color.white * Percent; } }; //Use this data struct to draw a Pie Graph [System.Serializable] public class Pies { public IList<PieData> PieDatas = new List<PieData>(); public Pies(){} public Pies( IList<PieData> pieDatas ) { PieDatas = pieDatas; } public void AddPieData( Pies piedatas ) { foreach (PieData pieData in piedatas.PieDatas ) PieDatas.Add(pieData); } }; //GUI Text information for pie Graph [System.Serializable] public class PieText { public string Content = null; public Vector2 Position; public bool IsLeft = true; public PieText(){} public PieText( string content,Vector2 position ,bool isLeft ) { Content = content; Position = position; IsLeft = isLeft; } } // core class public class PieGraph : MaskableGraphic { [Header("Pie Base Setting")] [Range(5 , 150)]public float PieRadius = 60.0f; [Range(0 , 120)]public float HollowWidth = 0.0f; [Range(0, 15)] public float BoomDegree = 1.5f; [Range(20, 200)] public float Smooth = 100; [Header("Percent Text Setting")] public bool IsShowPercnet = true; [Range(0.5f, 4)] public float BrokenLineWidth = 2; private Pies PieData = new Pies(); private List<PieText> _pieText = null; private Vector3 _realPosition; public void Inject( IList<float> percents,IList<Color> colors ) { IList<PieData> piedatas= new List<PieData>(); for ( int i = 0 ; i < percents.Count ; i++ ) piedatas.Add(new PieData(percents[i] , colors[i])); Inject(piedatas); } public void Inject( IList<float> percents ) { IList<PieData> piedatas = new List<PieData>(); foreach (float percent in percents) piedatas.Add(new PieData(percent)); Inject(piedatas); } public void Inject( IList<PieData> pieData ) { Inject(new Pies(pieData)); } public void Inject( Pies pies ) { PieData.AddPieData(pies); } #region draw pie private void OnGUI() { if ( null == _pieText ) return; if (!IsShowPercnet) return; Vector3 result = transform.localPosition; _realPosition = getScreenPosition(transform , ref result); foreach ( PieText text in _pieText ) { Vector2 position = local2Screen(_realPosition , text.Position); GUIStyle guiStyle = new GUIStyle(); guiStyle.normal.textColor = Color.black; guiStyle.fontStyle = FontStyle.Bold; guiStyle.alignment = TextAnchor.MiddleLeft; if ( !text.IsLeft ) guiStyle.alignment = TextAnchor.MiddleRight; if(text.IsLeft) position += new Vector2(3,-10); else position += new Vector2(-23,-10); GUI.Label(new Rect(position , new Vector2(20 , 20)) , text.Content , guiStyle); } } //draw pie core method protected override void OnPopulateMesh(VertexHelper vh) { if ( null == PieData ) return; vh.Clear(); vh.AddUIVertexTriangleStream(DrawPie()); } private List<UIVertex> DrawPie() { if (PieData == null || PieData.PieDatas.Count <= 0) return new List<UIVertex>(); if (IsShowPercnet) _pieText = new List<PieText>(); List<UIVertex> vertexs = new List<UIVertex>(); float perRadian = Mathf.PI * 2 / Smooth; float totalRadian = 0; float boomRadian = BoomDegree * Mathf.PI / 180 ; float pieRadianBase = Mathf.PI * 2 - boomRadian * PieData.PieDatas.Count; for ( int i = 0 ; i < PieData.PieDatas.Count ; i++ ) { PieData data = PieData.PieDatas[i]; float endRadian = boomRadian + data.Percent * pieRadianBase + totalRadian; for ( float r = boomRadian + totalRadian ; r < endRadian ; r += perRadian ) { Vector2 first = new Vector2(Mathf.Cos(r) , Mathf.Sin(r)) * HollowWidth; Vector2 second = new Vector2(Mathf.Cos(r + perRadian) , Mathf.Sin(r + perRadian)) * HollowWidth; Vector2 third = new Vector2(Mathf.Cos(r) , Mathf.Sin(r)) * PieRadius; Vector2 four = new Vector2(Mathf.Cos(r + perRadian) , Mathf.Sin(r + perRadian)) * PieRadius; vertexs.Add(GetUIVertex(first , data.Color)); vertexs.Add(GetUIVertex(third , data.Color)); vertexs.Add(GetUIVertex(second , data.Color)); vertexs.Add(GetUIVertex(second , data.Color)); vertexs.Add(GetUIVertex(third , data.Color)); vertexs.Add(GetUIVertex(four , data.Color)); } #region ShowPercnet if (IsShowPercnet) { float middleRadian = boomRadian + data.Percent * pieRadianBase / 2 + totalRadian; float brokenLineLength = PieRadius * 0.2f; Vector2 middlePoint = new Vector2(Mathf.Cos(middleRadian), Mathf.Sin(middleRadian)) * PieRadius; Vector2 secondPoint = middlePoint + new Vector2(Mathf.Cos(middleRadian), Mathf.Sin(middleRadian)) * brokenLineLength; Vector2 first = middlePoint + new Vector2(Mathf.Sin(middleRadian), -Mathf.Cos(middleRadian)) * BrokenLineWidth / 2; Vector2 second = middlePoint + new Vector2(-Mathf.Sin(middleRadian), Mathf.Cos(middleRadian)) * BrokenLineWidth / 2; Vector2 third = secondPoint + new Vector2(Mathf.Sin(middleRadian), -Mathf.Cos(middleRadian)) * BrokenLineWidth / 2; Vector2 four = secondPoint + new Vector2(-Mathf.Sin(middleRadian), Mathf.Cos(middleRadian)) * BrokenLineWidth / 2; Vector2 five; Vector2 six; if (middleRadian > Mathf.PI / 2 && middleRadian < Mathf.PI * 3 / 2) { five = third + new Vector2(-brokenLineLength, 0); six = four + new Vector2(-brokenLineLength, 0); // right text _pieText.Add(new PieText(data.Percent * 100 + "%", six, false)); } else { five = third + new Vector2(brokenLineLength, 0); six = four + new Vector2(brokenLineLength, 0); // left text _pieText.Add(new PieText(data.Percent * 100 + "%", six, true)); } vertexs.Add(GetUIVertex(first, data.Color)); vertexs.Add(GetUIVertex(second, data.Color)); vertexs.Add(GetUIVertex(third, data.Color)); vertexs.Add(GetUIVertex(third, data.Color)); vertexs.Add(GetUIVertex(second, data.Color)); vertexs.Add(GetUIVertex(four, data.Color)); vertexs.Add(GetUIVertex(third, data.Color)); vertexs.Add(GetUIVertex(four, data.Color)); vertexs.Add(GetUIVertex(five, data.Color)); vertexs.Add(GetUIVertex(five, data.Color)); vertexs.Add(GetUIVertex(four, data.Color)); vertexs.Add(GetUIVertex(six, data.Color)); } #endregion totalRadian = endRadian; } return vertexs; } #endregion #region draw helper methods private UIVertex GetUIVertex( Vector2 point , Color color0 ) { UIVertex vertex = new UIVertex { position = point , color = color0 , uv0 = new Vector2(0 , 0) }; return vertex; } private Vector2 local2Screen( Vector2 parentPos,Vector2 localPosition ) { Vector2 pos = localPosition + parentPos; float xValue, yValue = 0; if ( pos.x > 0 ) xValue = pos.x + Screen.width / 2.0f; else xValue = Screen.width / 2.0f - Mathf.Abs(pos.x); if ( pos.y > 0 ) yValue = Screen.height / 2.0f - pos.y; else yValue = Screen.height / 2.0f + Mathf.Abs(pos.y); return new Vector2(xValue,yValue); } private Vector2 getScreenPosition( Transform trans, ref Vector3 result ) { if ( null != trans.parent && null != trans.parent.parent ) { result += trans.parent.localPosition; getScreenPosition(trans.parent , ref result); } if ( null != trans.parent && null == trans.parent.parent ) return result; return result; } #endregion }