
1. 项目概述与核心价值在Unity游戏开发中数据可视化是一个高频且关键的需求。无论是展示玩家的血量、经验值、技能冷却还是呈现复杂的游戏内经济系统、任务完成度一个直观的图表往往比干巴巴的数字更具表现力。最近我在一个项目中需要动态展示多个角色的属性占比比如一个队伍中不同职业的伤害贡献百分比。手动在Unity编辑器里拖拽预制件、配置图表参数不仅效率低下更无法应对运行时数据的实时变化。这正是代码动态生成图表的用武之地。XCharts是Unity Asset Store上一款功能强大且易用的图表插件它提供了丰富的图表类型和灵活的配置选项。但很多开发者尤其是新手往往只停留在使用其编辑器界面进行静态配置。实际上XCharts的API设计得非常友好支持通过C#脚本进行全方位的动态控制。本次分享的核心就是如何彻底摆脱对预制件的依赖纯代码驱动从零开始创建一个美观的百分比图表通常是饼图或环形图并附上可直接集成到你项目中的完整C#脚本。这个方案的价值在于其极致的灵活性和可复用性。你不再需要为每一种数据组合预先制作多个图表预制件一个脚本传入不同的数据列表就能瞬间生成对应的图表。这对于制作数据驱动的游戏、工具编辑器或是需要动态报告功能的游戏内系统如数据分析面板来说是效率的质变。接下来我将从设计思路、核心API解析、到一步步实现和避坑指南完整拆解这个过程。2. 核心思路与XCharts动态API解析在动手写代码之前我们必须理清动态创建图表的核心逻辑。整个过程可以分解为几个清晰的步骤创建图表游戏对象、配置图表基本属性、添加并配置系列Series、填充数据、以及最后的美化与交互设置。XCharts为此提供了一套以BaseChart为核心的API体系。2.1 动态创建流程拆解首先动态生成意味着我们不会从Resources或AssetBundle加载一个预设好的PieChart.prefab。取而代之的是我们在运行时通过new GameObject()并添加必要的组件来构建图表。创建容器在场景中创建一个空的GameObject作为图表的根节点。添加图表组件为这个GameObject添加PieChart组件对于百分比图表我们通常使用饼图或环形图。PieChart继承自BaseChart是所有操作的入口。初始化图表主题与设置通过代码设置图表的标题Title、图例Legend、提示框Tooltip等基本元素的显示、位置和样式。这些在编辑器中通过Inspector面板配置的选项在代码中都有对应的属性。构建数据系列Series这是最关键的一步。一个图表可以包含多个系列但对于简单的百分比展示一个系列就够了。我们需要创建Pie类型的系列并为其设置关键参数如roseType是否显示为南丁格尔玫瑰图、radius饼图半径用于控制环形图的内外径等。注入数据SeriesData将你的百分比数据例如一个Listfloat或Dictionarystring, float转换为XCharts能识别的SeriesData列表。每个SeriesData包含数据名name、数据值value以及可选的个性化样式itemStyle。刷新与渲染在所有配置完成后调用图表实例的RefreshChart()方法强制其根据当前数据重新绘制。2.2 关键API深度解读理解以下几个核心类是灵活操控XCharts的基础BaseChart/PieChart: 所有图表的基类/具体实现。我们通过chart.GetComponentPieChart()或gameObject.AddComponentPieChart()来获取或添加它。它包含了title、legend、tooltip等主要模块的引用。TitleComponent: 控制图表主标题和子标题的显示、文字、位置和样式。LegendComponent: 控制图例数据系列名称的列表的显示、类型滚动、普通、位置和朝向。TooltipComponent: 控制鼠标悬浮提示框的触发类型Item、Axis、显示格式和样式。Series: 系列列表。chart.series是一个ListSeries每个Series对象代表图表中的一组数据。我们需要通过chart.AddSerie()来添加并设置其type为SerieType.Pie。SerieData: 系列中的数据项。series.data是一个ListSerieData。每个SerieData的name和value属性构成了饼图的一个扇形区块。ItemStyle: 数据项的样式可以设置每个扇形的颜色color、边框borderColor,borderWidth等。颜色可以通过Theme主题获取也可以单独指定。注意XCharts的API设计是“配置式”的。这意味着你修改了series.data或chart.title.text等属性后图表并不会立即更新。必须手动调用RefreshChart()或设置chart.animation.enable为true让其动态更新。在动态生成场景中我们通常在配置完成后调用一次RefreshChart()。3. 完整C#脚本实现与逐行详解理论清晰后我们来看实战代码。下面这个DynamicPercentageChartCreator类封装了动态创建百分比环形图的所有功能。你可以将它挂载到任何游戏对象上或者通过静态方法调用。using System.Collections.Generic; using UnityEngine; using XCharts; // 引入XCharts命名空间 public class DynamicPercentageChartCreator : MonoBehaviour { [Header(基础设置)] public string chartName 动态占比图; public Vector2 chartSize new Vector2(600, 400); public Vector3 chartPosition Vector3.zero; [Header(图表样式)] public float innerRadiusPercent 40f; // 内径百分比0为实心饼图0为环形图 public float outerRadiusPercent 70f; // 外径百分比 public bool showDataLabel true; // 是否在扇形上显示数据标签 /// summary /// 在指定父节点下动态创建一个百分比环形图 /// /summary /// param nameparent父Transform如果为null则创建在场景根目录/param /// param namedataPairs数据字典Key为名称Value为数值会自动计算百分比/param /// returns创建好的PieChart组件实例/returns public PieChart CreateChart(Transform parent, Dictionarystring, float dataPairs) { // 1. 创建图表游戏对象 GameObject chartGo new GameObject(chartName); chartGo.transform.SetParent(parent, false); chartGo.transform.localPosition chartPosition; // 2. 添加RectTransform并设置尺寸对于UI图表 RectTransform rect chartGo.AddComponentRectTransform(); rect.sizeDelta chartSize; // 3. 添加PieChart组件 PieChart chart chartGo.AddComponentPieChart(); // 初始化图表清除可能存在的默认系列 chart.Init(); chart.ClearData(); // 确保开始状态是干净的 // 4. 配置图表基础组件 ConfigureTitle(chart); ConfigureLegend(chart); ConfigureTooltip(chart); // 5. 添加并配置一个Pie系列 Serie serie chart.AddSerie(SerieType.Pie); serie.name 占比系列; // 关键设置环形图半径 [内径% 外径%] serie.radius[0] innerRadiusPercent; serie.radius[1] outerRadiusPercent; serie.center[0] 0.5f; // 中心点X相对位置0-1 serie.center[1] 0.5f; // 中心点Y // 6. 填充数据 float total 0f; foreach (var value in dataPairs.Values) total value; int colorIndex 0; foreach (var kvp in dataPairs) { SerieData serieData new SerieData(); serieData.name kvp.Key; serieData.data[0] kvp.Value; // 存储原始值 // 计算并格式化显示百分比 float percentage (total 0) ? (kvp.Value / total * 100f) : 0f; // 数据标签显示内容名称 百分比 if (showDataLabel) { serieData.label.show true; // 使用富文本格式化\n换行 serieData.label.formatter {b}\n{c:0.0}%; } // 从图表主题中按顺序获取颜色避免颜色重复 var itemStyle serieData.EnsureComponentItemStyle(); if (chart.theme.serie.Count colorIndex) { itemStyle.color chart.theme.serie[colorIndex % chart.theme.serie.Count]; } colorIndex; serie.AddData(serieData); } // 7. 配置系列的全局标签例如显示在中间的总结文字 serie.label.show showDataLabel; serie.label.position LabelStyle.Position.Center; serie.label.formatter $总计\n{total:F0}; // 在环中心显示总数 // 8. 刷新图表使其渲染 chart.RefreshChart(); return chart; } private void ConfigureTitle(PieChart chart) { chart.title.show true; chart.title.text chartName; chart.title.subText 动态生成示例; chart.title.left Center; // Center是XCharts定义的常量代表居中 chart.title.top 10f; } private void ConfigureLegend(PieChart chart) { chart.legend.show true; chart.legend.left Center; chart.legend.bottom 10f; chart.legend.orient Orient.Horizonal; // 图例水平排列 chart.legend.itemWidth 70; chart.legend.itemHeight 20; // 图例筛选点击可以隐藏/显示对应的扇形 chart.legend.selectedMode Legend.SelectedMode.Multiple; } private void ConfigureTooltip(PieChart chart) { chart.tooltip.show true; chart.tooltip.type Tooltip.Type.Item; // 提示框格式器{a}系列名, {b}数据名, {c}数据值, {d}百分比 chart.tooltip.formatter {b}: {c} ({d}%); chart.tooltip.background.color new Color32(50, 50, 50, 230); } }脚本关键点解析参数化设计通过[Header]属性在Inspector中暴露关键参数如图表尺寸、环形图半径使得同一个脚本无需修改代码即可快速调整图表外观非常适合在编辑器工具中复用。数据输入采用Dictionarystring, float作为输入键值对的形式非常符合“标签-数值”的映射关系清晰且易于准备数据。百分比计算脚本内部完成了从原始值到百分比的计算percentage kvp.Value / total * 100f。这意味着你传入的数据可以是任意单位的原始值如伤害量、金币数图表会自动处理比例关系。标签格式化这是提升图表可读性的精髓。我们使用了XCharts内置的格式化语法如{b}\n{c:0.0}%。{b}代表数据名{c}代表数据值{d}代表百分比。\n用于换行0.0是数字格式表示保留一位小数。颜色管理通过chart.theme.serie从当前主题的颜色列表中循环取色确保了扇形颜色的自动分配且不与主题冲突。你也可以在这里硬编码一组颜色数组来实现自定义配色方案。环形图控制serie.radius[0]和serie.radius[1]是实现环形图的关键。它们分别代表内半径和外半径单位是百分比相对于图表容器的较小边。40f和70f的配置会产生一个中间有孔的环形图。4. 使用示例与场景集成有了创造器脚本在游戏运行时动态生成图表就变得非常简单。以下是在几个典型场景中的应用示例。4.1 示例在UI画布中实时生成队伍伤害占比假设你有一个战斗结算界面需要展示队伍中四个英雄的输出占比。using UnityEngine; using System.Collections.Generic; public class BattleResultUI : MonoBehaviour { public RectTransform chartContainer; // 在Inspector中指定一个UI面板作为容器 void Start() { // 模拟战斗数据 Dictionarystring, float damageData new Dictionarystring, float { {战士, 12500f}, {法师, 9800f}, {射手, 15600f}, {辅助, 3200f} }; // 获取或添加创造器组件 DynamicPercentageChartCreator creator gameObject.AddComponentDynamicPercentageChartCreator(); creator.chartName 本场战斗伤害占比; creator.innerRadiusPercent 50f; // 更大的空心做成甜甜圈图 creator.outerRadiusPercent 80f; // 动态创建图表 PieChart chart creator.CreateChart(chartContainer, damageData); // 可选进一步自定义例如调整图例位置 chart.legend.left Right; chart.legend.top Top; chart.RefreshChart(); } }将这段代码挂载到结算界面的一个空物体上并将UI中的某个Panel拖拽给chartContainer变量。运行游戏进入结算界面时一个美观的伤害占比环形图就会立刻出现。4.2 示例在3D/2D场景世界中生成数据看板有时我们需要将图表作为游戏世界的一部分比如一个科幻基地里的全息数据面板。public class WorldSpaceDashboard : MonoBehaviour { public Transform chartAnchor; // 3D世界中的一个空物体作为图表位置 public Vector2 worldChartSize new Vector2(3, 2); // 世界单位下的尺寸 void Start() { Dictionarystring, float resourceData FetchResourceDataFromGame(); // 从游戏逻辑获取数据 DynamicPercentageChartCreator creator gameObject.AddComponentDynamicPercentageChartCreator(); creator.chartName 资源分布; creator.chartSize worldChartSize * 100; // 粗略转换1世界单位 ≈ 100像素需根据Canvas缩放调整 creator.showDataLabel false; // 世界空间图表标签可能太小选择关闭 PieChart chart creator.CreateChart(null, resourceData); // 父节点为null创建在根目录 // 将图表对象移动到3D世界位置并可能附加到看板物体上 chart.transform.SetParent(chartAnchor, false); chart.transform.localPosition Vector3.zero; chart.transform.localRotation Quaternion.identity; // 确保图表Canvas的Render Mode是World Space并正确设置Event Camera Canvas chartCanvas chart.GetComponentCanvas(); if (chartCanvas ! null) { chartCanvas.renderMode RenderMode.WorldSpace; chartCanvas.worldCamera Camera.main; RectTransform rect chart.GetComponentRectTransform(); rect.sizeDelta creator.chartSize; } } }这个示例的关键在于处理世界空间渲染。你需要确保动态创建的图表GameObject上有一个Canvas组件并将其Render Mode设置为World Space同时指定渲染相机。XCharts生成的图表本质上是UI元素因此必须遵循UGUI的世界空间Canvas规则。4.3 动态更新已有图表数据动态生成不仅仅是创建还包括更新。以下函数展示了如何修改已有图表的数据public void UpdateChartData(PieChart chart, Dictionarystring, float newData) { if (chart null) return; Serie serie chart.series[0]; // 假设我们只处理第一个系列 serie.data.Clear(); float total 0f; foreach (var value in newData.Values) total value; int idx 0; foreach (var kvp in newData) { SerieData newSerieData new SerieData(); newSerieData.name kvp.Key; newSerieData.data[0] kvp.Value; // 重新分配颜色或保持原索引颜色 var itemStyle newSerieData.EnsureComponentItemStyle(); if (chart.theme.serie.Count idx) { itemStyle.color chart.theme.serie[idx % chart.theme.serie.Count]; } idx; serie.AddData(newSerieData); } // 更新中心总数值 serie.label.formatter $总计\n{total:F0}; // 必须刷新 chart.RefreshChart(); }调用UpdateChartData并传入图表实例和新数据字典即可实现图表内容的实时刷新。结合Unity的Update或协程可以轻松制作出动态变化的实时数据监控面板。5. 常见问题、性能优化与避坑指南在实际项目中使用动态图表生成难免会遇到一些坑。这里分享我踩过的一些雷区和优化经验。5.1 常见问题排查表问题现象可能原因解决方案图表不显示或一片空白1. 未调用RefreshChart()。2. 数据值全为0。3.series.data添加数据后未关联到正确的series实例。4. 图表尺寸 (RectTransform) 为0。1. 确保在配置完成后调用chart.RefreshChart()。2. 检查输入数据确保总和大于0。3. 使用chart.AddSerie()返回的Serie对象来添加数据或通过chart.series[0]访问。4. 检查创建图表对象的RectTransform的sizeDelta是否设置合理。环形图显示为实心饼图serie.radius[0](内半径) 设置为0。将serie.radius[0]设置为一个大于0的值例如40f。数据标签不显示或格式错误1.serieData.label.show未设置为true。2.formatter字符串语法错误。3. 标签字体大小太大或颜色与背景相同。1. 显式设置show true。2. 参考XCharts文档检查格式化符如{a},{b},{c},{d}的使用。3. 调整serieData.label.textStyle的fontSize和color。图例点击无效chart.legend.selectedMode设置为None。设置为Legend.SelectedMode.Multiple或Single。世界空间图表看不到1.Canvas的Render Mode不是World Space。2. 没有设置worldCamera。3. 图表被其他3D物体遮挡。1. 创建后获取Canvas组件并设置renderMode。2. 将主相机或其他相机赋值给canvas.worldCamera。3. 调整图表物体的图层 (Layer) 或Canvas的Sorting Order。大量动态图表导致性能下降每个图表都是独立的GameObject和CanvasDraw Call 过高。1.合批处理确保图表的UI元素Text, Image材质相同以促进Unity UI合批。2.对象池对于频繁创建销毁的图表如滚动列表中的项实现一个简单的图表对象池。3.按需更新不要每帧都调用RefreshChart()仅在数据确实变化时更新。4.简化图表在移动端或性能敏感场景关闭阴影、渐变色等特效减少数据项。5.2 性能优化实操心得对于需要展示数十个甚至上百个数据项的复杂饼图性能是需要重点考虑的。XCharts在绘制大量扇形时本质上是生成大量的三角形网格和UI文本。数据聚合当数据项过多时例如超过15个考虑将占比很小的项如小于2%合并为一个“其他”类别。这能显著减少渲染元素。// 简化数据示例 var aggregatedData new Dictionarystring, float(); float othersSum 0f; foreach (var kvp in rawData) { if (kvp.Value / total 0.02f) // 大于2%的单独显示 aggregatedData.Add(kvp.Key, kvp.Value); else othersSum kvp.Value; } if (othersSum 0) aggregatedData.Add(“其他”, othersSum);禁用非必要动画虽然动画很酷但chart.animation.enable在动态更新数据时会带来额外的计算开销。在数据看板等需要快速刷新的场景可以考虑关闭。chart.animation.enable false; // 创建时或更新前关闭复用图表实例与其频繁销毁和创建不如隐藏和显示。维护一个图表池当需要新图表时从池中取出一个闲置的调用UpdateChartData更新其内容然后显示。这能有效避免GC垃圾回收带来的卡顿。5.3 样式深度自定义技巧默认的主题颜色可能不符合你的游戏风格。除了使用内置主题你还可以完全自定义每个扇形的颜色。静态颜色数组定义一组符合你项目UI规范的颜色。private Color[] customColors new Color[] { new Color(0.2f, 0.6f, 1.0f, 1.0f), // 蓝色 new Color(0.8f, 0.4f, 0.9f, 1.0f), // 紫色 // ... 更多颜色 }; // 在分配颜色时使用 itemStyle.color customColors[colorIndex % customColors.Length];根据数据含义着色例如在血量占比图中你可以根据“安全”、“警告”、“危险”阈值来分配红黄绿不同的颜色。Color GetColorByValue(float value, float max) { float ratio value / max; if (ratio 0.6f) return Color.green; else if (ratio 0.3f) return Color.yellow; else return Color.red; }6. 扩展思路超越基础饼图掌握了基础环形图的动态生成后你可以利用XCharts的强大功能进行更多探索。1. 多系列对比环形图在一个环形图区域展示两组相关联数据的对比。例如内环展示计划目标外环展示实际完成情况。这需要添加两个SerieType.Pie系列并仔细调整它们的radius参数使它们嵌套。Serie targetSerie chart.AddSerie(SerieType.Pie); targetSerie.radius[0] 50f; targetSerie.radius[1] 65f; targetSerie.name “计划”; Serie actualSerie chart.AddSerie(SerieType.Pie); actualSerie.radius[0] 70f; actualSerie.radius[1] 85f; actualSerie.name “实际”; // 分别为两个系列填充数据...2. 与其他图表类型联动XCharts支持折线图、柱状图等。你可以创建一个仪表盘中间是环形图展示整体占比周围用折线图展示趋势变化。这需要你管理多个BaseChart实例并协调它们的数据更新。3. 交互事件绑定XCharts提供了丰富的事件回调如OnSerieClick点击扇形、OnLegendClick点击图例。你可以为这些事件添加监听实现点击某个扇形后在游戏世界中高亮对应的单位或者弹出详细数据面板极大地增强游戏的交互性和信息深度。chart.onSerieClick.AddListener((serieIndex, dataIndex) { string clickedName chart.series[serieIndex].data[dataIndex].name; Debug.Log($“你点击了{clickedName}”); // 在这里触发游戏内的逻辑 });动态生成图表的核心思想是将数据与表现分离。你的游戏逻辑只负责产生干净的数据结构而DynamicPercentageChartCreator这样的工具类则负责将数据转化为直观的视觉表达。这种模式使得UI层与逻辑层耦合度降低维护和迭代变得更加容易。希望这份详细的指南和即拿即用的脚本能帮助你高效地在Unity项目中实现各种炫酷又实用的数据可视化需求。