Weird bug when trying to snap items to tetris style inventory grid

Godot Version

Godot 4.2.1 Stable

Question

Hi, I’ve been building a tetris-based inventory system from the following tutorial:

And I’ve run into trouble when it comes to “picking up” items, and placing them in the next nearest slot in the inventory. So far, the data for the slots is being set perfectly fine, however, I’m unable to get different sized items to snap onto the grid correctly. Here is an example video:


As you can see, the L shaped items line up correctly, but then when the line shaped items come in, they snap 1 grid block to the left of where they should be. This is even weirder when we see what happens when I spawn the line shaped objects first:

They fit perfectly.

Another thing to note is that objects can be picked up and placed perfectly fine one they’re in the inventory, it’s just the initial placing into the nearest possible slot seems to not be working, evidence here:

Here is the most relevant code to the picking up objects system:

Firstly, all in-game items have a unique scene that represents the object in the game world the player will interact with to “pick up” the object. This object contains all of the variables the item needs. Here is an example:

extends Node2D

@onready var inventory = $"../Inventory"

var item_ID = 1
var item_grids := [[0, 0], [0, 1], [-1, 0]]
var item_icon = preload("res://Assets/Item1.png")
var icon_size = Vector2(100,100)
var selected = false
var grid_anchor = null


func _on_area_2d_mouse_entered():
	inventory.set_pickup_item(item_ID,item_grids.duplicate(true),item_icon,icon_size)

This then makes the inventory scene run the set_pickup_item function:

func set_pickup_item(item_ID, item_grids, item_icon,icon_size):
	pickup_item = item_scene.instantiate()
	pickup_item.item_ID = item_ID
	pickup_item.item_icon = item_icon
	pickup_item.item_grids = item_grids
	pickup_item.icon_size = icon_size
	item_held = pickup_item
	_pickup_item()

For reference, the item_scene I am instantiating is the generic scene that represents items within the inventory (the shape that will fill the tetris-style inventory). Here is the code for it:

extends Node2D

@onready var IconRect_path = $Icon

var item_ID = null
var item_grids := []
var item_icon = null
var icon_size = null
var selected = false
var grid_anchor = null


# Called when the node enters the scene tree for the first time.
func _ready():
	IconRect_path.texture = item_icon
	IconRect_path.size = icon_size
	IconRect_path.position = -(icon_size/2)

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	if selected:
		global_position = lerp(global_position, get_global_mouse_position(), 25 * delta)

#rotate 90 degress CW
func rotate_item():
	if selected == true:
		for grid in item_grids:
			var temp_y = grid[0]
			grid[0] = -grid[1]
			grid[1] = temp_y
		rotation_degrees += 90
		if rotation_degrees>=360:
			rotation_degrees = 0

func _snap_to(destination):
	print(str(IconRect_path.size))
	var tween = get_tree().create_tween()
	#separate cases to avoid snapping errors
	if int(rotation_degrees) % 180 == 0:
		destination += IconRect_path.size/2
	else:
		var temp_xy_switch = Vector2(IconRect_path.size.y,IconRect_path.size.x)
		destination += temp_xy_switch/2
	tween.tween_property(self, "global_position", destination, 0.15).set_trans(Tween.TRANS_SINE)
	selected = false

The inventory will run the _pickup_item() function after it has finished it’s previous function. This looks as follows:

func _pickup_item():
	for i in grid_array.size():
		if check_slot_availability_pickup(grid_container.get_child(i)) == true:
			place_item_pickup(grid_container.get_child(i))
			return
		else:
			if i == grid_array.size() - 1 and item_held.rotation_degrees >= 270:
				print("No Space in Inventory")
				item_held = null
				pickup_item = null
				held_ID = null
				held_icon = null
				held_grid = null
			elif i == grid_array.size() - 1 and item_held.rotation_degrees < 270:
				item_held.rotate_item()
				print(item_held.rotation)
				_pickup_item()
			else:
				continue

Here is the function that checks for slot availability (check_slot_availability_pickup):

func check_slot_availability_pickup(a_Slot):
	for grid in pickup_item.item_grids:
		var grid_to_check = a_Slot.slot_ID + grid[0] + grid[1] * col_count
		var line_switch_check = a_Slot.slot_ID % col_count + grid[0]
		if line_switch_check < 0 or line_switch_check >= col_count:
			return false
		if grid_to_check < 0 or grid_to_check >= grid_array.size():
			return false
		if grid_array[grid_to_check].state == grid_array[grid_to_check].States.TAKEN:
			return false
	return true

And here is the code that finally puts the item into the grid:

func place_item_pickup(a_Slot):
	var calculated_grid_id = a_Slot.slot_ID + icon_anchor.x * col_count + icon_anchor.y
	grid_container.add_child(pickup_item)
	pickup_item.grid_anchor = a_Slot
	for grid in pickup_item.item_grids:
		var grid_to_check = a_Slot.slot_ID + grid[0] + grid[1] * col_count
		grid_array[grid_to_check].state = grid_array[grid_to_check].States.TAKEN 
		grid_array[grid_to_check].item_stored = pickup_item
		prints("Placing item in", grid_array[grid_to_check].slot_ID)
		grid_array[grid_to_check].stored_item_ID = pickup_item.item_ID
		if grid[1] < icon_anchor.x: icon_anchor.x = grid[1]
		if grid[0] < icon_anchor.y: icon_anchor.y = grid[0]
	calculated_grid_id = a_Slot.slot_ID + icon_anchor.x * col_count + icon_anchor.y
	pickup_item._snap_to(grid_array[calculated_grid_id].global_position)
	item_held = null
	clear_grid()

I apologies for the giant wall of code, I’ve been trying to figure this out for days now, and it seems like nothing I do works. I’m hoping someone can help me figure this out. Your assistance would be greatly appreciated. Thank you.

Can you send me the project? I tried downloading the project from the tutorial but it doesn’t seem like they have the functionality to automatically insert items into the grid. I can try recreating your project, but it would be easier to just send me the code.

Found your issue. The original code has an icon_anchor variable to describe where the top left corner of the block was. It’s set to Vector2(10000, 10000) when the mouse enters a slot, but you didn’t set it back to that value when you auto-place an item. Modify your code like this:

func place_item_pickup(a_Slot):
	icon_anchor = Vector2(10000,100000)

It should be fixed after that.