UE5 UMG图表插件实战:纯C++绘制曲线、饼图、环状图与柱状图
简介这是一款面向Unreal Engine5开发者的UMG图表控件插件专为游戏与虚拟现实项目提供纯UMG实现的数据可视化方案无需WebBrowser或WebUI嵌套通过C与蓝图即可完成图表搭建。插件覆盖曲线图、饼图、环状图与柱状图四类常用图表可用于角色生命值趋势、任务完成占比、技能升级进度、关卡评分与资源消耗等界面展示兼顾程序员底层优化与设计师图形化编辑需求。资源包共102个文件以h与cpp源码、uasset资源、json配置及uplugin插件描述文件为主另含lib、dll等编译产物整体约23.37MB解压至Plugins目录重载项目即可在UMG面板中调用图表组件。目前已有2599人学习下载适合希望提升项目数据展示效果、需要可定制图表组件的UE5开发者参考使用。1. 拆开一个 UE5 图表插件UMG 里画曲线、饼图、环状图和柱状图到底靠不靠谱在 UE5 里做数据可视化很多人第一反应是接 Web 前端或者干脆用材质拼假图。但真到项目里UMG 原生只有 Image、Text、ProgressBar 这几样想画一条带坐标轴的折线、一个能分片的饼图就得自己从 Slate 层往上搭。我最近拆了一个纯 C 实现的 UMG 图表控件插件覆盖曲线图、饼图、环状图、柱状图四类全部走 UMG 控件体系能直接在 Widget Blueprint 里拖出来用。它解决的核心问题就一个不引入任何第三方渲染库用 UE5 自带的 Slate 绘制接口把统计图表画进 UI。适合谁做数据看板、后台工具、模拟器 HUD、运营报表这类需要在游戏或编辑器内展示统计结果的开发者。如果你只是想要一张静态图那没必要上插件但只要数据会变、要交互、要跟蓝图打通这套东西就值得看下去。2. 先搞懂 Slate 绘制管线为什么图表控件必须继承 UWidget 而不是 UUserWidget2.1 从 UWidget 到 SWidget图表控件的继承链UE5 的 UMG 控件分两层U 层是UWidget负责蓝图暴露、属性同步S 层是SWidget负责实际绘制和布局。图表这种需要自定义OnPaint的控件必须继承UWidget然后在RebuildWidget()里返回一个自定义的SCompoundWidget子类。很多人第一次写会直接继承UUserWidget结果发现根本没有OnPaint可以重写这就是没理清继承链。常见做法是建一个UChartWidgetBase : public UWidget再建一个SChartWidgetBase : public SCompoundWidget在UChartWidgetBase::RebuildWidget()里MakeSharedSChartWidgetBase()并把数据指针传进去。这样蓝图里拖出来的是 U 层对象实际画图的是 S 层。// ChartWidgetBase.h UCLASS(Abstract) class UChartWidgetBase : public UWidget { GENERATED_BODY() public: // 数据源蓝图可写 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category Chart) TArrayfloat DataPoints; protected: virtual TSharedRefSWidget RebuildWidget() override; virtual void SynchronizeProperties() override; }; // ChartWidgetBase.cpp TSharedRefSWidget UChartWidgetBase::RebuildWidget() { // 创建 Slate 层实体把 this 传进去以便读取属性 SChartWidgetBase::Construct(this); return SNew(SChartWidgetBase).OwnerWidget(this); }逻辑说明RebuildWidget是 UMG 控件生命周期的入口每次控件被创建或重建时调用。SynchronizeProperties负责把蓝图里改过的属性同步到 Slate 层图表数据变了要在这里触发重绘。参数上DataPoints用TArrayfloat是最简模型实际项目里通常要换成带标签的结构体。2.2 OnPaint 里到底能画什么FSlateDrawElement 的四种常用元素Slate 的绘制入口是OnPaint拿到FPaintArgs、FGeometry、FSlateWindowElementList和LayerId。能画的东西由FSlateDrawElement提供图表场景下最常用四种绘制元素用途关键参数MakeLines折线、坐标轴点数组、颜色、线宽MakeBox柱状图矩形、背景尺寸、颜色MakeCustomVerts饼图/环状图扇形顶点缓冲、索引缓冲MakeText轴标签、数值字体、位置、颜色饼图和环状图是难点因为 Slate 没有内置扇形绘制必须用MakeCustomVerts手动构造三角形扇。一个圆按数据比例切成 N 段每段用圆心加弧上两点组成三角形段数越多边缘越平滑。// 构造饼图扇形顶点简化示意 TArrayFSlateVertex Verts; TArraySlateIndex Indices; FVector2D Center FVector2D(Size.X * 0.5f, Size.Y * 0.5f); float Radius FMath::Min(Size.X, Size.Y) * 0.4f; float StartAngle 0.f; for (float Value : DataPoints) { float Sweep (Value / Total) * 2.f * PI; int32 Segments FMath::Max(3, FMath::CeilToInt(Sweep / (PI / 36.f))); for (int32 i 0; i Segments; i) { float Angle StartAngle Sweep * i / Segments; FVector2D Pos Center FVector2D(FMath::Cos(Angle), FMath::Sin(Angle)) * Radius; // 顶点色、UV 按需填充 Verts.Add(FSlateVertex::MakeESlateVertexRounding::Disabled(...)); } StartAngle Sweep; }逻辑说明Segments按弧度切分保证每段不超过 5 度避免大扇形边缘锯齿。StartAngle累加实现多段拼接。参数上Radius取短边 40% 是留边距的常见做法实际可暴露成蓝图属性。注意MakeCustomVerts需要自己管理顶点和索引缓冲索引顺序错了会画出破面。3. 四类图表的数据模型与绘制实现曲线、饼图、环状图、柱状图逐个拆3.1 曲线图数据归一化与坐标轴映射曲线图的核心不是画线是把原始数据映射到控件坐标系。控件坐标原点在左上角Y 轴向下而数据通常是 Y 向上所以要做一次翻转。常见做法是先求数据的最小最大值做归一化再乘以绘图区高度。// 数据点转屏幕坐标 FVector2D MapToScreen(float Index, float Value, const FVector2D PlotSize, float MinVal, float MaxVal) { float NormX Index / FMath::Max(1, DataPoints.Num() - 1); float NormY (Value - MinVal) / FMath::Max(KINDA_SMALL_NUMBER, MaxVal - MinVal); // Y 轴翻转1 - NormY return FVector2D(NormX * PlotSize.X, (1.f - NormY) * PlotSize.Y); }逻辑说明NormX把索引映射到 0-1NormY把数值映射到 0-1翻转后乘绘图区尺寸。参数上MinVal、MaxVal建议留 5% 余量否则峰值会贴边。如果数据全相等MaxVal - MinVal为 0用KINDA_SMALL_NUMBER兜底防止除零。坐标轴和网格线用MakeLines画标签用MakeText。一个容易翻车的地方是MakeText的坐标是相对控件左上角的绝对坐标不是相对绘图区算标签位置时别忘了加绘图区偏移。3.2 饼图与环状图扇形顶点构造与内外半径饼图和环状图共用一套扇形逻辑区别只在环状图多一个内半径。饼图是圆心到弧的三角形扇环状图是内弧到外弧的四边形带。实现上环状图每个分段要构造四个顶点而不是三个。// 环状图单段顶点内弧两点 外弧两点 float InnerRadius Radius * InnerRatio; // InnerRatio 如 0.6 FVector2D InnerStart Center FVector2D(FMath::Cos(StartAngle), FMath::Sin(StartAngle)) * InnerRadius; FVector2D OuterStart Center FVector2D(FMath::Cos(StartAngle), FMath::Sin(StartAngle)) * Radius; FVector2D InnerEnd Center FVector2D(FMath::Cos(EndAngle), FMath::Sin(EndAngle)) * InnerRadius; FVector2D OuterEnd Center FVector2D(FMath::Cos(EndAngle), FMath::Sin(EndAngle)) * Radius; // 两个三角形拼一个四边形InnerStart-OuterStart-OuterEnd 和 InnerStart-OuterEnd-InnerEnd逻辑说明InnerRatio控制环的粗细0 就是饼图0.6 是常见环状图比例。顶点顺序必须统一为逆时针或顺时针否则背面剔除会导致部分扇形不显示。参数上EndAngle StartAngle SweepSweep由数据占比决定。饼图还有一个坑数据项太多时小扇形会挤在一起看不清。常见做法是小于 3% 的项合并成「其他」或者加引出线标注。插件里如果没做这个就得在数据层预处理。3.3 柱状图布局计算与负值处理柱状图相对简单但有两个细节容易忽略。一是柱宽和间距的计算二是负值柱要向下画。柱宽公式常见做法是PlotWidth / (N * 1.5)留出 0.5 倍间距。float BarSlot PlotSize.X / DataPoints.Num(); float BarWidth BarSlot * 0.6f; // 60% 宽度40% 间距 float ZeroY MapToScreen(0, 0, PlotSize, MinVal, MaxVal).Y; // 零线位置 for (int32 i 0; i DataPoints.Num(); i) { float ValueY MapToScreen(0, DataPoints[i], PlotSize, MinVal, MaxVal).Y; float Top FMath::Min(ValueY, ZeroY); float Height FMath::Abs(ValueY - ZeroY); FVector2D Pos(PlotSize.X * i / DataPoints.Num() (BarSlot - BarWidth) * 0.5f, Top); // MakeBox 绘制 }逻辑说明ZeroY是零值对应的屏幕 Y正值柱从 ZeroY 向上负值柱向下用Min和Abs统一处理。参数上0.6f是柱宽占比太宽会挤太窄看不清可按数据量动态调整。3.4 数据更新与重绘Invalidate 的时机图表数据变了必须触发重绘否则画面不动。UMG 里调用Invalidate(EWidgetInvalidation::Paint)或InvalidateLayoutAndVolatility。前者只重绘不重排后者会重新计算布局。数据点数量不变时用前者数量变了用后者。void UChartWidgetBase::SetData(const TArrayfloat InData) { DataPoints InData; // 数据量变化需要重排否则只重绘 if (MyChart.IsValid()) { MyChart-Invalidate(EWidgetInvalidation::LayoutAndVolatility); } }逻辑说明Invalidate是 Slate 的重绘请求下一帧生效。参数EWidgetInvalidation决定重绘范围选错会导致布局不更新或性能浪费。注意不要在OnPaint里调Invalidate会死循环。4. 避坑与排查图表控件从编译到显示的血泪经验4.1 现象控件拖进蓝图后一片空白日志无报错原因RebuildWidget返回了空指针或者SChartWidgetBase的OnPaint里DataPoints为空直接 return 了。另一个常见原因是SynchronizeProperties没把数据同步过去Slate 层拿到的还是默认空数组。解决先在OnPaint开头加if (DataPoints.Num() 0) return;之前打日志确认数据是否到达。再检查RebuildWidget是否真的return SNew(...)。如果用的是Construct模式确认OwnerWidget指针非空。4.2 现象饼图扇形之间有缝隙或重叠原因浮点精度导致相邻扇形的起止角度对不齐前一段的EndAngle和后一段的StartAngle差了一个极小值。或者顶点索引顺序不一致部分三角形被剔除。解决不要每段独立算角度用累加方式让下一段的StartAngle严格等于上一段的EndAngle。索引顺序统一为逆时针并在材质里关闭背面剔除做验证。如果还有缝把每段弧的Segments加一让边缘更密。4.3 现象曲线图数据点多了之后帧率掉得厉害原因每帧在OnPaint里重新构造顶点数组数据点几千个时 CPU 开销大。或者MakeLines一次传了上万个点Slate 的线绘制没有做抽稀。解决把顶点构造缓存起来只在数据变化时重建OnPaint里直接复用。曲线做抽稀屏幕上一条线最多几百个像素宽超过这个数的点用 Douglas-Peucker 之类的算法简化。参数上可以设一个MaxPointsPerLine超过就降采样。4.4 现象打包后图表不显示编辑器里正常原因Slate 的字体资源在打包时没被引用到MakeText找不到字体就静默失败。或者自定义的FSlateBrush资源没加进AdditionalAssetDirectoriesToCook。解决在DefaultGame.ini里把字体和画刷目录加进打包列表。更稳的做法是图表控件不依赖外部字体资源用引擎内置的FCoreStyle::GetDefaultFontStyle。如果必须用自定义字体在控件的UPROPERTY里硬引用一下保证被 cook 进去。4.5 现象蓝图里改了数据但图表不刷新原因UPROPERTY没加BlueprintReadWrite或者加了但没在SynchronizeProperties里处理。UMG 的属性同步只在特定时机触发运行时直接改成员变量不会自动通知 Slate。解决数据属性用BlueprintReadWrite暴露并在 setter 里手动调Invalidate。如果用的是TArray注意 UMG 的属性面板对数组的编辑不会触发SynchronizeProperties得提供一个SetData函数在蓝图里显式调用。5. 进阶技巧把图表控件做成可复用的蓝图资产5.1 用 USTRUCT 封装图表配置硬编码颜色、线宽、边距这些参数改一次要重新编译。更好的做法是定义一个FChartStyle结构体把所有视觉参数收进去蓝图里可以存成数据资产。USTRUCT(BlueprintType) struct FChartStyle { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite) FLinearColor AxisColor FLinearColor::White; UPROPERTY(EditAnywhere, BlueprintReadWrite) float LineThickness 2.f; UPROPERTY(EditAnywhere, BlueprintReadWrite) float Padding 20.f; UPROPERTY(EditAnywhere, BlueprintReadWrite) float InnerRatio 0.6f; // 环状图内径比 };逻辑说明USTRUCT加BlueprintType后可以在蓝图里作为变量类型也能存成DataAsset。参数上Padding控制绘图区边距InnerRatio只对环状图生效饼图忽略。这样一套控件配多套样式不用改代码。5.2 动画过渡让数据变化不那么生硬数据突变时图表直接跳变观感差。可以在 Slate 层加一个插值每帧把当前显示值向目标值逼近。常见做法是用FMath::FInterpTo在OnPaint里根据 DeltaTime 更新。// Tick 里插值 CurrentValues[i] FMath::FInterpTo(CurrentValues[i], TargetValues[i], DeltaTime, InterpSpeed); // 接近目标时停止重绘省性能 if (bIsAnimating) Invalidate(EWidgetInvalidation::Paint);逻辑说明InterpSpeed控制动画快慢越大越快。bIsAnimating在差值小于阈值时置 false避免无休止重绘。参数上DeltaTime从FSlateApplication::Get().GetDeltaTime()取或者用控件的Tick。5.3 验证图表正确性的三个土办法第一喂一组已知数据比如[25, 25, 25, 25]饼图应该是四个等分扇形肉眼能判断。第二喂[0, 0, 0, 100]饼图应该只有一个满圆其他不可见。第三曲线图喂一条直线[1,2,3,4,5]屏幕上应该是一条斜率恒定的直线如果弯了说明映射有问题。这三个用例覆盖了归一化、零值、线性映射三个最容易出错的点。我现在的习惯是每接一个新的图表控件先跑这三组数据确认绘制管线通了再上真实数据。从那以后我每次集成图表控件都强制走一遍这个验证流程省了不少返工。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

