BooleanSpriteCutterWithTaggedShapes / FragmentManager 英文手册 第1部分(关于示例项目)

使用此脚本,您可以使用任意形状对精灵(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,导致切割过程失败。

设置步骤(添加到您的项目时)

  1. addons/gdDelaunay/ 放入您的项目中。
  2. 项目 > 项目设置 > 插件 中,启用 gdDelaunay

在示例项目中

  • res://addons/gdDelaunay/ 已包含在内。
  • project.godot 已在 [editor_plugins] enabled 下注册了 res://addons/gdDelaunay/plugin.cfg

1-2. 保持 FragmentManager 运行以自动管理碎片(推荐)

目的
切割会生成许多 RigidBody2D 碎片。由于碎片涉及物理、碰撞和渲染,如果不加管理,负载很容易增加。保持 FragmentManager 运行可以自动化以下“清理”操作:

  • 在 TTL(生存时间)后删除碎片
  • 如果碎片数量超过限制,优先删除较旧的碎片
  • 一旦碎片减速到一定程度,将其冻结以减少物理负载
  • (可选)删除超出屏幕范围的碎片

设置步骤(与示例项目风格相同)

  1. 准备 fragment_manager.tscn(根节点为 Node2D 并附加了 FragmentManager 脚本)。
  2. 将其添加到 项目 > 项目设置 > 自动加载 中。
  • 名称必须与 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 标准输入)

  1. 项目 > 项目设置 > 输入映射 中添加一个动作(例如 call_cut)。
  2. 分配一个按键(例如空格键)。
  3. 将输入脚本附加到任何节点并调用 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 阈值处理无法提取不透明区域,它将被跳过。

步骤

  1. 选择您要切割的 Sprite2D
  2. 在节点的“组”中添加 SpriteGroup(或 Cutter 中设置的组名称)。
  3. 确保已分配 Sprite2D.texture

在示例项目中

  • res://sample.tscn 中,Egg 位于 groups=["SpriteGroup"]

1-5. 将遮罩形状节点添加到“遮罩组”

目的
Cutter 从 shape_node_group 组中收集“遮罩形状”,并为每个形状查找重叠的切割目标。如果没有注册遮罩形状,Cutter 将没有依据来分类内部/外部区域,无法继续处理。

支持的形状通过脚本内部的分支处理;典型支持类型包括:

  • Polygon2D / CollisionPolygon2D
  • CollisionShape2D (CircleShape2D / RectangleShape2D / ConvexPolygonShape2D / ConcavePolygonShape2D 等)

步骤

  1. 准备用作遮罩的形状节点(例如 CollisionShape2D + CircleShape2D)。
  2. 将其作为 CutterShapeGroup(或 Cutter 中设置的组名称)添加到组中。
  3. 放置它,使其与您要切割的精灵重叠。
  • 如果位置不重叠,它将被视为“AABB 不相交”并被跳过。

在示例项目中

  • res://spritecutter.tscn 中,CollisionShape2D2 位于 groups=["CutterShapeGroup"]
  • 形状:CircleShape2Dradius=135.004
  • 遮罩形状作为 BooleanSpriteCutterWithTaggedShapes 的子节点放置,并定位使其与场景中的切割目标重叠。

1-6. 在场景中放置 Cutter

