BooleanSpriteCutterWithTaggedShapes / FragmentManager English Manual Part 1(About Sample Project)

Using this script, you can split/subdivide a sprite using arbitrary shapes, assign physics bodies to the resulting pieces, and create destruction effects.
You can freely configure fragment size, physical properties, number of splits, density gradients, materials, and more.
Optionally, you can also further subdivide (re-cut) the fragments after they have been split once.

ダウンロード

boolean_sprite_cutter_with_tagged_shapes.gd (59.0 KB)
fragment_manager.gd (16.3 KB)
BooleanSpriteCutterWithTaggedShapesSampleProject.zip (635.2 KB)

Note: This script requires GDScript Delaunay + Voronoi.

Because this is highly complex, downloading the sample project is recommended.


Usage Example / Sample Project Explanation

0. Terminology

Term Meaning
Cutter BooleanSpriteCutterWithTaggedShapes (the main cutting processor; has call_cut())
Cut targets Sprite2D nodes in target_sprite_group (and optionally fragment RigidBody2D nodes that are allowed to be re-cut)
Mask shapes Shape nodes in shape_node_group (circle/rectangle/polygon, etc.)
Inside fragments Fragments generated from the region overlapping the mask (_fragment_type="inside")
Outside fragments Fragments generated from the remaining region outside the mask (_fragment_type="outside")
Manager FragmentManager (automatically manages fragment lifetime/count limits; typically kept as an Autoload singleton)

1. Setup (Make it “ready to cut”)

1-1. Enable gdDelaunay to use Voronoi splitting

Purpose
The Cutter uses Voronoi partitioning to generate “how to break” the fragments. For Voronoi computation it uses res://addons/gdDelaunay/Delaunay.gd, so the add-on must exist in your project and the plugin must be enabled. If this is not configured, the Cutter cannot load Delaunay internally and the cutting process will fail.

Setup steps (when adding to your own project)

  1. Place addons/gdDelaunay/ into your project.
  2. In Project > Project Settings > Plugins, enable gdDelaunay.

In the sample project

  • res://addons/gdDelaunay/ is included.
  • project.godot already registers res://addons/gdDelaunay/plugin.cfg under [editor_plugins] enabled.

1-2. Keep FragmentManager running to auto-manage fragments (recommended)

Purpose
Cutting generates many RigidBody2D fragments. Since fragments have physics, collisions, and rendering, leaving them unmanaged can easily increase load. Keeping FragmentManager running automates “cleanup” such as:

  • Delete fragments after a TTL (time to live)
  • If fragment count exceeds a limit, delete older ones first
  • Freeze fragments once they slow down enough to reduce physics load
  • (Optional) Delete fragments that go off-screen

Setup steps (same style as the sample project)

  1. Prepare fragment_manager.tscn (root Node2D with the FragmentManager script attached).
  2. Add it to Project > Project Settings > Autoload.
  • The Name must match the name referenced by the Cutter.
  • Path should point to where fragment_manager.tscn is located.

In the sample project

  • Autoload name: FragmentManagerSingleton
  • Path: res://fragment_manager.tscn
  • This matches the Cutter default manager_autoload_name="FragmentManagerSingleton", so it auto-links without additional setup.

1-3. Configure InputMap so cutting can be triggered by input

Purpose
The Cutter is not an input-listening node; it is a node that performs cutting when call_cut() is invoked. Therefore, you need an entry point to call call_cut() at any desired timing (player input, UI button, events, etc.). The simplest Godot-standard entry point is InputMap (Input Actions).

Minimal setup (Godot standard input)

  1. Add an action in Project > Project Settings > Input Map (e.g. call_cut).
  2. Assign a key (e.g. Space).
  3. Attach an input script to any node and call call_cut().

In the sample project

  • call_cut is already registered in InputMap.
  • Space is already assigned to call_cut.

1-4. Add target Sprite2D nodes to the “target group”

Purpose
Each time, the Cutter collects candidates using get_tree().get_nodes_in_group(target_sprite_group). Therefore, any Sprite2D you want to cut must be in this group. If not, calling call_cut() will appear to do nothing because there are zero targets.

Also, the Cutter reads alpha from the target sprite texture and builds a base polygon (the cuttable region). If the sprite has no texture or the opaque region cannot be extracted due to alpha thresholding, it will be skipped.

Steps

  1. Select the Sprite2D you want to cut.
  2. Add SpriteGroup (or the group name set in the Cutter) in the node’s Groups.
  3. Ensure Sprite2D.texture is assigned.

In the sample project

  • In res://sample.tscn, Egg is in groups=["SpriteGroup"].

1-5. Add mask shape nodes to the “mask group”

Purpose
The Cutter collects “mask shapes” from the shape_node_group group, and for each shape, finds overlapping cut targets. If no mask shapes are registered, the Cutter has no basis to classify inside/outside regions and cannot proceed.