零基础实战Vibe Coding:普通人用AI从0到1做出小工具

零基础实战Vibe Coding:普通人用AI从0到1做出小工具

最近一年被问得特别多的一句话是:"我完全没写过代码,现在让 AI 帮我做个小工具,到底靠不靠谱?"以前听到这种问题,我一般会说你先去补点基础再谈。但这两年 Vibe Coding 这个概念火起来以后,我的答…

2026/10/12 6:22:43 阅读更多 →
从Pi到DSH:Agent Harness如何实现自生长与经验进化

从Pi到DSH:Agent Harness如何实现自生长与经验进化

1. 从"能插拔"到"会进化":Agent Harness 到底在解决什么问题如果你最近在折腾 AI Agent 相关的东西,大概率会碰到一个很尴尬的局面:模型能力明明够用,但整个系统跑起来就是"死板"。加一个新工具要改…

2026/10/12 6:22:43 阅读更多 →
FDE工程化方法卡:用Echo与Delta信号构建领域本体

FDE工程化方法卡:用Echo与Delta信号构建领域本体

前一阵接了个内部知识库的活,要做的其实不复杂:从几万篇领域文本里把核心实体和它们之间的关系抽出来,最后组织成一版可查询的领域本体。一开始我觉得这事直接上大模型就行,让模型开箱即用地输出实体和关系,结果返回的…

