Lesson 12
Two touches that make GridFall feel finished: a preview of the upcoming piece so players can plan, and a brief flash when lines clear so the payoff reads. Both reuse systems you already built.
To preview, we pick the next shape ahead of time and hold it. Update main.gd to keep a next_shape:
var next_shape: Dictionary = {}
func _ready() -> void:
board = Board.new(COLS, ROWS)
_draw_border()
game_over_panel.visible = false
restart_button.pressed.connect(_on_restart_pressed)
next_shape = Shapes.random_shape() # prime the pump
_refresh_hud()
spawn_piece()In spawn_piece(), use next_shape and then roll a new one:
func spawn_piece() -> void:
if game_over:
return
var shape: Dictionary = next_shape
next_shape = Shapes.random_shape()
_draw_preview()
if not _shape_fits_at(shape, SPAWN_ORIGIN):
_trigger_game_over()
return
active_piece = PieceScene.instantiate()
add_child(active_piece)
active_piece.setup(shape, SPAWN_ORIGIN)
_fall_accum = 0.0main.tscn, under Main (not the HUD, so it uses world space), add a Node2D named Preview. Position it to the right of the well, e.g. (360, 160).Render the next shape into it, reusing the Cell scene:
@onready var preview_root: Node2D = $Preview
func _draw_preview() -> void:
for child in preview_root.get_children():
child.queue_free()
if next_shape.is_empty():
return
for off in next_shape["cells"]:
var c: Cell = CellScene.instantiate()
preview_root.add_child(c)
# Offsets can be negative; nudge by (1,1) so the preview sits nicely.
var p: Vector2i = off + Vector2i(1, 1)
c.set_grid_position(p)
c.set_color(Shapes.COLORS[next_shape["id"]])queue_free the old preview
Clear the preview's children before drawing the new shape or old cells pile up and overlap. Same pattern as _render_board(): wipe, then draw.
A tiny flash sells the clear. We create a short white overlay across the cleared rows and fade it out with a Tween — Godot's built-in animation tool. Add to main.gd and call it from _award_score:
func _flash_clear() -> void:
var flash := ColorRect.new()
flash.color = Color(1, 1, 1, 0.6)
flash.size = Vector2(WELL_WIDTH, WELL_HEIGHT)
flash.position = Vector2.ZERO
flash.z_index = 5
add_child(flash)
# Fade alpha to 0 over 0.25s, then remove the node.
var tw: Tween = create_tween()
tw.tween_property(flash, "modulate:a", 0.0, 0.25)
tw.tween_callback(flash.queue_free)func _award_score(cleared: int) -> void:
cleared = clampi(cleared, 0, 4)
score += LINE_SCORES[cleared]
lines_cleared_total += cleared
_update_level()
_refresh_hud()
_flash_clear()Tweens in one line
create_tween() makes a throwaway animator. tween_property(target, "modulate:a", 0.0, 0.25) animates the alpha to 0 over a quarter second. tween_callback then frees the node. No AnimationPlayer setup needed for simple effects.
Checkpoint — expected state