…
BubbleLayout 气泡布局系统 — 操作手册
1. 架构总览
┌─────────────────────────────┐
│ BubbleLayoutGroup │
│ (宿主, 继承 LayoutGroup) │
│ │
│ 扫描 GameObject 上的插件 │
│ 管理 BubbleItemData 字典 │
│ 调度管线执行顺序 │
└──────────┬──────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────────────┐ ┌───────────────────────┐
│ 动画插件列表 │ │ 物理积分 (内置) │ │ 约束插件列表 │
│ IBubbleAnim- │ │ BubblePhysicsSolver │ │ IBubbleConstraint- │
│ ationPlugin[] │ │ .Integrate() │ │ Plugin[] │
│ │ │ │ │ │
│ 施加力/速度 │ │ F→a→v→position │ │ 限制位置/排列布局 │
└───────────────┘ └───────────────────────┘ └───────────┬───────────┘
│
┌────────▼────────┐
│ 气泡碰撞排挤 │
│ (内置, 四叉树) │
└────────┬────────┘
▼
┌────────────────┐
│ ApplyPositions │
│ → RectTransform│
└────────────────┘
每帧管线顺序(不可改变):
SyncItemList → 动画插件(按Order) → 物理积分 → 约束插件(按Order) → 碰撞排挤 → ApplyPositions
2. 核心数据类型:BubbleItemData
BubbleItemData.cs — 每个子 UI 元素的完整物理状态,插件通过它读写数据。
| 字段 | 类型 | 说明 |
|---|---|---|
rectTransform |
RectTransform |
关联的 UI 变换组件 |
Key |
string |
唯一标识 {name}_{instanceID} |
position |
Vector2 |
当前动画位置 |
size |
Vector2 |
UI 尺寸 |
rotation |
float |
旋转角度(度) |
velocity / prevVelocity |
Vector2 |
当前 / 上一帧速度 |
force / prevForce |
Vector2 |
当前 / 上一帧力 |
momentum |
Vector2 |
动量 |
angularMomentum |
float |
角动量 |
mass |
float |
质量(默认=1) |
关键约定:每帧物理积分后会清零
force。插件需要在每帧重新施加力。
3. 如何编写插件
3.1 动画插件 (IBubbleAnimationPlugin)
动画插件在物理积分之前执行,用于施加力/速度/动量。
using System.Collections.Generic;
using UnityEngine;
using XericUI.BubbleLayout;
[AddComponentMenu("Xeric UI Vessel/Layout/My Animation Plugin", 60)]
public class MyAnimationPlugin : MonoBehaviour, IBubbleAnimationPlugin
{
// ── 必须实现:Enabled 和 Order ──
[SerializeField] private bool m_Enabled = true;
[SerializeField] private int m_Order = 5;
public bool Enabled => m_Enabled;
public int Order => m_Order;
// ── 可自定义的参数 ──
[SerializeField] private float m_WindStrength = 10f;
// ── 核心方法:每帧调用,在此施加力 ──
public void ProcessAnimation(List<BubbleItemData> items, float deltaTime)
{
for (int i = 0; i < items.Count; i++)
{
var item = items[i];
// 示例:施加一个向右的风力
item.force += new Vector2(m_WindStrength, 0f);
// 示例:根据速度施加速度阻尼
// item.velocity *= 0.95f;
}
}
}
要点:
- 直接挂载到
BubbleLayoutGroup所在 GameObject 上即可被自动发现 items是所有气泡元素的列表,遍历它来施加力- 力通过
+=累加(多个插件可同时施加力) Order越小越先执行,建议范围 1~99
3.2 约束插件 (IBubbleConstraintPlugin)
约束插件在物理积分之后、碰撞排挤之前执行,用于排列布局或限制位置。
using System.Collections.Generic;
using UnityEngine;
using XericUI.BubbleLayout;
[AddComponentMenu("Xeric UI Vessel/Layout/My Constraint Plugin", 61)]
public class MyConstraintPlugin : MonoBehaviour, IBubbleConstraintPlugin
{
[SerializeField] private bool m_Enabled = true;
[SerializeField] private int m_Order = 20;
public bool Enabled => m_Enabled;
public int Order => m_Order;
[SerializeField] private float m_BoundaryRadius = 300f;
[SerializeField] private float m_AttractionForce = 50f;
public void ProcessConstraint(List<BubbleItemData> items, Vector2 layoutCenter)
{
for (int i = 0; i < items.Count; i++)
{
var item = items[i];
// 示例1:限制在圆形边界内
Vector2 toCenter = layoutCenter - item.position;
float dist = toCenter.magnitude;
if (dist > m_BoundaryRadius)
{
Vector2 clampedPos = layoutCenter + toCenter.normalized * m_BoundaryRadius;
// 方式A:直接修正位置(硬约束)
item.position = clampedPos;
}
// 示例2:通过弹簧力吸附到目标位置(软约束,推荐)
Vector2 targetPos = layoutCenter; // 目标位置
Vector2 springForce = (targetPos - item.position) * m_AttractionForce;
item.force += springForce;
}
}
}
要点:
- 可以直接修改
item.position(硬约束,即时生效) - 也可以施加
item.force(软约束,下帧物理积分生效,有平滑动画过渡) layoutCenter是 BubbleLayoutGroup 的 Rect 中心点- 约束阶段在碰撞排挤之前,所以碰撞排挤会修正可能的重叠
3.3 IBubbleLayoutPlugin 接口参考
public interface IBubbleLayoutPlugin
{
bool Enabled { get; } // Inspector 中可关闭
int Order { get; } // 优先级,值越大越靠后
}
public interface IBubbleAnimationPlugin : IBubbleLayoutPlugin
{
void ProcessAnimation(List<BubbleItemData> items, float deltaTime);
}
public interface IBubbleConstraintPlugin : IBubbleLayoutPlugin
{
void ProcessConstraint(List<BubbleItemData> items, Vector2 layoutCenter);
}
4. 内置插件一览
| 插件 | Order 建议 | 功能 |
|---|---|---|
VerticalLayoutConstraint |
10 | 竖排排列,支持拉链模式(一左一右交替) |
HorizontalLayoutConstraint |
10 | 横排排列,支持垂直对齐 |
CircularLayoutConstraint |
10 | 环绕排列,支持自动/固定角度 |
BubbleCollisionConstraint |
1000 | 独立碰撞排挤插件(可选,布局组件已内置) |
5. 如何着手了解源码
推荐阅读顺序:
IBubbleLayoutPlugin.cs— 接口定义,了解插件契约(~45行)BubbleItemData.cs— 数据模型,了解插件操作的数据对象(~100行)BubbleLayoutGroup.cs— 核心宿主,从RunLayoutPipeline()方法入手看管线调度(~700行)BubblePhysicsSolver.cs— 物理积分器,Integrate()方法(~100行)QuadTree.cs— 四叉树空间索引,Clear/Rebuild/Retrieve方法(~450行)- 任意内置插件(
VerticalLayoutConstraint.cs等)— 作为插件编写参考
关键断点位置:
| 方法 | 文件 | 作用 |
|---|---|---|
RunLayoutPipeline() |
BubbleLayoutGroup.cs:223 |
管线入口,从这里开始调试 |
SyncItemList() |
BubbleLayoutGroup.cs:329 |
子元素增删改同步 |
RunAnimationPhase() |
BubbleLayoutGroup.cs:457 |
动画插件调度 |
BubblePhysicsSolver.Integrate() |
BubblePhysicsSolver.cs:18 |
力→速度→位置 |
RunConstraintPhase() |
BubbleLayoutGroup.cs:470 |
约束插件调度 |
RunBubbleCollision() |
BubbleLayoutGroup.cs:488 |
四叉树碰撞排挤 |
定制流程参考:
- 想修改碰撞算法 → 看
RunBubbleCollision()+QuadTree.cs - 想修改物理模型 → 看
BubblePhysicsSolver.Integrate() - 想修改子元素管理 → 看
SyncItemList()+RefreshChildList() - 想修改插件发现机制 → 看
RefreshPlugins() - 想添加新的布局排列 → 参考
VerticalLayoutConstraint.cs,实现IBubbleConstraintPlugin
6. BubbleLayoutGroup 可调参数
| 参数 | 默认值 | 说明 |
|---|---|---|
Enable Plugins |
true | 全局插件开关 |
Physics Damping |
0.9 | 速度阻尼 (0=无阻尼, 1=完全停止) |
Physics Mass |
1 | 默认质量 |
Mass Scale With Area |
false | 开启后质量 = 基础质量 × UI面积 |
Physics Delta Time |
0 | 0=自动, >0=固定步长 |
Max Iterations |
10 | 碰撞排挤最大迭代次数 |
Stiffness |
1 | 排斥刚度 |
Min Separation |
2 | 最小分离距离(像素) |
Iteration Damping |
0.85 | 每轮迭代刚度递减系数 |
Layout Ignore Inactive |
true | 忽略未激活的子对象 |