目的
Cutter 假设它作为场景中的一个节点存在,原因如下:

  • 通过 SceneTree 搜索组(收集切割目标和遮罩形状)
  • 使用 to_global() 等方法将形状转换为世界空间多边形
  • 使用 to_global() 将目标精灵的基于 Alpha 的局部多边形转换为世界空间
  • 将生成的碎片作为某些节点的子节点添加(取决于生成/父级模式)
  • 可选地通过 NodePath 引用爆炸中心节点(例如 inside_explosion_center_node

因此,仅拥有脚本文件是不够的:您必须在场景中放置一个 Cutter 节点,并确保可以引用切割目标、遮罩和爆炸中心。

步骤(与示例结构相同)

  1. 在场景中放置一个 Cutter 节点(Node2D)。
  2. 附加 boolean_sprite_cutter_with_tagged_shapes.gd
  3. 将遮罩形状节点(CutterShapeGroup)作为 Cutter 的子节点放置。
  • 这使得移动 Cutter 和遮罩变得容易。
  1. 如果使用爆炸中心,将 Node2D(例如 ForceCenter)作为 Cutter 的子节点放置,并通过 inside_explosion_center_node 等引用它。

在示例项目中

  • res://spritecutter.tscn 包含一个名为 BooleanSpriteCutterWithTaggedShapesNode2D,已附加脚本。
  • ForceCenter 是 Cutter 的子节点,通过 inside_explosion_center_node=NodePath("ForceCenter") 引用。
  • 遮罩形状 CollisionShape2D2 也是 Cutter 的子节点,并注册在 CutterShapeGroup 中。

2. 执行(如何操作)

2-1. 执行切割

流程

  1. 按下空格键,调用 call_cut()
  2. Cutter 从 SpriteGroup 收集目标候选项。
  • 对于 Sprite2D,它从 Alpha 生成基础多边形。
  • 已切割的精灵(_already_cut=true)将被跳过。
  1. Cutter 从 CutterShapeGroup 收集遮罩形状,并为每个形状:
  • 通过 AABB 相交过滤 → 矩形多边形相交 → 基础多边形相交
  • 只有实际相交的目标才会被分割成碎片
  1. 内部区域使用 Voronoi 单元细分,并通过布尔交集生成碎片。
  2. 外部区域由遮罩裁剪,并生成碎片。
  3. 生成的碎片作为 RigidBody2D 添加到场景中,并接收冲量/扭矩。
  4. 如果原始对象是 Sprite2D,它将被隐藏(visible=false_already_cut=true)。

示例操作

  1. 运行(F5)。
  2. 按下空格键(输入动作 call_cut)。
  3. Egg 消失,碎片散落。

2-2. 碎片消失(当启用管理器时)

目的
如果不加管理,碎片会不断累积。示例启用了“一段时间后删除”功能,以便在测试期间负载不会轻易增加。这更多是一种操作保障措施,而非纯粹的视觉效果。

在示例中

  • FragmentManagerSingleton 作为自动加载运行。
  • fragment_manager.tscn 中启用了 TTL:
    • inside_ttl_seconds = 10.0
    • outside_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 通过组收集,添加目标的成本很低。

步骤

  1. 选择您要切割的 Sprite2D
  2. 将其添加到 SpriteGroup 组中。
  3. 确保它与遮罩形状重叠。

在示例中

  • 只有 EggSpriteGroup 中,因此只有 Egg 被切割。

5-2. 添加更多遮罩形状(使用多个遮罩切割)

目的
放置多个形状,“仅切割与每个形状重叠的目标”。Cutter 按顺序处理 CutterShapeGroup 中的所有形状。

步骤

  1. 创建额外的形状节点(例如另一个 CollisionShape2D)。
  2. 将其作为 CutterShapeGroup 添加到组中。
  3. 定位它以重叠切割目标。

在示例中

  • 只有 CollisionShape2D2(圆形)在 CutterShapeGroup 中。

5-3. 使碎片更精细

目的
创建更“破碎”的印象。这主要影响内部碎片。

步骤

  1. 增加 voronoi_seed_count
  2. 如果需要,通过 voronoi_seed_density_modevoronoi_seed_density_power 调整偏差。
  3. 如果性能有问题,增加 simplify_tolerance 以减少基础多边形顶点数。

在示例中

  • voronoi_seed_count = 20

5-4. 使碎片更快消失 / 保持更长时间

目的
平衡效果持续时间和性能。示例使用 10 秒以避免测试期间的累积。

步骤

  1. 打开 fragment_manager.tscn
  2. 调整 inside_ttl_secondsoutside_ttl_seconds
  3. 若要无限期保留,请设置 *_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 秒)

1 个赞