UI update 02

This commit is contained in:
2026-06-30 21:21:30 +08:00
parent b2a1e307a4
commit f62f643cfc
1666 changed files with 211081 additions and 22799 deletions
@@ -1,12 +1,16 @@
using System.Collections.Generic;
using EasyChart;
using EasyChart.UGUI;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.UIElements;
using XCharts.Runtime;
public class URadarChartController : MonoBehaviour
{
[Header("Chart")]
[Header("Legacy Compatibility")]
public UGUIChartBridge chartBridge;
[Header("Chart")]
public bool rebuildOnEnable = true;
public bool showReferenceSeries = false;
public int enforcedSortOrder = 0;
@@ -62,6 +66,11 @@ public class URadarChartController : MonoBehaviour
[Header("Style")]
public bool overrideSeriesStyle = true;
public bool overrideSymbolStyle = true;
public float mainSymbolSize = 8f;
public float referenceSymbolSize = 7f;
public SymbolType mainSymbolType = SymbolType.Circle;
public SymbolType referenceSymbolType = SymbolType.Circle;
public Color mainStrokeColor = new Color32(82, 126, 255, 255);
public Color mainFillColor = new Color(82f / 255f, 126f / 255f, 255f / 255f, 0.28f);
public Color mainPointColor = Color.white;
@@ -72,15 +81,34 @@ public class URadarChartController : MonoBehaviour
[Min(0f)] public float radarInnerRadius = 0f;
[Min(0f)] public float radarLabelRadialOffset = 0f;
private ChartProfile sourceProfile;
private ChartProfile runtimeProfile;
private ChartTheme runtimeTheme;
[Header("Legacy Serialized Fields")]
[SerializeField] private UIRadarGraphic radarGraphic;
[SerializeField] private RectTransform labelRoot;
[SerializeField] private bool autoCreateLabelRoot = true;
[SerializeField] private string labelRootName = "RadarLabels";
[Header("XCharts Runtime")]
[SerializeField] private RectTransform radarChartHost;
[SerializeField] private string radarChartHostName = "RadarChartHost";
[SerializeField] private RadarChart radarChart;
private bool rebuildQueued;
private bool chartInitialized;
public bool IsRendererReady
{
get
{
EnsureRenderer();
return radarChart != null;
}
}
private void Awake()
{
showReferenceSeries = false;
NormalizeAxes();
EnsureRenderer();
QueueRebuild();
}
@@ -88,6 +116,7 @@ public class URadarChartController : MonoBehaviour
{
showReferenceSeries = false;
NormalizeAxes();
EnsureRenderer();
if (rebuildOnEnable)
{
QueueRebuild();
@@ -111,27 +140,15 @@ public class URadarChartController : MonoBehaviour
gridLineWidth = Mathf.Max(0.1f, gridLineWidth);
radarInnerRadius = Mathf.Max(0f, radarInnerRadius);
radarLabelRadialOffset = Mathf.Max(0f, radarLabelRadialOffset);
mainSymbolSize = Mathf.Max(0f, mainSymbolSize);
referenceSymbolSize = Mathf.Max(0f, referenceSymbolSize);
tooltipBorderWidth = Mathf.Max(0f, tooltipBorderWidth);
tooltipCornerRadius = Mathf.Max(0f, tooltipCornerRadius);
axisCount = Mathf.Max(3, axisCount);
EnsureRenderer(false);
QueueRebuild();
}
private void OnDestroy()
{
if (runtimeProfile != null)
{
DestroyImmediate(runtimeProfile);
runtimeProfile = null;
}
if (runtimeTheme != null)
{
DestroyImmediate(runtimeTheme);
runtimeTheme = null;
}
}
public void QueueRebuild()
{
rebuildQueued = true;
@@ -203,6 +220,35 @@ public class URadarChartController : MonoBehaviour
NormalizeAxes();
}
public void EnsureRenderer(bool initializeChart = true)
{
DisableLegacyRenderer();
EnsureChartHost();
if (radarChart == null)
{
radarChart = radarChartHost != null ? radarChartHost.GetComponent<RadarChart>() : null;
}
if (radarChart == null && radarChartHost != null)
{
radarChart = radarChartHost.gameObject.AddComponent<RadarChart>();
}
if (radarChart != null)
{
radarChart.enabled = true;
radarChart.gameObject.SetActive(true);
}
if (initializeChart)
{
EnsureChartInitialized();
}
EnsureRuntimeChartStyle();
}
private bool TryGetAxis(int index, out URadarAxisEntry axis)
{
NormalizeAxes();
@@ -218,243 +264,368 @@ public class URadarChartController : MonoBehaviour
private void TryRebuild()
{
if (chartBridge == null || chartBridge.Profile == null)
EnsureRenderer();
if (radarChart == null)
{
return;
}
if (sourceProfile == null)
{
sourceProfile = chartBridge.Profile;
}
if (sourceProfile == null)
{
return;
}
EnsureRuntimeProfile();
EnsureBridgePriority();
ConfigureRuntimeProfile();
chartBridge.Refresh();
ApplyRuntimeTheme();
ApplyRendererState();
rebuildQueued = false;
}
private void EnsureRuntimeProfile()
{
if (runtimeProfile != null)
{
if (!ReferenceEquals(chartBridge.Profile, runtimeProfile))
{
chartBridge.Profile = runtimeProfile;
}
return;
}
runtimeProfile = Instantiate(sourceProfile);
runtimeProfile.name = sourceProfile.name + "_URadar_Runtime";
runtimeProfile.hideFlags = HideFlags.DontSave;
chartBridge.Profile = runtimeProfile;
}
private void EnsureBridgePriority()
{
if (chartBridge != null && chartBridge.SortOrder < enforcedSortOrder)
{
chartBridge.SortOrder = enforcedSortOrder;
}
}
private void ConfigureRuntimeProfile()
private void ApplyRendererState()
{
NormalizeAxes();
EnsureChartInitialized();
runtimeProfile.coordinateSystem = CoordinateSystemType.Polar2D;
runtimeProfile.EnsureRuntimeData();
radarChart.RemoveChartComponents<RadarCoord>();
radarChart.RemoveData();
runtimeProfile.polarAxes.angleAxis.labels = BuildLabels();
runtimeProfile.polarAxes.angleAxis.visible = true;
runtimeProfile.polarAxes.angleAxis.showLabels = true;
if (runtimeProfile.polarAxes.angleAxis.labelStyle == null)
RadarCoord radarCoord = radarChart.AddChartComponent<RadarCoord>();
if (radarCoord == null)
{
runtimeProfile.polarAxes.angleAxis.labelStyle = new LabelStyleSettings();
}
runtimeProfile.polarAxes.angleAxis.labelStyle.enabled = true;
runtimeProfile.polarAxes.radiusAxis.visible = true;
runtimeProfile.polarAxes.radiusAxis.autoRangeMin = false;
runtimeProfile.polarAxes.radiusAxis.autoRangeMax = false;
runtimeProfile.polarAxes.radiusAxis.minValue = minValue;
runtimeProfile.polarAxes.radiusAxis.maxValue = Mathf.Max(minValue + 1f, maxValue);
runtimeProfile.polarAxes.radiusAxis.splitCount = Mathf.Max(2, splitCount);
if (runtimeProfile.polarAxes.radiusAxis.labelStyle == null)
{
runtimeProfile.polarAxes.radiusAxis.labelStyle = new LabelStyleSettings();
}
runtimeProfile.polarAxes.radiusAxis.labelStyle.enabled = runtimeProfile.polarAxes.radiusAxis.showLabels;
if (overrideGridStyle)
{
runtimeProfile.polarAxes.angleAxis.color = gridLineColor;
runtimeProfile.polarAxes.angleAxis.width = gridLineWidth;
runtimeProfile.polarAxes.radiusAxis.color = gridLineColor;
runtimeProfile.polarAxes.radiusAxis.edgeColor = outerGridLineColor;
runtimeProfile.polarAxes.radiusAxis.width = gridLineWidth;
return;
}
if (overrideLabelStyle)
{
runtimeProfile.polarAxes.angleAxis.labelColor = axisLabelColor;
runtimeProfile.polarAxes.angleAxis.fontSize = axisLabelFontSize;
runtimeProfile.polarAxes.angleAxis.labelOffset = axisLabelOffset;
runtimeProfile.polarAxes.angleAxis.labelStyle.fontSize = axisLabelFontSize;
runtimeProfile.polarAxes.angleAxis.labelStyle.color = axisLabelColor;
runtimeProfile.polarAxes.angleAxis.labelStyle.offset = axisLabelOffset;
radarCoord.indicatorList.Clear();
runtimeProfile.polarAxes.radiusAxis.labelColor = axisLabelColor;
runtimeProfile.polarAxes.radiusAxis.fontSize = axisLabelFontSize;
runtimeProfile.polarAxes.radiusAxis.labelStyle.fontSize = axisLabelFontSize;
runtimeProfile.polarAxes.radiusAxis.labelStyle.color = axisLabelColor;
float clampedMin = Mathf.Min(minValue, maxValue - 0.0001f);
float clampedMax = Mathf.Max(minValue + 0.0001f, maxValue);
for (int i = 0; i < axes.Count; i++)
{
string label = string.IsNullOrWhiteSpace(axes[i].label) ? $"Axis {i + 1}" : axes[i].label.Trim();
radarCoord.AddIndicator(label, clampedMin, clampedMax);
}
if (runtimeProfile.series == null)
ConfigureRadarCoord(radarCoord);
Radar mainSerie = radarChart.AddSerie<Radar>(mainSeriesName);
if (mainSerie != null)
{
runtimeProfile.series = new List<Serie>();
mainSerie.radarIndex = radarCoord.index;
}
ConfigureSerie(mainSerie, true);
if (mainSerie != null)
{
radarChart.AddData(mainSerie.index, BuildDataValues(false), mainSeriesName);
}
int targetSeriesCount = 1;
while (runtimeProfile.series.Count < targetSeriesCount)
if (showReferenceSeries)
{
runtimeProfile.series.Add(new Serie());
Radar referenceSerie = radarChart.AddSerie<Radar>(referenceSeriesName);
if (referenceSerie != null)
{
referenceSerie.radarIndex = radarCoord.index;
}
ConfigureSerie(referenceSerie, false);
if (referenceSerie != null)
{
radarChart.AddData(referenceSerie.index, BuildDataValues(true), referenceSeriesName);
}
}
while (runtimeProfile.series.Count > targetSeriesCount)
{
runtimeProfile.series.RemoveAt(runtimeProfile.series.Count - 1);
}
ConfigureSeries(runtimeProfile.series[0], mainSeriesName, true);
runtimeProfile.EnsureRuntimeData();
RefreshLegendAndTooltip();
radarChart.RefreshChart();
}
private void ApplyRuntimeTheme()
private void ConfigureRadarCoord(RadarCoord radarCoord)
{
if (chartBridge == null || chartBridge.ChartElement == null)
RectTransform rectTransform = transform as RectTransform;
float minRectSize = rectTransform == null ? 0f : Mathf.Min(rectTransform.rect.width, rectTransform.rect.height);
float reservedRadius = Mathf.Max(0f, radarPlotPadding);
float computedRadius = minRectSize > 0f
? Mathf.Max(0f, (minRectSize * 0.5f) - reservedRadius - Mathf.Max(0f, radarLabelRadialOffset))
: 0f;
float xchartsRadius = minRectSize > 0f ? Mathf.Clamp01(computedRadius / minRectSize) : 0.35f;
if (xchartsRadius <= 0f)
{
return;
xchartsRadius = 0.35f;
}
bool useTooltipStyle = overrideTooltipStyle;
bool useTooltipFont = overrideTooltipFont && tooltipFont != null;
bool useLabelFont = overrideLabelFont && axisLabelFont != null;
radarCoord.show = true;
radarCoord.shape = RadarCoord.Shape.Polygon;
radarCoord.splitNumber = Mathf.Max(2, splitCount);
radarCoord.center[0] = 0.5f;
radarCoord.center[1] = 0.5f;
radarCoord.radius = xchartsRadius;
radarCoord.indicator = true;
radarCoord.indicatorGap = radarLabelRadialOffset;
radarCoord.positionType = RadarCoord.PositionType.Vertice;
radarCoord.isAxisTooltip = false;
radarCoord.startAngle = 0f;
if (!useLabelFont && !useTooltipFont && !useTooltipStyle)
{
chartBridge.ChartElement.Theme = null;
return;
}
Color innerGrid = overrideGridStyle ? gridLineColor : new Color32(255, 255, 255, 180);
Color outerGrid = overrideGridStyle ? outerGridLineColor : Color.white;
Color effectiveLabelColor = overrideLabelStyle ? axisLabelColor : Color.white;
Vector2 effectiveLabelOffset = overrideLabelStyle ? axisLabelOffset : Vector2.zero;
Font effectiveLabelFont = overrideLabelFont && axisLabelFont != null ? axisLabelFont : axisLabelFont;
if (runtimeTheme == null)
{
runtimeTheme = ScriptableObject.CreateInstance<ChartTheme>();
runtimeTheme.name = "URadar_RuntimeTheme";
runtimeTheme.hideFlags = HideFlags.DontSave;
}
radarCoord.axisLine.show = true;
radarCoord.axisLine.lineStyle.show = true;
radarCoord.axisLine.lineStyle.type = LineStyle.Type.Solid;
radarCoord.axisLine.lineStyle.width = gridLineWidth;
radarCoord.axisLine.lineStyle.color = innerGrid;
runtimeTheme.primaryFont = useLabelFont ? axisLabelFont : null;
runtimeTheme.axisFontSize = useLabelFont ? axisLabelFontSize : -1f;
runtimeTheme.tooltipFont = useTooltipFont ? tooltipFont : null;
runtimeTheme.tooltipFontSize = (useTooltipFont || useTooltipStyle) ? tooltipFontSize : -1f;
runtimeTheme.tooltipTextColor = useTooltipStyle ? tooltipTextColor : Color.white;
runtimeTheme.tooltipBackgroundColor = useTooltipStyle ? tooltipBackgroundColor : new Color(0f, 0f, 0f, 0.8f);
runtimeTheme.tooltipBorderColor = useTooltipStyle ? tooltipBorderColor : new Color(0f, 0f, 0f, 0f);
runtimeTheme.tooltipBorderWidth = useTooltipStyle ? tooltipBorderWidth : 0f;
runtimeTheme.tooltipCornerRadius = useTooltipStyle ? tooltipCornerRadius : 4f;
runtimeTheme.tooltipPadding = useTooltipStyle ? tooltipPadding : new Vector4(8f, 8f, 4f, 4f);
radarCoord.splitLine.show = true;
radarCoord.splitLine.lineStyle.show = true;
radarCoord.splitLine.lineStyle.type = LineStyle.Type.Solid;
radarCoord.splitLine.lineStyle.width = gridLineWidth;
radarCoord.splitLine.lineStyle.color = outerGrid;
// Force ChartElement.Theme setter to re-apply tooltip visuals even when reusing the same runtime theme instance.
chartBridge.ChartElement.Theme = null;
chartBridge.ChartElement.Theme = runtimeTheme;
radarCoord.splitArea.show = false;
radarCoord.axisName.show = true;
radarCoord.axisName.name = null;
radarCoord.axisName.labelStyle.show = true;
radarCoord.axisName.labelStyle.textStyle.autoColor = false;
radarCoord.axisName.labelStyle.textStyle.color = effectiveLabelColor;
radarCoord.axisName.labelStyle.textStyle.fontSize = axisLabelFontSize;
radarCoord.axisName.labelStyle.textStyle.font = effectiveLabelFont;
radarCoord.axisName.labelStyle.textStyle.autoAlign = false;
radarCoord.axisName.labelStyle.textStyle.alignment = TextAnchor.MiddleCenter;
radarCoord.axisName.labelStyle.offset = new Vector3(effectiveLabelOffset.x, effectiveLabelOffset.y, 0f);
radarCoord.axisName.labelStyle.width = 140f;
radarCoord.axisName.labelStyle.height = Mathf.Max(24f, axisLabelFontSize + 10f);
radarCoord.SetVerticesDirty();
}
private void ConfigureSeries(Serie serie, string serieName, bool isMainSeries)
private void ConfigureSerie(Radar serie, bool isMainSerie)
{
if (serie == null)
{
return;
}
serie.name = string.IsNullOrWhiteSpace(serieName) ? (isMainSeries ? "Player" : "Reference") : serieName.Trim();
serie.visible = true;
if (serie.type != SerieType.Radar)
serie.radarType = RadarType.Multiple;
serie.showDataName = false;
serie.symbol.show = true;
if (overrideSymbolStyle)
{
serie.SetType(SerieType.Radar);
}
if (!(serie.settings is RadarSettings radarSettings))
{
radarSettings = new RadarSettings();
serie.settings = radarSettings;
}
radarSettings.radar.innerRadius = radarInnerRadius;
radarSettings.radar.outerRadius = 0f;
radarSettings.radar.plot.padding = radarPlotPadding;
radarSettings.radar.plot.labelRadialOffset = radarLabelRadialOffset;
radarSettings.area.show = true;
radarSettings.point.show = true;
if (overrideSeriesStyle)
{
radarSettings.stroke.color = isMainSeries ? mainStrokeColor : referenceStrokeColor;
radarSettings.stroke.width = isMainSeries ? 2.5f : 2f;
radarSettings.area.textureFill.color = isMainSeries ? mainFillColor : referenceFillColor;
radarSettings.point.textureFill.color = isMainSeries ? mainPointColor : referencePointColor;
radarSettings.point.size = isMainSeries ? 8f : 7f;
}
if (serie.labelSettings != null)
{
serie.labelSettings.enabled = false;
}
if (serie.seriesData == null)
{
serie.seriesData = new List<SeriesData>();
serie.symbol.type = isMainSerie ? mainSymbolType : referenceSymbolType;
serie.symbol.size = isMainSerie ? mainSymbolSize : referenceSymbolSize;
serie.symbol.color = isMainSerie ? mainPointColor : referencePointColor;
}
else
{
serie.seriesData.Clear();
serie.symbol.type = SymbolType.Circle;
serie.symbol.size = isMainSerie ? 8f : 7f;
serie.symbol.color = Color.white;
}
for (int i = 0; i < axisCount; i++)
serie.lineStyle.show = true;
serie.lineStyle.type = LineStyle.Type.Solid;
serie.lineStyle.width = isMainSerie ? 2.5f : 2f;
serie.lineStyle.color = isMainSerie
? (overrideSeriesStyle ? mainStrokeColor : new Color32(82, 126, 255, 255))
: (overrideSeriesStyle ? referenceStrokeColor : new Color32(72, 229, 229, 255));
AreaStyle areaStyle = serie.EnsureComponent<AreaStyle>();
areaStyle.show = true;
areaStyle.color = isMainSerie
? (overrideSeriesStyle ? mainFillColor : new Color(82f / 255f, 126f / 255f, 1f, 0.28f))
: (overrideSeriesStyle ? referenceFillColor : new Color(72f / 255f, 229f / 255f, 229f / 255f, 0.22f));
areaStyle.toColor = Color.clear;
areaStyle.opacity = 1f;
LabelStyle labelStyle = serie.EnsureComponent<LabelStyle>();
labelStyle.show = false;
}
private List<double> BuildDataValues(bool useReferenceValue)
{
List<double> values = new List<double>(axes.Count);
for (int i = 0; i < axes.Count; i++)
{
URadarAxisEntry axis = axes[i];
float value = isMainSeries ? axis.value : axis.referenceValue;
serie.seriesData.Add(new SeriesData
float raw = useReferenceValue ? axes[i].referenceValue : axes[i].value;
values.Add(Mathf.Clamp(raw, minValue, maxValue));
}
return values;
}
private void RefreshLegendAndTooltip()
{
Legend legend = radarChart.GetChartComponent<Legend>();
if (legend != null)
{
legend.show = false;
legend.data.Clear();
}
Title title = radarChart.GetChartComponent<Title>();
if (title != null)
{
title.show = false;
title.text = string.Empty;
title.subText = string.Empty;
}
XCharts.Runtime.Background background = radarChart.GetChartComponent<XCharts.Runtime.Background>();
if (background != null)
{
background.show = false;
}
Tooltip tooltip = radarChart.GetChartComponent<Tooltip>();
if (tooltip == null)
{
return;
}
tooltip.show = false;
tooltip.showContent = false;
tooltip.type = Tooltip.Type.None;
tooltip.trigger = Tooltip.Trigger.None;
if (overrideTooltipStyle)
{
tooltip.backgroundColor = tooltipBackgroundColor;
tooltip.borderColor = tooltipBorderColor;
tooltip.borderWidth = tooltipBorderWidth;
tooltip.paddingLeftRight = Mathf.RoundToInt(Mathf.Max(0f, tooltipPadding.x));
tooltip.paddingTopBottom = Mathf.RoundToInt(Mathf.Max(0f, tooltipPadding.z));
}
if (overrideTooltipFont)
{
tooltip.titleLabelStyle.textStyle.font = tooltipFont;
tooltip.titleLabelStyle.textStyle.fontSize = tooltipFontSize;
tooltip.titleLabelStyle.textStyle.color = tooltipTextColor;
for (int i = 0; i < tooltip.contentLabelStyles.Count; i++)
{
id = (isMainSeries ? "main_" : "ref_") + i,
name = axis.label,
x = i,
value = Mathf.Clamp(value, minValue, maxValue)
});
tooltip.contentLabelStyles[i].textStyle.font = tooltipFont;
tooltip.contentLabelStyles[i].textStyle.fontSize = tooltipFontSize;
tooltip.contentLabelStyles[i].textStyle.color = tooltipTextColor;
}
}
}
private List<string> BuildLabels()
private void EnsureChartInitialized()
{
List<string> labels = new List<string>(axisCount);
for (int i = 0; i < axisCount; i++)
if (radarChart == null)
{
string label = axes[i] != null ? axes[i].label : null;
labels.Add(string.IsNullOrWhiteSpace(label) ? $"Axis {i + 1}" : label.Trim());
return;
}
return labels;
if (!chartInitialized)
{
radarChart.Init();
chartInitialized = true;
}
else
{
radarChart.EnsureChartComponent<Title>();
radarChart.EnsureChartComponent<Tooltip>();
radarChart.EnsureChartComponent<XCharts.Runtime.Background>();
radarChart.EnsureChartComponent<RadarCoord>();
}
}
private void EnsureRuntimeChartStyle()
{
if (radarChart == null)
{
return;
}
radarChart.raycastTarget = false;
CanvasRenderer canvasRenderer = radarChart.GetComponent<CanvasRenderer>();
if (canvasRenderer != null)
{
canvasRenderer.SetAlpha(1f);
}
}
private void EnsureChartHost()
{
if (radarChartHost == null)
{
Transform existing = transform.Find(radarChartHostName);
if (existing != null)
{
radarChartHost = existing as RectTransform;
}
}
if (radarChartHost == null)
{
GameObject host = new GameObject(radarChartHostName, typeof(RectTransform));
radarChartHost = host.GetComponent<RectTransform>();
radarChartHost.SetParent(transform, false);
}
radarChartHost.anchorMin = Vector2.zero;
radarChartHost.anchorMax = Vector2.one;
radarChartHost.pivot = new Vector2(0.5f, 0.5f);
radarChartHost.anchoredPosition = Vector2.zero;
radarChartHost.sizeDelta = Vector2.zero;
radarChartHost.offsetMin = Vector2.zero;
radarChartHost.offsetMax = Vector2.zero;
radarChartHost.localScale = Vector3.one;
radarChartHost.localRotation = Quaternion.identity;
radarChartHost.SetAsLastSibling();
radarChartHost.gameObject.SetActive(true);
}
private void DisableLegacyRenderer()
{
if (chartBridge == null)
{
chartBridge = GetComponent<UGUIChartBridge>();
}
if (chartBridge != null)
{
chartBridge.enabled = false;
}
UIDocument uiDocument = GetComponent<UIDocument>();
if (uiDocument != null)
{
uiDocument.enabled = false;
}
RawImage rawImage = GetComponent<RawImage>();
if (rawImage != null)
{
rawImage.enabled = false;
rawImage.texture = null;
rawImage.raycastTarget = false;
Color color = rawImage.color;
color.a = 0f;
rawImage.color = color;
}
if (radarGraphic == null)
{
radarGraphic = GetComponent<UIRadarGraphic>();
}
if (radarGraphic != null)
{
radarGraphic.enabled = false;
radarGraphic.raycastTarget = false;
Color color = radarGraphic.color;
color.a = 0f;
radarGraphic.color = color;
radarGraphic.SetVerticesDirty();
}
if (labelRoot == null && autoCreateLabelRoot)
{
Transform existing = transform.Find(labelRootName);
if (existing != null)
{
labelRoot = existing as RectTransform;
}
}
if (labelRoot != null)
{
labelRoot.gameObject.SetActive(false);
}
}
private void NormalizeAxes()