2026/10/12 6:22:43 阅读更多 →

最新新闻

DeepSeek开源算力地基:FlashMLA与DeepEP如何加速国产大模型?

DeepSeek开源算力地基:FlashMLA与DeepEP如何加速国产大模型?

先说明一下我的第一反应:看到“致敬,DeepSeek 最新开源的不是模型,是国产算力的地基”这个标题,我以为是又一个大模型权重开源了。结果点进仓库一看,里面没有模型文件,躺着的全是 FlashMLA、DeepEP、DeepGE…

2026/10/12 7:08:08 阅读更多 →
NumPy随机函数与广播机制:维度对齐、代码实战与踩坑全解

NumPy随机函数与广播机制:维度对齐、代码实战与踩坑全解

【day 48】随机函数与广播机制,打卡到这个组合的时候,我明显感觉身边不少同学开始卡壳了:随机函数单独用没问题,广播规则单独看也能理解,但真到写代码的时候,要么随机生成的数组形状对不上,要么…

2026/10/12 7:08:08 阅读更多 →
MTCNN人脸检测详解:P-Net、R-Net、O-Net三级网络训练与部署实战

MTCNN人脸检测详解:P-Net、R-Net、O-Net三级网络训练与部署实战

简介:面向深度学习和计算机视觉研究者,这份完整代码基于MTCNN级联卷积网络实现人脸检测,清晰覆盖P-Net候选框提议、R-Net边界框精修、O-Net关键点定位的完整流程。包体共80个文件,以Python源码为核心,包含48个py文件&a…

