简介
(应用展示) 版本:UE 5.3
- 本文介绍了一款标牌聚合插件,该插件已应用于全民健身房项目,用于实现标牌的精准、灵活、动态聚合管理。
- 原项目采用蓝图划分网格的方式实现聚合,存在位置精度不足、分类扩展不灵活等问题。本插件基于 C++ 开发,提供 Distance Cluster System(距离聚合) 和 Screen Cluster System(屏幕聚合) 两种方案。
- 当 Actor 数量大于 1 且满足距离或屏幕条件时,系统会自动生成聚合簇,并通过对象池管理聚合标牌;当条件不满足时,自动拆分簇并切换 Actor 显隐,实现标牌的自动统计、聚合与切换。
一、聚合系统核心介绍

【标牌聚合系统原理】
- 系统首先通过 SpatialHash(空间哈希) 将三维空间划分成很多小区域,把每个标牌记录到对应的空间格子中。当需要聚合时,不需要遍历全部标牌,只需要查询当前位置附近的几个格子,快速找到可能需要合并的标牌,提高大量标牌场景下的计算效率。
- 找到附近标牌后,由 Algorithm(聚合算法) 决定哪些标牌需要合并。算法采用 Seed-Radius(种子半径)聚合方式:选择一个标牌作为中心,只吸收一定距离范围内的其他标牌,形成一个聚合簇,并计算这个簇的中心位置和包含数量。
- 当摄像机距离较远时,合并范围扩大,多个标牌会合成为一个数字标牌;当摄像机靠近时,超过展开距离的标牌会自动拆分,恢复显示单个标牌。
- 简单来说:SpatialHash负责快速找到附近标牌,Algorithm负责判断哪些标牌合并,最终生成聚合结果并控制标牌显示。
二、方案&&节点
方案
**【按距离聚合】Initialize Distance Cluster System**

| 参数 | 默认 | 说明 |
| Manager id | None | 用来区分不同的管理器,便于楼层管理(也支持将返回值cluster Manager提升为变量) |
| Actors(数组) | / | 将你的actor全部给进去(比如你的监控actor) |
| observer Pawn | / | 输入你当前使用的pawn,要根据你的pawn计算变化的距离实现动态聚合 |
| Merge Distance | 10000 |
合并半径基础值(cm,最小1) (也就是actor之间距离多远可以合并) |
| Expand Distance | 5000 |
两层:① DistCenter≤Expand → 全拆单点;② 局部相机拆开。0=关闭**(pawn距离总簇中心多远开始合并)[常见问题里也有具体描述]** |
| Add scale speed | 0.05【0-1】 |
使用的公式(增值)DistCenter>Expand:EffMerge = Merge + speed×(DistCenter−Expand);Expand=0时:Merge+speed×DistCenter;(所以说这个值越大合并越快,值越小合并越慢) |
| Distance of change(简写 DoC) | 500 |
节流效果,pawn的变化大于等于这个值的时候才会刚性簇。0=每帧重算 |
| Draw Debug | false |
画调试球+数量字;仅数值变化打日志 这个功能在调试时很好用! |
| Marker Pool Warm Size | 0 |
预热个数,不用池保持0。不用担心自己填的数量不准确,不够的它会自己继续spawn |
| Marker Class | None |
动态对象池标牌样式类。仅使用池时设置,默认None |
Distance 更新逻辑:
DistCenter ≤ Expand→ 全拆为单点;留在 Expand 内时不再按公式重算合并(若结果里仍有成簇会再强制拆一次)。DistCenter > Expand→ 远处按EffMerge(就是按公式计算的那个值)种子合并;相机到 Actor ≤ Expand 的立刻就地拆开(不受 DoC 推迟)。- 远处 EffMerge 仅当
|DistCenter − last| ≥ Distance of change(或 DoC = 0)才重算。
**【按屏幕聚合】Initialize Screen Cluster System**

