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)
- Place
addons/gdDelaunay/into your project. - In
Project > Project Settings > Plugins, enablegdDelaunay.
In the sample project
res://addons/gdDelaunay/is included.project.godotalready registersres://addons/gdDelaunay/plugin.cfgunder[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)
- Prepare
fragment_manager.tscn(rootNode2Dwith theFragmentManagerscript attached). - Add it to
Project > Project Settings > Autoload.
- The Name must match the name referenced by the Cutter.
- Path should point to where
fragment_manager.tscnis 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)
- Add an action in
Project > Project Settings > Input Map(e.g.call_cut). - Assign a key (e.g. Space).
- Attach an input script to any node and call
call_cut().
In the sample project
call_cutis already registered inInputMap.- 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
- Select the
Sprite2Dyou want to cut. - Add
SpriteGroup(or the group name set in the Cutter) in the node’s Groups. - Ensure
Sprite2D.textureis assigned.
In the sample project
- In
res://sample.tscn,Eggis ingroups=["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/CollisionPolygon2DCollisionShape2D(CircleShape2D/RectangleShape2D/ConvexPolygonShape2D/ConcavePolygonShape2D, etc.)
Steps
- Prepare a shape node to use as a mask (e.g.
CollisionShape2D+CircleShape2D). - Add it to Groups as
CutterShapeGroup(or the group name set in the Cutter). - 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,CollisionShape2D2is ingroups=["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)
- Place a Cutter node (
Node2D) in your scene. - Attach
boolean_sprite_cutter_with_tagged_shapes.gd. - Place mask shape nodes (
CutterShapeGroup) as children of the Cutter.
- This makes it easy to move the Cutter and mask together.
- If using an explosion center, place a
Node2D(e.g.ForceCenter) as a child of the Cutter and reference it viainside_explosion_center_node, etc.
In the sample project
res://spritecutter.tscncontains aNode2DnamedBooleanSpriteCutterWithTaggedShapeswith the script attached.ForceCenteris a child of the Cutter, referenced viainside_explosion_center_node=NodePath("ForceCenter").- Mask shape
CollisionShape2D2is also a child of the Cutter and registered inCutterShapeGroup.
2. Execution (How to operate it)
2-1. Perform a cut
Flow
- Press Space, which calls
call_cut(). - The Cutter collects target candidates from
SpriteGroup.
- For
Sprite2D, it generates a base polygon from alpha. - Sprites already cut (
_already_cut=true) are skipped.
- 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
- The inside region is subdivided using Voronoi cells and fragments are generated via boolean intersection.
- The outside region is clipped by the mask and fragments are generated.
- Generated fragments are added to the scene as
RigidBody2Dand receive impulse/torque. - If the original was a
Sprite2D, it is hidden (visible=false,_already_cut=true).
Sample operation
- Run (F5).
- Press Space (Input Action
call_cut). Eggdisappears 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
FragmentManagerSingletonis running as an Autoload.- TTL is enabled in
fragment_manager.tscn:inside_ttl_seconds = 10.0outside_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_countincreases 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
ForceCenteris 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
Line2Dper 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
- Select the
Sprite2Dyou want to cut. - Add
SpriteGroupto its Groups. - Ensure it overlaps the mask shape.
In the sample
- Only
Eggis inSpriteGroup, so onlyEggis 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
- Create an additional shape node (e.g. another
CollisionShape2D). - Add it to Groups as
CutterShapeGroup. - Position it to overlap the cut target.
In the sample
- Only
CollisionShape2D2(circle) is inCutterShapeGroup.
5-3. Make fragments finer
Purpose
Create a more “shattered” impression. This mainly affects inside fragments.
Steps
- Increase
voronoi_seed_count. - If needed, adjust bias via
voronoi_seed_density_modeandvoronoi_seed_density_power. - If performance is an issue, increase
simplify_toleranceto 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
- Open
fragment_manager.tscn. - Adjust
inside_ttl_secondsandoutside_ttl_seconds. - 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) |