Hard dropping blocks isn't instant and doesn't put it in the 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
			
			

Why are you slowly extending the raycast downwards? Can you not just make a really long raycast, so it reaches all the way to the floor instantly? Then there’s no need for the while loop.

1 Like

The raycast extends because I’m trying to find the closest surface from the top that the block can land on.

I’ll try making a long raycast and see where that leads me; I’ll edit this post after I’ve done so.

If the raycast intersects multiple colliders, it will report on the one closest to its starting point. So if the starting point is at the top, and it points straight down, that should give you exactly what you want.

1 Like

UPDATE: Your solution has gotten me closer to what I want, but there’s still an issue: pieces can still be embedded in blocks and the floor.

I suspect this might have something to do with:
A. The origin of the blocks, and/or
B. Me not using enough raycasts (I’ll have to figure out how to make multiple raycasts depending on block width)

Here’s a video to show what I mean (the highlight polygons a holdover from one of my previous solutions btw):

(P.S. It’s kinda late where I’m from so I might not reply as often)

I mean, yeah, it looks like you’re only checking at one x-coordinate, so you’re not accounting for the case where there’s a height difference in the area where your tile will land. Also, I’d suggest putting your raycasts in the middle of the tiles, like this for example:


But yeah, I suspect you have some slightly tricky logic you’ll need to figure out for situations like this:

Where the raycasts will detect different Y positions - perhaps it would be more helpful to look at the distance from the bottom of each part of the block to the floor, and pick the smallest distance reported, rather than looking at the absolute Y coordinate.

1 Like

Holy moly man your solution works! Took a bit of finetuning and a couple rewrites of my horrible code, but it works!

Thanks!

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.