使用此脚本,您可以使用任意形状对精灵(Sprite)进行分割/细分,为生成的碎片分配物理体,并创建破坏效果。
您可以自由配置碎片大小、物理属性、分割数量、密度梯度、材质等。
此外,您还可以在碎片首次分割后进一步细分(重新切割)这些碎片。

boolean_sprite_cutter_with_tagged_shapes.gd (59.0 KB)
fragment_manager.gd (16.3 KB)
BooleanSpriteCutterWithTaggedShapesSampleProject.zip (635.2 KB)
注意: 此脚本需要 GDScript Delaunay + Voronoi。
由于逻辑较为复杂,建议下载示例项目。
使用示例 / 示例项目说明
0. 术语表
| 术语 | 含义 |
|---|---|
| Cutter(切割器) | BooleanSpriteCutterWithTaggedShapes(主要的切割处理器;具有 call_cut() 方法) |
| 切割目标 | target_sprite_group 中的 Sprite2D 节点(以及可选的允许被重新切割的碎片 RigidBody2D 节点) |
| 遮罩形状 | shape_node_group 中的形状节点(圆形/矩形/多边形等) |
| 内部碎片 | 从与遮罩重叠的区域生成的碎片(_fragment_type="inside") |
| 外部碎片 | 从遮罩外部剩余区域生成的碎片(_fragment_type="outside") |
| Manager(管理器) | FragmentManager(自动管理碎片的生存周期/数量限制;通常作为自动加载单例保留) |
1. 设置(使其“准备好切割”)
1-1. 启用 gdDelaunay 以使用 Voronoi 分割
目的
Cutter 使用 Voronoi 分区来生成碎片的“破碎方式”。Voronoi 计算使用 res://addons/gdDelaunay/Delaunay.gd,因此您的项目中必须存在该插件,且插件必须已启用。如果未配置此项,Cutter 将无法在内部加载 Delaunay,导致切割过程失败。
设置步骤(添加到您的项目时)
- 将
addons/gdDelaunay/放入您的项目中。 - 在
项目 > 项目设置 > 插件中,启用gdDelaunay。
在示例项目中
res://addons/gdDelaunay/已包含在内。project.godot已在[editor_plugins] enabled下注册了res://addons/gdDelaunay/plugin.cfg。
1-2. 保持 FragmentManager 运行以自动管理碎片(推荐)
目的
切割会生成许多 RigidBody2D 碎片。由于碎片涉及物理、碰撞和渲染,如果不加管理,负载很容易增加。保持 FragmentManager 运行可以自动化以下“清理”操作:
- 在 TTL(生存时间)后删除碎片
- 如果碎片数量超过限制,优先删除较旧的碎片
- 一旦碎片减速到一定程度,将其冻结以减少物理负载
- (可选)删除超出屏幕范围的碎片
设置步骤(与示例项目风格相同)
- 准备
fragment_manager.tscn(根节点为Node2D并附加了FragmentManager脚本)。 - 将其添加到
项目 > 项目设置 > 自动加载中。
- 名称必须与 Cutter 引用的名称匹配。
- 路径应指向
fragment_manager.tscn所在的位置。
在示例项目中
- 自动加载名称:
FragmentManagerSingleton - 路径:
res://fragment_manager.tscn - 这与 Cutter 默认值
manager_autoload_name="FragmentManagerSingleton"匹配,因此无需额外设置即可自动链接。
1-3. 配置 InputMap 以便通过输入触发切割
目的
Cutter 不是一个监听输入的节点;它是一个在调用 call_cut() 时执行切割的节点。因此,您需要一个入口点,在所需的任何时机(玩家输入、UI 按钮、事件等)调用 call_cut()。最简单的 Godot 标准入口点是 InputMap(输入动作)。
最小设置(Godot 标准输入)
- 在
项目 > 项目设置 > 输入映射中添加一个动作(例如call_cut)。 - 分配一个按键(例如空格键)。
- 将输入脚本附加到任何节点并调用
call_cut()。
在示例项目中
call_cut已注册在InputMap中。- 空格键已分配给
call_cut。
1-4. 将目标 Sprite2D 节点添加到“目标组”
目的
每次切割时,Cutter 使用 get_tree().get_nodes_in_group(target_sprite_group) 收集候选对象。因此,您想要切割的任何 Sprite2D 都必须在此组中。如果不在,调用 call_cut() 时看似没有反应,因为没有目标。
此外,Cutter 从目标精灵纹理读取 Alpha 值并构建基础多边形(可切割区域)。如果精灵没有纹理,或者由于 Alpha 阈值处理无法提取不透明区域,它将被跳过。
步骤
- 选择您要切割的
Sprite2D。 - 在节点的“组”中添加
SpriteGroup(或 Cutter 中设置的组名称)。 - 确保已分配
Sprite2D.texture。
在示例项目中
- 在
res://sample.tscn中,Egg位于groups=["SpriteGroup"]。
1-5. 将遮罩形状节点添加到“遮罩组”
目的
Cutter 从 shape_node_group 组中收集“遮罩形状”,并为每个形状查找重叠的切割目标。如果没有注册遮罩形状,Cutter 将没有依据来分类内部/外部区域,无法继续处理。
支持的形状通过脚本内部的分支处理;典型支持类型包括:
Polygon2D/CollisionPolygon2DCollisionShape2D(CircleShape2D/RectangleShape2D/ConvexPolygonShape2D/ConcavePolygonShape2D等)
步骤
- 准备用作遮罩的形状节点(例如
CollisionShape2D+CircleShape2D)。 - 将其作为
CutterShapeGroup(或 Cutter 中设置的组名称)添加到组中。 - 放置它,使其与您要切割的精灵重叠。
- 如果位置不重叠,它将被视为“AABB 不相交”并被跳过。
在示例项目中
- 在
res://spritecutter.tscn中,CollisionShape2D2位于groups=["CutterShapeGroup"]。 - 形状:
CircleShape2D,radius=135.004。 - 遮罩形状作为
BooleanSpriteCutterWithTaggedShapes的子节点放置,并定位使其与场景中的切割目标重叠。
1-6. 在场景中放置 Cutter
目的
Cutter 假设它作为场景中的一个节点存在,原因如下:
- 通过 SceneTree 搜索组(收集切割目标和遮罩形状)
- 使用
to_global()等方法将形状转换为世界空间多边形 - 使用
to_global()将目标精灵的基于 Alpha 的局部多边形转换为世界空间 - 将生成的碎片作为某些节点的子节点添加(取决于生成/父级模式)
- 可选地通过
NodePath引用爆炸中心节点(例如inside_explosion_center_node)
因此,仅拥有脚本文件是不够的:您必须在场景中放置一个 Cutter 节点,并确保可以引用切割目标、遮罩和爆炸中心。
步骤(与示例结构相同)
- 在场景中放置一个 Cutter 节点(
Node2D)。 - 附加
boolean_sprite_cutter_with_tagged_shapes.gd。 - 将遮罩形状节点(
CutterShapeGroup)作为 Cutter 的子节点放置。
- 这使得移动 Cutter 和遮罩变得容易。
- 如果使用爆炸中心,将
Node2D(例如ForceCenter)作为 Cutter 的子节点放置,并通过inside_explosion_center_node等引用它。
在示例项目中
res://spritecutter.tscn包含一个名为BooleanSpriteCutterWithTaggedShapes的Node2D,已附加脚本。ForceCenter是 Cutter 的子节点,通过inside_explosion_center_node=NodePath("ForceCenter")引用。- 遮罩形状
CollisionShape2D2也是 Cutter 的子节点,并注册在CutterShapeGroup中。
2. 执行(如何操作)
2-1. 执行切割
流程
- 按下空格键,调用
call_cut()。 - Cutter 从
SpriteGroup收集目标候选项。
- 对于
Sprite2D,它从 Alpha 生成基础多边形。 - 已切割的精灵(
_already_cut=true)将被跳过。
- Cutter 从
CutterShapeGroup收集遮罩形状,并为每个形状:
- 通过 AABB 相交过滤 → 矩形多边形相交 → 基础多边形相交
- 只有实际相交的目标才会被分割成碎片
- 内部区域使用 Voronoi 单元细分,并通过布尔交集生成碎片。
- 外部区域由遮罩裁剪,并生成碎片。
- 生成的碎片作为
RigidBody2D添加到场景中,并接收冲量/扭矩。 - 如果原始对象是
Sprite2D,它将被隐藏(visible=false,_already_cut=true)。
示例操作
- 运行(F5)。
- 按下空格键(输入动作
call_cut)。 Egg消失,碎片散落。
2-2. 碎片消失(当启用管理器时)
目的
如果不加管理,碎片会不断累积。示例启用了“一段时间后删除”功能,以便在测试期间负载不会轻易增加。这更多是一种操作保障措施,而非纯粹的视觉效果。
在示例中
FragmentManagerSingleton作为自动加载运行。fragment_manager.tscn中启用了 TTL:inside_ttl_seconds = 10.0outside_ttl_seconds = 10.0
- 因此,碎片在生成后约 10 秒被删除。
3. Cutter 设置(示例值)
3-1. “切割什么 / 用什么形状切割”
| 项目 | 目的(影响什么) | 示例值 (spritecutter.tscn) |
|---|---|---|
target_sprite_group |
从何处收集切割目标。不在此组中的精灵永远不会被处理。 | 默认 ("SpriteGroup") |
shape_node_group |
从何处收集遮罩形状。如果为空,无法确定内部/外部,处理无法继续。 | 默认 ("CutterShapeGroup") |
常见切割失败原因
- 目标不在
SpriteGroup中。 - 形状不在
CutterShapeGroup中。 - 目标精灵没有纹理。
- 遮罩和目标在世界空间中不重叠(AABB 不相交)。
3-2. 碎片粒度(Voronoi)
| 项目 | 目的 | 示例值 (spritecutter.tscn) |
|---|---|---|
voronoi_seed_count |
Voronoi 种子数量。增加它倾向于创建更多/更小的内部碎片。 | 20 |
voronoi_seed_density_mode |
种子分布偏差。增加向中心的密度倾向于创建“中心附近更细的裂纹”。 | 1 (TowardMaskCenter) |
关于负载与视觉效果的通知
- 增加
voronoi_seed_count会增加布尔多边形相交次数,倾向于增加负载。 - 如果您希望偏置“在哪里精细分割”,请使用密度模式,而不是过度增加数量。
3-3. 散射方向(内部 / 外部)
| 项目 | 目的 | 示例值 (spritecutter.tscn) |
|---|---|---|
inside_force_base_strength |
内部碎片散射强度(基础冲量) | 1300.0 |
inside_force_direction_mode |
内部方向选择(固定向量 / 从爆炸中心径向) | 1 (ExplosionFromPoint) |
inside_explosion_center_node |
内部爆炸中心 (NodePath) |
NodePath("ForceCenter") |
outside_force_direction_mode |
外部方向选择 | 1 (ExplosionFromPoint) |
示例中的爆炸中心
ForceCenter是 Cutter 的子节点。ForceCenter.position = (0, 134)。- 爆炸方向是从中心到碎片(向外),产生散射效果。
3-4. 外观(边缘线)
| 项目 | 目的 | 示例值 (spritecutter.tscn) |
|---|---|---|
draw_edge_line |
使用 Line2D 绘制碎片多边形轮廓,使裂纹更容易看到。 |
false |
注意
- 边缘线为每个碎片添加一个
Line2D,当碎片数量高时可能会增加绘制成本。 - 示例中禁用了它,以优先考虑性能和简洁性。
4. FragmentManager 设置(示例值)
4-1. 生存周期(TTL)
| 项目 | 目的 | 示例值 (fragment_manager.tscn) |
|---|---|---|
inside_enable_ttl / inside_ttl_seconds |
在固定时间后删除内部碎片,防止其无限期存在。 | true / 10.0 |
outside_enable_ttl / outside_ttl_seconds |
在固定时间后删除外部碎片。 | true / 10.0 |
注意事项
- TTL 基于生成后的经过时间,而不是物理是否已稳定。
- 如果您希望碎片保持更长时间以显示效果,请增加秒数。
- 如果您希望无限期保留它们,请设置
*_enable_ttl=false,但建议使用最大限制/冻结等以避免负载问题。
4-2. 稳定时冻结(在示例中禁用)
| 项目 | 目的 | 示例值 (fragment_manager.tscn) |
|---|---|---|
inside_enable_freeze_on_settled |
一旦内部碎片充分稳定,将其冻结以减少物理更新。 | false |
outside_enable_freeze_on_settled |
外部碎片同理。 | false |
inside_disable_collision_when_frozen |
冻结后,将碰撞层/掩码设置为 0 以减少碰撞成本。 | false |
outside_disable_collision_when_frozen |
外部碎片同理。 | false |
何时启用冻结
- 如果您希望“碎片落到地面并停止”,但希望在停止后减少物理更新,这很有用。
- 一旦冻结,除非稍后施加外力,否则碎片不会移动。
5. 修改步骤(常见用例)
5-1. 添加更多切割目标
目的
允许多个精灵被同一个 Cutter 切割。由于 Cutter 通过组收集,添加目标的成本很低。
步骤
- 选择您要切割的
Sprite2D。 - 将其添加到
SpriteGroup组中。 - 确保它与遮罩形状重叠。
在示例中
- 只有
Egg在SpriteGroup中,因此只有Egg被切割。
5-2. 添加更多遮罩形状(使用多个遮罩切割)
目的
放置多个形状,“仅切割与每个形状重叠的目标”。Cutter 按顺序处理 CutterShapeGroup 中的所有形状。
步骤
- 创建额外的形状节点(例如另一个
CollisionShape2D)。 - 将其作为
CutterShapeGroup添加到组中。 - 定位它以重叠切割目标。
在示例中
- 只有
CollisionShape2D2(圆形)在CutterShapeGroup中。
5-3. 使碎片更精细
目的
创建更“破碎”的印象。这主要影响内部碎片。
步骤
- 增加
voronoi_seed_count。 - 如果需要,通过
voronoi_seed_density_mode和voronoi_seed_density_power调整偏差。 - 如果性能有问题,增加
simplify_tolerance以减少基础多边形顶点数。
在示例中
voronoi_seed_count = 20。
5-4. 使碎片更快消失 / 保持更长时间
目的
平衡效果持续时间和性能。示例使用 10 秒以避免测试期间的累积。
步骤
- 打开
fragment_manager.tscn。 - 调整
inside_ttl_seconds和outside_ttl_seconds。 - 若要无限期保留,请设置
*_enable_ttl=false(建议结合最大限制和/或冻结使用)。
在示例中
- 内部和外部均为
ttl_seconds = 10.0。
6. 故障排除
| 症状 | 可能原因 | 解决方法 |
|---|---|---|
| 按下空格键无反应 | InputMap 中没有 call_cut,或按键绑定错误 |
在 Input Map 中创建 call_cut 并绑定空格键 |
调用 call_cut() 但未切割 |
目标 Sprite2D 不在 SpriteGroup 中 |
将 SpriteGroup 添加到目标精灵的组中 |
调用 call_cut() 但未切割 |
遮罩形状不在 CutterShapeGroup 中 |
将 CutterShapeGroup 添加到形状节点的组中 |
| 未切割 / 仅有时切割 | 遮罩和目标不重叠(AABB 不相交) | 调整位置/缩放/旋转,使其明显重叠 |
| 碎片不断增加 | FragmentManager 未运行 / 自动加载名称不匹配 |
设置自动加载 FragmentManagerSingleton="res://fragment_manager.tscn" |
| 碎片负载高 | voronoi_seed_count 过高 / 启用了边缘线 / 太多碎片保留 |
降低 voronoi_seed_count,禁用 draw_edge_line,缩短 TTL |
| 碎片消失太快 | TTL 太短 | 增加 inside_ttl_seconds / outside_ttl_seconds(示例为 10 秒) |