Unity自定义线段长度、角度、内外轮廓及颜色的雷达图

效果演示如下:

功能简述:

  • 显示隐藏内轮廓线、外轮廓线、中心点。
  • 自定义内外轮廓、线的宽度。
  • 自定义中心点的颜色和半径。
  • 每条线段的最大值及当前值(0~1f)。
  • 每条线段对应的区块的角度范围。
  • 运行中动态刷新数据及显示。
  • 整体的旋转角度(RectTransform控制即可)。
  • 所有参数均有中文注释来替换显示。

脚本如下:

主脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// ****************************************************************** 
//@file RadarChart.cs
//@brief 自定义的雷达图UI
//@author yufulao, yufulao@qq.com
//@createTime 2025.01.14 17:41:48
// ******************************************************************

using UnityEngine.UI;
using UnityEngine;

[RequireComponent(typeof(CanvasRenderer))]
public class RadarChart : MaskableGraphic
{
[SerializeField] private bool showOutline; //是否显示外轮廓
[SerializeField] private Color outlineColor; //外轮廓颜色
[SerializeField] private float outlineWidth; //外轮廓宽度
[SerializeField] private bool showInline; //是否显示内轮廓
[SerializeField] private Color inlineColor; //内轮廓颜色
[SerializeField] private float inlineWidth; //内轮廓宽度
[SerializeField] private bool showCenterPoint; //是否显示中心点
[SerializeField] private Color centerPointColor; //中心点颜色
[SerializeField] private float centerPointRadius; //中心点半径
[SerializeField] private RadarData[] dataArray; //线段数据


[System.Serializable]
public class RadarData
{
[Range(0f, 1f)] public float value = 1f; //当前值
public float angle; //区块角度
public float maxLength = 40f; //线段总长度
}

/// <summary>
/// 重绘
/// </summary>
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
if (dataArray == null || dataArray.Length < 3)
{
return;
}

var center = Vector2.zero;
var vertices = new Vector2[dataArray.Length];
for (var i = 0; i < dataArray.Length; i++)
{
var radian = dataArray[i].angle * Mathf.Deg2Rad;
var distance = Mathf.Lerp(0, dataArray[i].maxLength, dataArray[i].value);
vertices[i] = new Vector2(Mathf.Cos(radian) * distance, Mathf.Sin(radian) * distance);
}

for (var i = 0; i < dataArray.Length; i++) //填充区域
{
var nextIndex = (i + 1) % dataArray.Length;
AddTriangle(vh, center, vertices[i], vertices[nextIndex], color);
}

if (showOutline) //绘制外轮廓
{
DrawOuterOutline(vh, vertices, outlineColor, outlineWidth);
}

if (showInline) //绘制内部边
{
for (var i = 0; i < dataArray.Length; i++)
{
DrawLine(vh, center, vertices[i], inlineColor, inlineWidth);
}
}

if (showCenterPoint) //绘制中心点
{
DrawCenterPoint(vh, center, centerPointColor, centerPointRadius);
}
}

/// <summary>
/// 刷新数据
/// </summary>
public void UpdateRadarData(RadarData[] newData)
{
if (newData == null || newData.Length < 3)
{
Debug.LogError("非法数据");
return;
}

dataArray = newData;
SetVerticesDirty();
}

/// <summary>
/// 刷新数据
/// </summary>
public void UpdateRadarData(int index, float value)
{
if (index >= dataArray.Length)
{
Debug.LogError("越界");
return;
}

var data = dataArray[index];
data.value = value;
SetVerticesDirty();
}

/// <summary>
/// 添加三角形
/// </summary>
private static void AddTriangle(VertexHelper vh, Vector2 v0, Vector2 v1, Vector2 v2, Color color)
{
var vert0 = UIVertex.simpleVert;
var vert1 = UIVertex.simpleVert;
var vert2 = UIVertex.simpleVert;
vert0.color = color;
vert1.color = color;
vert2.color = color;
vert0.position = v0;
vert1.position = v1;
vert2.position = v2;
vh.AddVert(vert0);
vh.AddVert(vert1);
vh.AddVert(vert2);

var index = vh.currentVertCount;
vh.AddTriangle(index - 3, index - 2, index - 1);
}

/// <summary>
/// 绘制外轮廓
/// </summary>
private static void DrawOuterOutline(VertexHelper vh, Vector2[] vertices, Color colorT, float width)
{
for (var i = 0; i < vertices.Length; i++)
{
var nextIndex = (i + 1) % vertices.Length;
DrawLine(vh, vertices[i], vertices[nextIndex], colorT, width);
}
}

/// <summary>
/// 绘制线段
/// </summary>
private static void DrawLine(VertexHelper vh, Vector2 start, Vector2 end, Color colorT, float width)
{
var direction = (end - start).normalized;
var perpendicular = new Vector2(-direction.y, direction.x) * width * 0.5f;

var v0 = start - perpendicular;
var v1 = start + perpendicular;
var v2 = end + perpendicular;
var v3 = end - perpendicular;
var vert0 = UIVertex.simpleVert;
var vert1 = UIVertex.simpleVert;
var vert2 = UIVertex.simpleVert;
var vert3 = UIVertex.simpleVert;
vert0.color = colorT;
vert1.color = colorT;
vert2.color = colorT;
vert3.color = colorT;
vert0.position = v0;
vert1.position = v1;
vert2.position = v2;
vert3.position = v3;
vh.AddVert(vert0);
vh.AddVert(vert1);
vh.AddVert(vert2);
vh.AddVert(vert3);

var index = vh.currentVertCount;
vh.AddTriangle(index - 4, index - 3, index - 2);
vh.AddTriangle(index - 4, index - 2, index - 1);
}