| 参数 | 默认 | 说明 |
| Manager id | None | 用来区分不同的管理器,便于楼层管理(也支持将返回值cluster Manager提升为变量) |
| Actors(数组) | / | 将你的actor全部给进去(比如你的监控actor) |
| observer Pawn | / | 输入你当前使用的pawn,要根据你的pawn计算变化的距离实现动态聚合 |
| Merge Pixel Radius (px) | 80 |
屏幕种子半径(px,最小1) |
| Distance of change | 500 |
相机位移≥该值重算;绕圈因Yaw/Pitch约2°更新。0=每帧重算 |
| Draw Debug | false |
画调试球+数量字(无DistanceDebug日志) |
| Marker Pool Warm Size | 0 |
预热个数,不用池保持0 |
| Marker Class | None |
动态对象池标牌样式类。仅使用池时设置,默认None |
远 → 投影挤在一起易合并;近 → 像素间距变大自然拆开。注意:因为是按像素,所以角度也会影响它的聚合
注意:Screen 必须能投影:依赖有效 PlayerController(优先观察者 Pawn 的 PC,否则 First PC)。PC 无效或全部投影失败 → 退化为全部单点。
它的使用和另外一个系统是一样的,只是底层算法不同,这里不过多赘述。
方案对比
| 对比项 | 方案一:Distance Cluster(距离聚合) | 方案二:Screen Cluster(屏幕聚合) |
| 聚合依据 | 世界空间 Actor 距离 | 屏幕像素距离 |
| 核心计算 | Actor Location + 相机距离 | World To Screen 投影位置 |
| 适用场景 | 数字孪生、工业监控、3D虚拟仿真 | GIS地图、二维标注、地图POI |
| 聚合单位 | 世界单位(cm) | 屏幕单位(px) |
| 主要参数 | Merge Distance、Expand Distance、Add Scale Speed | Merge Pixel Radius |
| 远距离效果 | 相机越远,有效合并范围越大,更多标牌聚合 | 屏幕点位越密集,自动聚合 |
| 近距离效果 | Expand范围内可强制拆开显示单体 | 屏幕间距变大自动散开 |
| 空间准确性 | 高,符合真实三维距离关系 | 较低,只关注视觉距离 |
| 视觉一致性 | 需要调节距离参数 | 高,看到的效果与屏幕一致 |
| 是否支持局部拆分 | ✅ 支持(Expand Distance) | ❌ 不支持 |
| 相机影响 | 相机距离影响聚合 | 相机投影影响聚合 |
| 性能控制 | Distance of Change节流计算 | Distance of Change + 视角变化更新 |
| Observer Pawn | 推荐填写 | 必须有效 |
| Marker对象池 | 支持 | 支持 |
| 聚合结果输出 | ClusterLocation、ActorCount、Actors | ClusterLocation、ActorCount、Actors |
| 标牌显示方式 | BP_After_JH + Cluster Marker Interface | BP_After_JH + Cluster Marker Interface |
| 优点 | 空间逻辑真实,适合大型三维场景 | 视觉效果稳定,适合地图显示 |
| 缺点 | 需要调距离参数 | 不考虑真实空间距离 |
三、节点
**1、**Initialize Distance/screen Cluster System【分别对应两个不同的方案】



初始化一个距离/屏幕聚类系统,并创建对应的 Cluster Manager。
可以通过 Settings 结构体配置聚类系统的初始参数。
**Settings** 结构体支持直接拆分,可以分别设置其中的聚类参数。
该节点主要完成:
- 创建 Cluster Manager
- 将 Manager 注册到 Distance Cluster Subsystem
- 设置初始 Actor 列表
- 设置 Observer Pawn
- 设置聚类 Settings
- 设置 Marker Class
- 初始化 Marker 对象池
初始化完成后,需要配合 Set Cluster System Active 激活系统。
**2、**Set Cluster System Active

控制指定 Cluster Manager 的运行状态。
设置为 false:
- 停止当前聚类系统
- 清空当前 Clusters
- Release 数字标牌回对象池
- 隐藏已经创建的 Cluster Marker
- 不 Destroy Marker,后续可以复用
- 如果 Initialize 时设置了
Marker Class,同时隐藏全部输入 Actor
设置为 true:
- 激活 Cluster Manager
- 立即重新计算 Clusters
- 恢复动态聚类
- 根据当前聚类结果控制输入 Actor 和 Marker 的显隐
适合用于楼层切换、区域切换、系统暂停/恢复等场景。

**3、**获取Distance Settings/Screen Settings
获取manager结果



4、事件On Cluster Marker Updated

