// personal reference · glass break scene transition · 3D shards · mockup for battle transition
Shatter transition captures the current scene as a screenshot, applies it to flat 3D shard pieces, then shatters them apart revealing the next scene underneath. Inspired by Code Monkey's Unity screen shatter tutorial.
| decision | choice | reason |
|---|---|---|
| Shard mesh | Flat pre-cut quads | Simple, no Voronoi fracture needed |
| Shard count | 25–35 pieces | Visual sweet spot, light on PC |
| UV mapping | Triplanar material | No Blender needed, seamless texture |
| Shard material | UNSHADED | Next scene lighting won't affect shards |
| Next scene | Live, not frozen | Atmosphere shows through gaps — dramatic |
| Black background | Optional (boolean) | Use for day→night transitions |
| Shard spin | 3D angular velocity | Cinematic feel |
| Explosion | Radiant from center | Realistic physics feel |
# =============================================
# SHATTER TRANSITION SETTINGS
# =============================================
# Number of shard pieces (25-35 recommended)
var SHARD_COUNT: int = 30
# How far shards fly outward (3D units)
var BLAST_POWER: float = 15.0
# Random angular spin on each shard
var SPIN_STRENGTH: float = 8.0
# How long shards stay before fading (seconds)
var SHARD_LIFETIME: float = 1.5
# How long fade out takes (seconds)
var FADE_DURATION: float = 0.4
# Show solid black background between shards
# true = black then fade to next scene (good for day→night)
# false = next scene visible through gaps immediately (dramatic)
var USE_BLACK_BACKGROUND: bool = false
# If USE_BLACK_BACKGROUND = true, how long to fade black to transparent
var BLACK_FADE_DURATION: float = 0.5
# =============================================
Capture the current scene as a static image texture. Apply to all shards. This is the "frozen frame" that shatters apart.
# Capture current viewport as ImageTexture
func capture_screenshot() -> ImageTexture:
var image = get_viewport().get_texture().get_image()
var texture = ImageTexture.create_from_image(image)
return texture
# Store it before loading next scene
var screenshot = capture_screenshot()
# then load Village, then apply screenshot to shards
UNSHADED mode means next scene lighting (day/night/storm) does NOT affect shard appearance. Screenshot always looks exactly as captured.
func create_shard_material(screenshot: ImageTexture) -> StandardMaterial3D:
var mat = StandardMaterial3D.new()
# KEY: unshaded — no lighting affects this material
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
# Apply screenshot as albedo texture
mat.albedo_texture = screenshot
# Triplanar mapping — seamless texture across all shards
# no UV unwrapping needed, projects from world space
mat.uv1_triplanar = true
mat.uv1_world_triplanar = true
# Enable transparency for fade out later
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.albedo_color = Color(1, 1, 1, 1) # fully opaque at start
return mat
Radiant explosion from center point. Closer pieces fly faster. Each piece also gets random 3D angular spin.
func blast_shards():
var explosion_origin: Vector3 = $ExplosionCenter.global_position
for shard in get_children():
if not shard is RigidBody3D: continue
# unfreeze
shard.freeze = false
# radiant direction from blast origin to shard
var direction: Vector3 = shard.global_position - explosion_origin
var distance: float = direction.length()
# closer = faster (inverse distance)
var impulse: Vector3 = direction.normalized() * (BLAST_POWER / max(distance, 0.1))
# add slight forward push (toward camera) for dramatic effect
impulse.z += BLAST_POWER * 0.3
shard.apply_central_impulse(impulse)
# random 3D angular spin
var spin = Vector3(
randf_range(-SPIN_STRENGTH, SPIN_STRENGTH),
randf_range(-SPIN_STRENGTH, SPIN_STRENGTH),
randf_range(-SPIN_STRENGTH, SPIN_STRENGTH)
)
shard.apply_torque_impulse(spin)
When enabled — solid black plane sits between shards and next scene. After shatter, black fades transparent revealing next scene. Good for dramatic day→night transitions.
func setup_black_background():
if not USE_BLACK_BACKGROUND: return
var bg_mesh = QuadMesh.new()
bg_mesh.size = Vector2(screen_width * 2, screen_height * 2) # oversized
var bg_mat = StandardMaterial3D.new()
bg_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
bg_mat.albedo_color = Color(0, 0, 0, 1) # solid black
bg_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
$BlackBackground.mesh = bg_mesh
$BlackBackground.material_override = bg_mat
$BlackBackground.position.z = -0.1 # slightly behind shards
# fade black to transparent after shatter done
func fade_black_background():
if not USE_BLACK_BACKGROUND: return
var tween = create_tween()
tween.tween_property(
$BlackBackground.material_override,
"albedo_color:a",
0.0, # fade to fully transparent
BLACK_FADE_DURATION
)
await tween.finished
# village now fully visible
After shards finish flying, fade them out and queue_free the entire shatter container. Never leave physics bodies running after transition ends.
func cleanup_shards():
# wait for shards to fly away
await get_tree().create_timer(SHARD_LIFETIME).timeout
# fade out all shards simultaneously
for shard in get_children():
if not shard is RigidBody3D: continue
var mat = shard.get_child(0).material_override
var tween = create_tween()
tween.tween_property(mat, "albedo_color:a", 0.0, FADE_DURATION)
# wait for fade to finish
await get_tree().create_timer(FADE_DURATION).timeout
# purge everything — shards, collisions, meshes all freed
queue_free()
# ShatterTransition.gd — attach to ShatterTransition node
extends Node3D
# =============================================
# SETTINGS
# =============================================
var SHARD_COUNT: int = 30
var BLAST_POWER: float = 15.0
var SPIN_STRENGTH: float = 8.0
var SHARD_LIFETIME: float = 1.5
var FADE_DURATION: float = 0.4
var USE_BLACK_BACKGROUND: bool = false
var BLACK_FADE_DURATION: float = 0.5
# =============================================
# MAIN TRIGGER
# =============================================
func start_transition(next_scene_path: String):
# 1. capture screenshot FIRST before loading next scene
var screenshot = capture_screenshot()
# 2. load next scene underneath
var next_scene = load(next_scene_path).instantiate()
get_parent().add_child(next_scene)
get_parent().move_child(next_scene, 0) # put behind everything
# 3. setup black background if enabled
setup_black_background()
# 4. create shards on top
create_shards(screenshot)
# 5. blast shards apart
blast_shards()
# 6. fade black if enabled
fade_black_background()
# 7. cleanup after transition
cleanup_shards()
# 8. remove old Town scene
get_parent().get_node("TownScene").queue_free()
# In Town.gd — when player hits exit trigger
func _on_exit_triggered():
var shatter = load("res://transitions/ShatterTransition.tscn").instantiate()
get_parent().add_child(shatter)
shatter.start_transition("res://scenes/Village.tscn")