So, a tween or an AnimationPlayer are both valid options. I would recommend an AnimatableBody2D. Specifically because it deals with the physics issue of the player standing on the block and it moving. I did a little test project (you can get the whole thing here), and this is what I came up with to solve both your problems. For my demo I used the Kenney Platformer Pack in which the blocks are 70x70 pixels.
PlatformerSwitch2D
- I created an AnimatableBody2D as the root node. It is on Collision Layer 1 only, no mask layers.
- Added CollisionShape2D with a 70x70px rectangle.
- Added a Sprite2D to hold the block.
- Added an Area2D which is on NO collision layers. It is on Collision Mask 2 only.
- Added a CollisionShape2D to the Area2D. Moved it up 35 pixels so it’s right at the top of the block. Made it 70px wide, and only 2px tall. Just enough for the player to land on. (Colored it red in the screenshot below.)
Then I added a script to the node. It’s a bit fancier than it needs to be, but I wanted it to be immediately useable by you. You can set the block_size to whatever yours is, the move_direction (y = 1.0 is straight down one whole block), and the overall speed measured in blocks per second.
class_name PlatformerSwitch2D extends AnimatableBody2D
signal changed
## Speed in blocks the platform travels per second. Default is one block pers second.
@export var speed: float = 1.0:
set(value):
speed = value
pixel_speed = value * block_size
@export var block_size: float = 64.0
## The direction the switch moves when stepped on, measured in blocks.
## Negative is left on x-axis and up on y-axis.
## Positive is right on x-axis, and down on y-axis.
## Anything other than 1.0 or -1.0 will alter the platform distance traveled.
@export var move_direction: Vector2:
set(value):
move_direction = value
pixel_move_direction = value * block_size
var on: bool = false
@onready var pixel_speed: float = speed * block_size
@onready var pixel_move_direction: Vector2 = move_direction * block_size
@onready var start_position = global_position
@onready var target_position = start_position + pixel_move_direction
@onready var area_2d: Area2D = $Area2D
func _ready() -> void:
set_physics_process(false)
area_2d.body_entered.connect(_on_body_entered)
func _physics_process(delta: float) -> void:
global_position = global_position.move_toward(target_position, pixel_speed * delta)
if global_position == target_position:
set_process(true)
on = true
changed.emit()
func _on_body_entered(body: Node2D) -> void:
set_physics_process(true)
Then I configured the platform like so:
Door
For the door, I wanted to make sure that it could operate on any number of switches. (Note you’ll get an error if you assign no switches to it and run the game.)
It’s just a StaticBody2D with a CollisionShape2D and two Sprite2D nodes for the door itself.

Then I attached a script to it that stores an array of all the switches. When the game starts, it connects to every switch’s changed signal. (More on this later.) Then, whenever that signal fires, it queries all the doors. If they are all open, it open, if not nothing changes.
When the open_door() function runs, it changes the textures and disables the CollisionShape2D so the Player can pass through. Your door would probably do more. (More below.)
extends StaticBody2D
const DOOR_OPEN_TOP = preload("uid://dvqiytukctnky")
const DOOR_OPEN_MID = preload("uid://i6y47hb81xts")
@export var switches: Array[PlatformerSwitch2D]
@onready var top_sprite_2d: Sprite2D = $TopSprite2D
@onready var bottom_sprite_2d: Sprite2D = $BottomSprite2D
@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D
func _ready() -> void:
for switch in switches:
switch.changed.connect(_switch_changed)
func open_door() -> void:
top_sprite_2d.texture = DOOR_OPEN_TOP
bottom_sprite_2d.texture = DOOR_OPEN_MID
collision_shape_2d.disabled = true
func _switch_changed() -> void:
var open = false
for switch in switches:
if switch.on:
open = true
else:
open = false
if not open:
return
open_door()
Player
I used the default script template for CharacterBody2D Added a Sprite2D for the texture, CollisionShape2D, and a Camera2D. The player is only on Collision Layer 2 so it will trigger the switches, and has Collision Mask 1 on so it will interact with the environment and no fall through the block/pass through the door.
Final Product
Game Start
Switch One
Switch Two
Next Steps
- You can create an Area2D on Collision Mask 2 to interact with the player and transport them to a new level. If you make its CollisionShape2D slightly smaller than the door’s CollisionShape2D, the player will not be able to trigger it until the door is open.
- The switches stay down when pressed. You could alter them so that they only go all the way down if the player stands on them, otherwise they bounce back up. Then you could add another block or item the player can push onto the switch to keep them down. Put it on Collision Layer 3 and add Collision Mask 3 to the switch base script.
- Make switches that move in other directions like in towards walls.
- You could make the PlatofmerSwitch2D node into a single node by combining all subnodes into nodes created by the script. This is a much more advanced exercise, but then you can add them directly from the Add Node dialog. For an example of how to do this, see my Curved Terrain 2D plugin.