Supported shapes are handled via branching inside the script; typical supported types include:

  • Polygon2D / CollisionPolygon2D
  • CollisionShape2D (CircleShape2D / RectangleShape2D / ConvexPolygonShape2D / ConcavePolygonShape2D, etc.)

Steps

  1. Prepare a shape node to use as a mask (e.g. CollisionShape2D + CircleShape2D).
  2. Add it to Groups as CutterShapeGroup (or the group name set in the Cutter).
  3. Place it so it overlaps the sprite you want to cut.
  • If positions do not overlap, it will be treated as “AABB does not intersect” and skipped.

In the sample project

  • In res://spritecutter.tscn, CollisionShape2D2 is in groups=["CutterShapeGroup"].
  • Shape: CircleShape2D, radius=135.004.
  • The mask shape is placed as a child of BooleanSpriteCutterWithTaggedShapes, and positioned so it overlaps the cut target in the scene.

1-6. Place the Cutter in the scene

Purpose
The Cutter assumes it exists as a node in the scene for all of the following:

  • Searching groups via the SceneTree (collecting cut targets and mask shapes)
  • Converting shapes to world-space polygons using to_global(), etc.
  • Converting alpha-derived local polygons of target sprites into world-space using to_global()
  • Adding generated fragments as children of some node (depends on spawn/parent mode)
  • Optionally referencing explosion center nodes via NodePath (e.g. inside_explosion_center_node)

Therefore, merely having the script file is not sufficient: you must place a Cutter node in the scene and ensure cut targets, masks, and explosion centers can be referenced.

Steps (same structure as the sample)

  1. Place a Cutter node (Node2D) in your scene.
  2. Attach boolean_sprite_cutter_with_tagged_shapes.gd.
  3. Place mask shape nodes (CutterShapeGroup) as children of the Cutter.
  • This makes it easy to move the Cutter and mask together.
  1. If using an explosion center, place a Node2D (e.g. ForceCenter) as a child of the Cutter and reference it via inside_explosion_center_node, etc.

In the sample project

  • res://spritecutter.tscn contains a Node2D named BooleanSpriteCutterWithTaggedShapes with the script attached.
  • ForceCenter is a child of the Cutter, referenced via inside_explosion_center_node=NodePath("ForceCenter").
  • Mask shape CollisionShape2D2 is also a child of the Cutter and registered in CutterShapeGroup.

2. Execution (How to operate it)

2-1. Perform a cut

Flow

  1. Press Space, which calls call_cut().
  2. The Cutter collects target candidates from SpriteGroup.
  • For Sprite2D, it generates a base polygon from alpha.
  • Sprites already cut (_already_cut=true) are skipped.
  1. The Cutter collects mask shapes from CutterShapeGroup, and for each shape:
  • Filters by AABB intersection → rectangle polygon intersection → base polygon intersection
  • Only actually intersecting targets are fragmented
  1. The inside region is subdivided using Voronoi cells and fragments are generated via boolean intersection.
  2. The outside region is clipped by the mask and fragments are generated.
  3. Generated fragments are added to the scene as RigidBody2D and receive impulse/torque.
  4. If the original was a Sprite2D, it is hidden (visible=false, _already_cut=true).

Sample operation

  1. Run (F5).
  2. Press Space (Input Action call_cut).
  3. Egg disappears and fragments scatter.

2-2. Fragments disappear (when Manager is enabled)

Purpose
Fragments accumulate if left unmanaged. The sample enables “delete after some time” so load does not increase too easily during testing. This is more of an operational safeguard than purely a visual effect.

In the sample

  • FragmentManagerSingleton is running as an Autoload.
  • TTL is enabled in fragment_manager.tscn:
    • inside_ttl_seconds = 10.0
    • outside_ttl_seconds = 10.0
  • Therefore, fragments are deleted around 10 seconds after being generated.

3. Cutter Settings (Sample Values)

3-1. “What to cut / what shape to cut with”

Item Purpose (what it affects) Sample value (spritecutter.tscn)
target_sprite_group Where cut targets are collected from. Sprites not in this group are never processed. Default ("SpriteGroup")
shape_node_group Where mask shapes are collected from. If empty, inside/outside cannot be determined and processing cannot proceed. Default ("CutterShapeGroup")

Common reasons it does not cut

  • The target is not in SpriteGroup.
  • The shape is not in CutterShapeGroup.
  • The target sprite has no texture.
  • Mask and target do not overlap in world space (AABB does not intersect).

3-2. Fragment granularity (Voronoi)

Item Purpose Sample value (spritecutter.tscn)
voronoi_seed_count Number of Voronoi seeds. Increasing it tends to create more/smaller inside fragments. 20
voronoi_seed_density_mode Seed distribution bias. Increasing density toward the center tends to create “finer cracks near the center.” 1 (TowardMaskCenter)

Note on load vs visuals

  • Increasing voronoi_seed_count increases boolean polygon intersections and tends to increase load.
  • If you want to bias “where to split finely,” use the density mode rather than increasing the count excessively.

3-3. Scatter direction (inside / outside)