需要 Cluster Marker Actor 实现:Cluster Marker Interface
当 Cluster Manager 创建、更新或重新计算某个 Cluster Marker 时,会触发该事件。
可以通过该事件获取当前 Cluster 的相关数据,并驱动自己的 Marker 表现。
**5、Get Dynamic Cluster subsystem**

全局聚类系统入口,负责统一管理场景中的多个 Cluster Manager。
一个 Subsystem 可以同时注册和管理多个独立的聚类 Manager,每个 Manager 可以拥有自己独立的:
- Actor 列表
- Observer Pawn
- 聚类模式
- Distance / Screen 参数
- Marker Class
- Cluster 状态
- Marker 对象池
Subsystem 本身主要负责 Manager 的注册、查找、创建、销毁和统一生命周期管理,具体的聚类计算与 Marker 显隐由对应的 Manager 负责。
四、使用指南
首先明确:
| 入口节点 | 合并依据 | 走近散开 |
| Initialize Distance Cluster System【按距离聚合】 | 世界 Actor 球半径;越远离质心,合并半径越大,增值的计算公式:EffMerge =Merge+speed×(DistCenter−Expand),具体见该节相关介绍。 | 两层规则:两层用途,互不推迟:① DistCenter ≤ Expand → 全部拆成单点,并冻结合并半径重算;② DistCenter > Expand 时,相机到某 Actor ≤ Expand → 该 Actor 就地拆开(靠边簇)。0 = 关闭全拆/局部拆,始终按公式合并 |
| Initialize Screen Cluster System【按屏幕聚合】 | 屏幕像素种子半径 | 投影自然散开(注意:角度也会改变投影) |
合并方式:合并方式均为 种子半径、非传递:种子只吸收自身半径内的点,不会 A→B→C 链式合并。
示例关卡介绍
使用该插件你所需资产汇总:
所需组件:AECMouseController 所需插件:DynamicClusterPlugin
| 类型 | 名称 | 数量 | 用途 |
| Blueprint Actor | BP_Before_JH | 多个(看你几个楼层,目前关卡中2个) | 聚合前单体标牌 |
| Blueprint Actor | BP_After_JH【改actor需实现插件自带接口On cluster Marker Updated,将数值给到相关控件text】 | 一个 | 聚合结果标牌 |
| Blueprint Actor | BP_DistanceManager/BP_screenManager | 一个 | 聚合系统管理 |
| Widget Blueprint | WBP_num | 一个 | 显示聚合数量 |
| Widget Blueprint | WBP_jk | 一个 | 显示单个actor图标样式(如单个监控) |
当前展示关卡的资产如下:

☆实现路径☆:
第一步: 准备聚合前后UMG控件

其中WBP_num显示数字的text要绑定一个变量或者设置一个函数方便后面调用赋值。
第二步: 准备聚合对象与聚合标牌 Actor



**注意:**这里的挂载了控件组件的聚合显示的actor要实现On Cluster Marker Updated接口(插件自带)
第三步:放置聚合对象 Actor
将需要参与聚合计算的 Actor:BP_Before_JH,按照实际场景需求放置到对应位置。
系统运行后,会自动获取这些 Actor 的空间位置,并根据设置的聚合规则进行计算。
场景中的源 Actor 不需要额外编写聚合逻辑,只需要满足:
- 类型符合要求
- Tag 设置正确
- 被聚合管理器识别
即可参与聚合。

第四步: 创建聚合管理器并初始化系统
准备一个聚合管理器(这里以BP_DistanceManger举例),里面的蓝图如下:

创建聚合管理器 Actor:
例如:BP_DistanceManager
在管理器中调用:Initialize Distance Cluster System 并输入相关参数完成聚合系统初始化。
初始化完成后,系统会自动完成:
- 对象注册
- 距离计算
- 聚合关系计算
- 聚合/拆散状态切换
同时连接:Set Cluster System Active用于控制系统初始状态。
第五步: 控制不同区域/楼层聚合状态
在项目逻辑中,根据实际业务需求控制聚合系统启停。核心节点**【Set cluster System Active】**
实现:
| 状态 | 效果 |
| 当前楼层开启 | 激活该楼层聚合计算 |
| 当前楼层关闭 | 停止该楼层聚合计算 |

最终效果图:

项目应用举例
注意:插件具体使用方法参照☆实现路径☆:即可
全民健身房项目(标牌聚合部分优化步骤记录)
第一步:初始化聚合系统【原项目各类actor等内容均已准备好】
首先在项目中调用 Initialize Distance Cluster System 节点,对标牌聚合系统进行初始化。
并输入相关配置参数。完成初始化后,系统开始对场景中的监控标牌进行距离检测与自动聚合计算。
第二步:根据楼层控制聚合系统启停
项目原有逻辑中已经存在楼层开关控制功能。
将原项目中的楼层控制 Bool 参数(BPI Set Tilp State) 接入:Set Cluster System Active节点。
实现:
- 当前楼层开启 → 启用该楼层标牌聚合计算
- 当前楼层关闭 → 停止该楼层聚合计算
流程:
楼层切换
↓
获取楼层显示状态 Bool
↓
Set Cluster System Active
↓
控制对应楼层聚合系统开关
第三步:标牌聚合与单体显示自动切换
系统会根据摄像机距离自动判断当前显示方式:
摄像机距离较远
↓
多个标牌满足聚合条件
↓
显示聚合标牌
摄像机距离较近
↓
取消聚合
↓
显示单个监控标牌无需额外手动控制,实现:
- 聚合标牌显示
- 单个监控标牌显示
两种状态的自动切换。
第四步:实现聚合标牌数量显示
在监控聚合 Actor 中:
(该 Actor 需要挂载聚合控件组件)
实现接口:On Cluster Marker Updated 用于接收聚合系统返回的数据。
接口返回:Actor Count 表示当前聚合簇中包含的标牌数量。
流程:
Cluster System
↓
计算聚合结果
↓
On Cluster Marker Updated
↓
获取 Actor Count
↓
更新聚合控件文本
↓
显示当前簇数量
第五步:自定义聚合控件显示
在自定义聚合 Widget 中:
创建文本显示区域:TextBlock
接收:Actor Count并转换为文本【这里是创建了一个函数Set Value,在Tilp_监控聚合中调用函数赋值对应text】
最终效果:
◎
12
表示:
当前聚合区域包含12个监控标牌五、常见问题
Q:动态对象池怎么用,显隐怎么控?
提示:首先聚类本身不依赖对象池。场景里放好源 Actor;需要数字聚合标牌时在 Initialize 填 Marker Class
- 在 Initialize * Cluster System 填写 Marker Class → Initialize 绑定池、开启源 Actor 显隐同步;之后每次重算会 Sync 池并 自动切换显隐。在你的actor里面实现 Cluster Marker Interface【接口】 可收到 Count(每个簇里面actor的数量)。
- 不填 Marker Class = 只算聚类,不 Spawn 标牌、不改源 Actor 显隐。
- 池 Actor 带 Tag
DynamicCluster.Marker,不会被再吃进聚类列表。
Q:Manager Id分类的含义?
Manager Id不同 = 两套互不影响的聚类。同一 Id 复用已有 Manager。空 / None →"Default"。- 也可:
Get Or Create Cluster Manager("Floor_A")→ 再Set clustersystem Active


控制单层显隐
- 重复 Initialize 会重置系统(重新绑列表 / 设置 / 池)
Q:Distance / Screen 是不是每帧都算?能不能少算?
Distance:合并区用 Distance of change 节流 EffMerge;局部 Expand 拆开与进出 DistCenter Expand 立即生效。Screen:位移 ≥ DoC 或转角约 2° 才重算;DoC = 0 每帧。
**Q:Distance 合并怎么随相机远近变化?**
两层规则,不冲突:
DistCenter ≤ Expand → 全部拆开,并冻结合并计算;再次 > Expand 才恢复动态聚合。
DistCenter > Expand → EffMerge 更新受 Distance of change 约束(DistCenter 变化不够就不重算合并半径)。
同时:相机到某个 Actor ≤ Expand → 该 Actor 立刻就地拆开(靠边簇不必 DistCenter 降到 Expand 以下;也不被 DoC 推迟)。
常见错误
| 错误 | 结果 |
| 未成簇时 For Each + Spawn 单个监控 | 极卡,Actor 越积越多 |
| 重复 Initialize | 会重置系统 |
把通用 Actor 直接接到自定义组件 Target |
连不上;先 Cast To 你的蓝图类 |
| 把池标牌塞进 Actors 列表 | 会被 Tag 过滤掉,不参与聚类 |
暂无评论,来抢沙发吧~