• Godot Help
  • [SOLVED] Hard dropping blocks isn’t instant and doesn't put in correct spot

Godot Version

v4.3.stable.official [77dcf97d8]

Question

I'm trying to add the ability for blocks (frozen RigidBody2Ds) to be hard-dropped instantly (they get placed on the closest surface directly below them) But I’m having trouble with it. Many of the methods I have tried to accomplish hard-dropping (specifically surface detection) are very slow (they have a noticeable delay). Even after a surface is detected, the block might sometimes end up being placed partially in the floor, not directly on top of it.
Can anyone help me figure out what I’m doing wrong or link me to an answer for reference? I’ve looked around but couldn’t find anything.
Here are some methods I've tried:

  • Using raycasts (either a noticeable delay or it goes too fast for the raycast to check for collisions)
  • Using Area2Ds (same issue as raycasts)
  • Unfreezing the blocks, applying a high gravity scale, and freezing them shortly afterward

=====

  • Here’s my code:
    extends RigidBody2D
    
    # for i & for j loops start at top-right, go up, go back down and move left, and repeat for the whole square.
    
    @onready var tiles: TileMapLayer = $TileMapLayer
    @onready var tile_start: Marker2D = $Marker2D
    @onready var collision_polygon: CollisionPolygon2D = $CollisionPolygon2D
    
    @onready var ray_cast_floor: RayCast2D = $RayCastFloor
    @onready var detect_area: Area2D = $DetectArea
    @onready var detect_polygon: CollisionPolygon2D = $DetectArea/DetectPolygon
    
    @export var piece_type = -1 as int
    
    var detect_moving = true
    var movable = true
    var drop_y = null
    
    
    var rng = RandomNumberGenerator.new()
    var piece_data = [
    	"NULL/NULL/NULL/NULL/(2, 3)/(2, 2)/(2, 2)/(2, 1)/NULL/NULL/NULL/NULL/NULL/NULL/NULL/NULL/=(16, -64)/(16, 64)/(-16, 64)/(-16, -64)=(16, 0)=(0, 16)", # I, 0
    	"NULL/NULL/NULL/NULL/NULL/(6, 2)/(6, 1)/NULL/NULL/(5, 2)/(5, 1)/NULL/NULL/NULL/NULL/NULL/=(32, -32)/(32, 32)/(-32, 32)/(-32, -32)=(0, 0)=(0, 16)", # O, 1
    	"NULL/(4, 0)/NULL/NULL/(5, 0)/(3, 0)/NULL/NULL/NULL/(2, 0)/NULL/NULL/NULL/NULL/NULL/NULL/=(-48, -32)/(48, -32)/(48, 0)/(16, 0)/(16, 32)/(-16, 32)/(-16, 0)/(-48, 0)=(16, 32)=(0, 16)", # T, 2
    	"NULL/NULL/NULL/NULL/NULL/(8, 0)/(9, 1)/(9, 0)/NULL/(6, 0)/NULL/NULL/NULL/NULL/NULL/NULL/=(32, -32)/(32, 64)/(-32, 64)/(-32, 32)/(0, 32)/(0, -32)=(0, -32)=(0, 16)", # J, 3
    	"NULL/NULL/NULL/NULL/NULL/(8, 0)/NULL/NULL/NULL/(6, 0)/(9, 1)/(9, 0)/NULL/NULL/NULL/NULL/=(32, 32)/(32, 64)/(-32, 64)/(-32, -32)/(0, -32)/(0, 32)=(0, -32)=(0, 16)", # L, 4
    	"NULL/NULL/NULL/NULL/NULL/NULL/(1, 0)/NULL/NULL/(1, 0)/(1, 0)/NULL/NULL/(1, 0)/NULL/NULL/=(32, -32)/(32, 0)/(0, 0)/(0, 32)/(-64, 32)/(-64, 0)/(-32, 0)/(-32, -32)=(0, 0)=(0, 16)", # S, 5
    	"NULL/NULL/NULL/NULL/NULL/(1, 0)/NULL/NULL/NULL/(1, 0)/(1, 0)/NULL/NULL/NULL/(1, 0)/NULL/=(32, 0)/(32, 32)/(-32, 32)/(-32, 0)/(-64, 0)/(-64, -32)/(0, -32)/(0, 0)=(0, 0)=(0, 16)", # Z, 6
    	"NULL/(4, 0)/NULL/NULL/NULL/(3, 0)/NULL/NULL/NULL/(3, 0)/NULL/NULL/NULL/(2, 0)/(1, 0)/NULL/=(-32, -32)/(-32, 0)/(64, 0)/(64, 32)/(-64, 32)/(-64, -32)=(0, 0)=(0, 16)", # Long sideways J, 7
    	"NULL/NULL/NULL/NULL/NULL/(4, 1)/NULL/NULL/NULL/(3, 1)/NULL/NULL/NULL/NULL/NULL/NULL/=(-32, -16)/(32, -16)/(32, 16)/(-32, 16)=(0, 16)=(0, 16)", # Small horizontal 2-long, 8
    	"NULL/NULL/NULL/NULL/(4, 0)/NULL/NULL/NULL/(3, 0)/(1, 1)/NULL/NULL/(2, 0)/(0, 1)/NULL/NULL/=(-48, -32)/(16, -32)/(16, 0)/(48, 0)/(48, 32)/(-48, 32)/(-48, 0)=(-16, 32)=(0, 16)", # O with a block right of bottom-right, 9
    ]
    
    # Called when the node enters the scene tree for the first time.
    func _ready() -> void:
    	#freeze_mode = 1
    	
    	tiles.tile_set.setup_local_to_scene()
    	
    	var picked_piece
    	if piece_type < 0:
    		picked_piece = piece_data.pick_random()
    	else:
    		picked_piece = piece_data[piece_type]
    	#print(picked_piece)
    	
    	
    	var piece_tile_string = picked_piece.get_slice("=", 0)
    	#print(piece_tile_string)
    	
    	var piece_tile_array = piece_tile_string.split("/", true)
    	
    	var piece_tile_array_vec2 = []
    	piece_tile_array_vec2.resize(piece_tile_array.size())
    	for entry in piece_tile_array.size():
    		var coord_string = piece_tile_array[entry]
    		if coord_string == "":
    			piece_tile_array_vec2.remove_at(entry)
    		elif coord_string == "NULL":
    			piece_tile_array_vec2[entry] = Vector2(-1, -1)
    		else:
    			coord_string = coord_string.replace("(", "")
    			coord_string = coord_string.replace(")", "")
    			coord_string = coord_string.replace(" ", "")
    			
    			var coord_vec2 = Vector2(float(coord_string.get_slice(",", 0)), float(coord_string.get_slice(",", 1)))
    			piece_tile_array_vec2[entry] = coord_vec2
    	
    	#print("tile array vector2")
    	#print(piece_tile_array_vec2)
    	
    	var piece_col_string = picked_piece.get_slice("=", 1)
    	#print(piece_col_string)
    	
    	var piece_col_array = piece_col_string.split("/", true)
    	#print(piece_col_array)
    	
    	var piece_col_array_vec2 = []
    	piece_col_array_vec2.resize(piece_col_array.size())
    	for entry in piece_col_array.size():
    		var coord_string = piece_col_array[entry]
    		if coord_string == "":
    			piece_col_array.remove_at(entry)
    		elif coord_string == "NULL":
    			piece_col_array_vec2[entry] = Vector2(-1, -1)
    		else:
    			coord_string = coord_string.replace("(", "")
    			coord_string = coord_string.replace(")", "")
    			coord_string = coord_string.replace(" ", "")
    			#print(coord_string)
    			
    			var coord_vec2 = Vector2(float(coord_string.get_slice(",", 0)), float(coord_string.get_slice(",", 1)))
    			#print(coord_vec2)
    			#print("  ")
    			piece_col_array_vec2[entry] = coord_vec2
    
    	#print("coord array vector2")
    	#print(piece_col_array_vec2)
    	
    	var tile_select = 0
    	for i in 4:
    		for j in 4:
    			
    			#print(str(i * 32) + ", " + str(j * 32))
    			var tile_used = 1 #rng.randi_range(0, 1) # 0 == Y, 1 == N
    			var tile_pos = tiles.local_to_map(Vector2(tile_start.position.x - (32 * i), tile_start.position.y - (32 * j)))
    			
    			if piece_tile_array_vec2[tile_select] != Vector2(-1, -1):
    				tiles.set_cell(tile_pos, 1, piece_tile_array_vec2[tile_select])
    				#var adjacent_array = []
    				#if tiles.get_cell_tile_data(Vector2i(tile_pos.x + 32, tile_pos.y)) == null:
    					#tiles.erase_cell(tile_pos)
    			else:
    				tiles.erase_cell(tile_pos)
    			
    			tile_select += 1
    	
    	collision_polygon.set_polygon(piece_col_array_vec2)
    	detect_polygon.set_polygon(piece_col_array_vec2)
    	
    	var col_position_string = picked_piece.get_slice("=", 2)
    	col_position_string = col_position_string.replace("(", "")
    	col_position_string = col_position_string.replace(")", "")
    	col_position_string = col_position_string.replace(" ", "")
    	collision_polygon.position = Vector2(float(col_position_string.get_slice(",", 0)), float(col_position_string.get_slice(",", 1)))
    	print(collision_polygon.position)
    	
    	#var ray_cast_string = picked_piece.get_slice("=", 3)
    	#ray_cast_string = ray_cast_string.replace("(", "")
    	#ray_cast_string = ray_cast_string.replace(")", "")
    	#ray_cast_string = ray_cast_string.replace(" ", "")
    	#ray_cast_floor.position = Vector2(float(ray_cast_string.get_slice(",", 0)), float(ray_cast_string.get_slice(",", 1)))
    	#print(ray_cast_floor.position)
    	
    	global_rotation_degrees = rng.randi_range(0, 3) * 90
    	ray_cast_floor.global_rotation_degrees = 0
    	
    	detect_area.global_rotation_degrees = 0
    	detect_polygon.global_rotation_degrees = global_rotation_degrees
    	
    	ray_cast_floor.position = Vector2(0, 0)
    	
    	#extend_raycast()
    	check_collisions()
    
    func extend_raycast():
    	ray_cast_floor.position.y -= 32
    	while not ray_cast_floor.is_colliding():
    		ray_cast_floor.target_position.y += 32
    		await get_tree().create_timer(0.2).timeout
    
    func check_collisions():
    	drop_y = null
    	detect_area.global_position = collision_polygon.global_position
    	if GameManager.game_phase == "calm" and movable == true:
    		detect_area.global_position.y = 1
    		await get_tree().create_timer(0.05).timeout
    		while not detect_area.has_overlapping_bodies():
    			detect_area.global_position.y += 32
    			await get_tree().create_timer(0.05).timeout
    		#ray_cast_floor.target_position.y = 33
    		#while not ray_cast_floor.is_colliding():
    			#ray_cast_floor.target_position.y += 32
    			#detect_area.global_position.y += 32
    			#await get_tree().create_timer(0.05).timeout
    	#	ray_cast_floor.target_position.y -= 1
    		drop_y = ray_cast_floor.get_collision_point().y
    		print(drop_y)
    
    		
    
    # Called every frame. 'delta' is the elapsed time since the previous frame.
    func _physics_process(delta: float) -> void:
    	if GameManager.game_phase == "calm" and movable == true:
    		freeze = true
    		if Input.is_action_just_pressed("move_left"):
    			global_position.x -= 32
    			
    			check_collisions()
    		elif Input.is_action_just_pressed("move_right"):
    			global_position.x += 32
    			check_collisions()
    		elif Input.is_action_just_pressed("move_down"):
    			print("GO DOWN")
    			movable = false
    			#freeze_mode = 1
    			gravity_scale = 300
    			freeze = false
    			await get_tree().create_timer(0.05).timeout
    			freeze = true
    			gravity_scale = 1
  • Here's my block scene dock:
  • Here’s a short video I uploaded of my running game:

    (This video uses the gravity scale method)

    DragonAero By "instant" you mean actually instant, like in one frame, or just very fast? Because raycasting can do it instantly while strong gravity can only do it very fast.

      xyz I'm trying to get it done instantly. Also yeah strong gravity was some sort of crazy last-ditch effort (the bug was driving me insane) and there are far better solutions (i.e. shapecasts, which i'm looking into as i type this)

      • xyz replied to this.

        DragonAero You don't need shapecasts if you only deal with axis aligned boxes. Raycasts should suffice. But if you can't get raycasts to work, you'll have the same problems with shapecasts. They are conceptually pretty much the same thing.

        DragonAero changed the title to [SOLVED] Hard dropping blocks isn’t instant and doesn't put in correct spot .