Item Purpose Sample value (spritecutter.tscn)
inside_force_base_strength Strength of inside fragment scattering (base impulse) 1300.0
inside_force_direction_mode Inside direction selection (fixed vector / radial from an explosion center) 1 (ExplosionFromPoint)
inside_explosion_center_node Explosion center for inside (NodePath) NodePath("ForceCenter")
outside_force_direction_mode Outside direction selection 1 (ExplosionFromPoint)

Explosion center in the sample

  • ForceCenter is a child node of the Cutter.
  • ForceCenter.position = (0, 134).
  • The explosion direction is from center to fragment (outward), producing a scattering effect.

3-4. Appearance (edge lines)

Item Purpose Sample value (spritecutter.tscn)
draw_edge_line Draw fragment polygon outlines with Line2D to make cracks easier to see. false

Note

  • Edge lines add one Line2D per fragment and can increase draw cost when fragment count is high.
  • The sample disables it to prioritize performance and simplicity.

4. FragmentManager Settings (Sample Values)

4-1. Lifetime (TTL)

Item Purpose Sample value (fragment_manager.tscn)
inside_enable_ttl / inside_ttl_seconds Delete inside fragments after a fixed time so they do not remain indefinitely. true / 10.0
outside_enable_ttl / outside_ttl_seconds Delete outside fragments after a fixed time. true / 10.0

Notes

  • TTL is based on elapsed time since spawn, not whether physics has settled.
  • If you want fragments to remain longer for the effect, increase the seconds.
  • If you want to keep them indefinitely, set *_enable_ttl=false, but use a max limit/freeze, etc., to avoid load issues.

4-2. Freeze when settled (disabled in the sample)

Item Purpose Sample value (fragment_manager.tscn)
inside_enable_freeze_on_settled Freeze inside fragments once sufficiently settled to reduce physics updates. false
outside_enable_freeze_on_settled Same for outside fragments. false
inside_disable_collision_when_frozen After freezing, set collision layer/mask to 0 to reduce collision cost. false
outside_disable_collision_when_frozen Same for outside. false

When to enable freeze

  • Useful if you want “fragments fall to the ground and stop,” but want to reduce physics updates after they stop.
  • Once frozen, fragments do not move unless you apply external forces later.

5. Modification Steps (Common Use Cases)

5-1. Add more cut targets

Purpose
Allow multiple sprites to be cut by the same Cutter. Since the Cutter collects via groups, adding targets is low-cost.

Steps

  1. Select the Sprite2D you want to cut.
  2. Add SpriteGroup to its Groups.
  3. Ensure it overlaps the mask shape.

In the sample

  • Only Egg is in SpriteGroup, so only Egg is cut.

5-2. Add more mask shapes (cut with multiple masks)

Purpose
Place multiple shapes and “cut only the targets overlapping each shape.” The Cutter processes all shapes in CutterShapeGroup sequentially.

Steps

  1. Create an additional shape node (e.g. another CollisionShape2D).
  2. Add it to Groups as CutterShapeGroup.
  3. Position it to overlap the cut target.

In the sample

  • Only CollisionShape2D2 (circle) is in CutterShapeGroup.

5-3. Make fragments finer

Purpose
Create a more “shattered” impression. This mainly affects inside fragments.

Steps

  1. Increase voronoi_seed_count.
  2. If needed, adjust bias via voronoi_seed_density_mode and voronoi_seed_density_power.
  3. If performance is an issue, increase simplify_tolerance to reduce base polygon vertex count.

In the sample

  • voronoi_seed_count = 20.

5-4. Make fragments disappear sooner / remain longer

Purpose
Balance effect duration and performance. The sample uses 10 seconds to avoid accumulation during testing.

Steps

  1. Open fragment_manager.tscn.
  2. Adjust inside_ttl_seconds and outside_ttl_seconds.
  3. To keep them indefinitely, set *_enable_ttl=false (recommended to combine with max limit and/or freeze).

In the sample

  • Both inside and outside are ttl_seconds = 10.0.

6. Troubleshooting

Symptom Likely cause Fix
Pressing Space does nothing No call_cut in InputMap, or wrong key binding Create call_cut in Input Map and bind Space
call_cut() is called but nothing is cut Target Sprite2D is not in SpriteGroup Add SpriteGroup to the target sprite’s Groups
call_cut() is called but nothing is cut Mask shape is not in CutterShapeGroup Add CutterShapeGroup to the shape node’s Groups
Not cut / only sometimes cut Mask and target do not overlap (AABB does not intersect) Adjust position/scale/rotation so they clearly overlap
Fragments keep increasing FragmentManager not running / Autoload name mismatch Set Autoload FragmentManagerSingleton="res://fragment_manager.tscn"
Fragments are heavy High voronoi_seed_count / edge lines enabled / too many fragments remain Lower voronoi_seed_count, disable draw_edge_line, shorten TTL
Fragments disappear too quickly TTL is too short Increase inside_ttl_seconds / outside_ttl_seconds (sample is 10s)

「いいね!」 1