Lesson 07
Falling on its own isn't a game. Now we wire input actions the proper Godot way, let the player slide the piece left and right (blocked by walls and stacks), and add soft drop and hard drop.
Godot lets you name abstract actions and bind keys to them. This is how you support rebinding and multiple keys per action later. Set them up once:
move_left and click Add. Repeat for move_right, soft_drop, hard_drop, and rotate_cw.move_left, press the Left Arrow (and optionally A). Bind move_right to Right/D, soft_drop to Down/S, hard_drop to Space, rotate_cw to Up/W.Why actions beat raw keys
With actions you check Input.is_action_pressed("move_left") instead of a specific key. Rebinding, controller support, and touch buttons all plug into the same action names without touching game logic.
Holding left should slide the piece smoothly, not teleport once or move impossibly fast. We add a small repeat timer for horizontal moves. Update main.gd's variables and _process:
# add with the other vars in main.gd
var _move_accum: float = 0.0
const MOVE_REPEAT: float = 0.09 # seconds between held-key steps
func _process(delta: float) -> void:
if active_piece == null:
return
_handle_horizontal(delta)
_handle_soft_drop(delta)
# gravity
_fall_accum += delta
if _fall_accum >= fall_interval:
_fall_accum = 0.0
_step_down()func _handle_horizontal(delta: float) -> void:
var dir: int = 0
if Input.is_action_pressed("move_left"):
dir -= 1
if Input.is_action_pressed("move_right"):
dir += 1
if dir == 0:
_move_accum = MOVE_REPEAT # so next press moves immediately
return
_move_accum += delta
if _move_accum >= MOVE_REPEAT:
_move_accum = 0.0
if _can_move(active_piece, Vector2i(dir, 0)):
active_piece.move_by(Vector2i(dir, 0))Immediate first step
Setting _move_accum = MOVE_REPEAT when no key is held means the very next press moves the piece instantly, then repeats at the delay. Without this the first tap feels laggy.
func _handle_soft_drop(delta: float) -> void:
if Input.is_action_pressed("soft_drop"):
# Speed up gravity while held by advancing the accumulator faster.
_fall_accum += delta * 8.0Soft drop is just faster gravity
Rather than a separate movement path, soft drop simply feeds the gravity accumulator faster. Fewer code paths = fewer bugs. The piece still locks through the normal _step_down route.
Hard drop belongs in _input, not _process, because it's a one-shot event, not a held state. Add:
func _input(event: InputEvent) -> void:
if active_piece == null:
return
if event.is_action_pressed("hard_drop"):
_hard_drop()
func _hard_drop() -> void:
# Move down until blocked, then lock.
while _can_move(active_piece, Vector2i(0, 1)):
active_piece.move_by(Vector2i(0, 1))
_lock_piece()is_action_pressed vs is_action_just_pressed
In _input, use event.is_action_pressed(name) — it's already edge-triggered per event. In _process, use Input.is_action_pressed for held state and Input.is_action_just_pressed for one-shots. Mixing these up causes hard drop to fire many times or not at all.
Checkpoint — expected state
_can_move against the board.