2026/10/12 7:08:08 阅读更多 →
物联网通信技术选型与架构设计:从链路预算到协议栈的工程实践指南

物联网通信技术选型与架构设计:从链路预算到协议栈的工程实践指南

干了十几年通信,最明显的感觉是:打电话的人越来越少,问"能不能帮我把这个箱子里的定位数据传回来"的人越来越多。物联网这个概念被炒了很多年,别人看到的是智能家居、智慧城市、大风口,我看到的是另一件事—…

2026/10/12 7:08:08 阅读更多 →
MyBatis查询功能全解析:从动态SQL到性能优化的实战指南

MyBatis查询功能全解析:从动态SQL到性能优化的实战指南

项目标题: MyBatis各种查询功能用到一定年头你就会发现,MyBatis的查询功能说难不难,说简单也绝不是写个select标签就完事。日常开发里,动态条件、分页、多表嵌套、批量操作,任何一个场景都能写出好几种不同写法,而每种…

2026/10/12 7:08:08 阅读更多 →
老毛桃UEFI启动盘v7.0:稳过Secure Boot,原生支持NVMe与Win11架构识别

老毛桃UEFI启动盘v7.0:稳过Secure Boot,原生支持NVMe与Win11架构识别

简介:老毛桃U盘启动盘制作工具(UEFI版 装机版)v7.0是一款面向电脑初学者与系统维护人员的轻量级系统辅助工具,专为快速制作兼容UEFI与传统BIOS的U盘启动盘、安装原版Windows系统及执行PE环境下的故障排查而设计。资源包共2个文件&…

2026/10/12 7:07:08 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →