Update!
I have basic blocks that fall by themselves and that I can move around with left/right on the keyboard. They will collide with each other and the walls/floor, and once they stick, a new block will spawn.
Basic movement
The first thing to do is set up movement on the blocks. We’ll give them a KinematicBody2D
and a script to handle keyboard controls.
The PixelEngine there is the falling sand block that I made in the [previous post]https://blog.jverkamp.com/2020/04/17/ludum-dare-46-tetris-sand/. That just handles the UI and the falling sand, it’s an animated sprite.
Then movement via script:
# Block.gd
func _physics_process(delta):
# Keyboard controls
if Input.is_action_pressed("ui_right"):
velocity += IMPULSE
if Input.is_action_pressed("ui_left"):
velocity -= IMPULSE
# Apply friction and gravity
velocity *= DECAY
velocity.y = FALLING
# Move the block
body.move_and_collide(velocity)
And that’s it. It moves left and right and falls down.
Collisions
To collide, we need something to collide with:
And that’s actually it. No code. I didn’t think I’d like that part of Godot… but it’s really handy.
Now the blocks fall down and can crash into walls.
Respawning
Now for the slightly tricky part. I want blocks to stop moving when they collide and then spawn a new block. Stopping them is actually pretty easy:
func _physics_process(delta):
...
# Move the block
var collision = body.move_and_collide(velocity)
# If we hit something, start a counter, if that goes long enough, lock the block
if collision:
velocity = Vector2.ZERO
stuck_time += delta
if stuck_time > LOCK_TIME:
set_physics_process(false)
else:
stuck_time = 0
Just tell Godot to stop the _physics_process
calls to this object.
To respawn blocks, I’m going to use signals
:
# Block.gd
signal on_lock
func _physics_process(delta):
...
if collision:
velocity = Vector2.ZERO
stuck_time += delta
if stuck_time > LOCK_TIME:
set_physics_process(false)
emit_signal("on_lock")
else:
stuck_time = 0
# Main.gd
extends Node2D
onready var Block = preload("res://scenes/Block.tscn")
onready var blocks = $Blocks
func _on_Block_on_lock():
var new_block = Block.instance()
new_block.name = "Block" + str(blocks.get_child_count() + 1)
new_block.position = Vector2(80, 20)
new_block.connect("on_lock", self, "_on_Block_on_lock")
blocks.add_child(new_block)
print('Spawned ' + new_block.name)
And… that’s actually it. The Main
scene/script is connected via UI for the first signal, but after that, whenever a Block
fires the on_lock
signal to Main
, Main
will create a new Block
instance (with a unique name), set it to the top of the screen, and wire up the new signal.
It’s really that easy.
Godot is nice.
Up next:
- Make the blocks into tetrominos.
- Work on the falling sand simulation
- Have the sand ‘drop’ into a shared simulation when the blocks lock (using the previous signal!)
This is fun!