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 }