简介

(应用展示) 版本: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 更新逻辑:

  1. DistCenter ≤ Expand → 全拆为单点;留在 Expand 内时不再按公式重算合并(若结果里仍有成簇会再强制拆一次)。
  2. DistCenter > Expand → 远处按 EffMerge(就是按公式计算的那个值) 种子合并;相机到 Actor ≤ Expand 的立刻就地拆开(不受 DoC 推迟)。
  3. 远处 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【分别对应两个不同的方案】

image-20260901222209710
image-20260901222209710

image-20260901222141844
image-20260901222141844

image-20260901222149459
image-20260901222149459

初始化一个距离/屏幕聚类系统,并创建对应的 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节点。

实现:

  • 当前楼层开启 → 启用该楼层标牌聚合计算
  • 当前楼层关闭 → 停止该楼层聚合计算

流程:

text
楼层切换
    ↓
获取楼层显示状态 Bool
    ↓
Set Cluster System Active
    ↓
控制对应楼层聚合系统开关

第三步:标牌聚合与单体显示自动切换

系统会根据摄像机距离自动判断当前显示方式:

text
摄像机距离较远
        ↓
多个标牌满足聚合条件
        ↓
显示聚合标牌


摄像机距离较近
        ↓
取消聚合
        ↓
显示单个监控标牌

无需额外手动控制,实现:

  • 聚合标牌显示
  • 单个监控标牌显示

两种状态的自动切换。


第四步:实现聚合标牌数量显示

在监控聚合 Actor 中:

(该 Actor 需要挂载聚合控件组件)

实现接口:On Cluster Marker Updated 用于接收聚合系统返回的数据。

接口返回:Actor Count 表示当前聚合簇中包含的标牌数量。

流程:

text
Cluster System
        ↓
计算聚合结果
        ↓
On Cluster Marker Updated
        ↓
获取 Actor Count
        ↓
更新聚合控件文本
        ↓
显示当前簇数量


第五步:自定义聚合控件显示

在自定义聚合 Widget 中:

创建文本显示区域:TextBlock

接收:Actor Count并转换为文本【这里是创建了一个函数Set Value,在Tilp_监控聚合中调用函数赋值对应text】

最终效果:

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 过滤掉,不参与聚类