/// <summary>
/// 绘制中心点
/// </summary>
private static void DrawCenterPoint(VertexHelper vh, Vector2 center, Color color, float radius)
{
var prevPoint = center + new Vector2(radius, 0);
for (var i = 1; i <= 20; i++)
{
var angle = i * 20 * Mathf.Deg2Rad; //20段画圆
var nextPoint = center + new Vector2(Mathf.Cos(angle) * radius, Mathf.Sin(angle) * radius);
AddTriangle(vh, center, prevPoint, nextPoint, color);
prevPoint = nextPoint;
}
}
}

Editor文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// ******************************************************************
//@file RadarChartEditor.cs
//@brief RadarChart的Editor类
//@author yufulao, yufulao@qq.com
//@createTime 2025.01.18 15:55:08
// ******************************************************************

using UnityEditor;
using UnityEditor.UI;
using UnityEngine;

[CustomEditor(typeof(RadarChart))]
public class RadarChartEditor : GraphicEditor
{
private SerializedProperty _showOutline;
private SerializedProperty _showInline;
private SerializedProperty _showCenterPoint;
private SerializedProperty _outlineColor;
private SerializedProperty _inlineColor;
private SerializedProperty _centerPointColor;
private SerializedProperty _outlineWidth;
private SerializedProperty _inlineWidth;
private SerializedProperty _centerPointRadius;
private SerializedProperty _dataArray;

protected override void OnEnable()
{
base.OnEnable();
_showOutline = serializedObject.FindProperty("showOutline");
_showInline = serializedObject.FindProperty("showInline");
_showCenterPoint = serializedObject.FindProperty("showCenterPoint");
_outlineColor = serializedObject.FindProperty("outlineColor");
_inlineColor = serializedObject.FindProperty("inlineColor");
_centerPointColor = serializedObject.FindProperty("centerPointColor");
_outlineWidth = serializedObject.FindProperty("outlineWidth");
_inlineWidth = serializedObject.FindProperty("inlineWidth");
_centerPointRadius = serializedObject.FindProperty("centerPointRadius");
_dataArray = serializedObject.FindProperty("dataArray");
}

/// <summary>
/// 重绘
/// </summary>
public override void OnInspectorGUI()
{
serializedObject.Update();
//GraphicEditor
AppearanceControlsGUI();
RaycastControlsGUI();
MaskableControlsGUI();

//自定义
DrawLine();
EditorGUILayout.PropertyField(_showOutline, new GUIContent("是否显示外轮廓"));
if (_showOutline.boolValue)
{
EditorGUILayout.PropertyField(_outlineColor, new GUIContent("外轮廓颜色"));
EditorGUILayout.PropertyField(_outlineWidth, new GUIContent("外轮廓宽度"));
}

EditorGUILayout.PropertyField(_showInline, new GUIContent("是否显示内轮廓"));
if (_showInline.boolValue)
{
EditorGUILayout.PropertyField(_inlineColor, new GUIContent("内轮廓颜色"));
EditorGUILayout.PropertyField(_inlineWidth, new GUIContent("内轮廓宽度"));
}

EditorGUILayout.PropertyField(_showCenterPoint, new GUIContent("是否显示中心点"));
if (_showCenterPoint.boolValue)
{
EditorGUILayout.PropertyField(_centerPointColor, new GUIContent("中心点颜色"));
EditorGUILayout.PropertyField(_centerPointRadius, new GUIContent("中心点半径"));
}

EditorGUILayout.PropertyField(_dataArray, new GUIContent("线段数据"), true);
serializedObject.ApplyModifiedProperties();
}

/// <summary>
/// 绘制分割线
/// </summary>
private static void DrawLine()
{
EditorGUILayout.Space();
var rect = EditorGUILayout.GetControlRect(false, 1);
EditorGUI.DrawRect(rect, Color.gray);
EditorGUILayout.Space();
}
}

/// <summary>
/// 绘制RadarData的字段注释
/// </summary>
[CustomPropertyDrawer(typeof(RadarChart.RadarData))]
public class RadarDataDrawer : PropertyDrawer
{
private const float Space = 5f;

/// <summary>
/// 重绘
/// </summary>
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var value = property.FindPropertyRelative("value");
var angle = property.FindPropertyRelative("angle");
var maxLength = property.FindPropertyRelative("maxLength");

//分配区域
var lineHeight = EditorGUIUtility.singleLineHeight;
var valueRect = new Rect(position.x, position.y, position.width, lineHeight);
var angleRect = new Rect(position.x, position.y + lineHeight + Space, position.width, lineHeight);
var maxLengthRect = new Rect(position.x, position.y + (lineHeight + Space) * 2, position.width, lineHeight);

//绘制
EditorGUI.PropertyField(valueRect, value, new GUIContent("当前值"));
EditorGUI.PropertyField(angleRect, angle, new GUIContent("区块角度"));
EditorGUI.PropertyField(maxLengthRect, maxLength, new GUIContent("线段总长度"));
}

/// <summary>
/// 控制字段的高度
/// </summary>
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return (EditorGUIUtility.singleLineHeight + Space) * 3; //三个字段,每个字段需要一行加间距
}
}

(无聊的小玩具+1