Godot Version
4.2
Question
Hi! I implemented an inventory based on this tutorial. It was working great for awhile; however, I felt that storing my items in a JSON file wasn’t sustainable whenever I started to add more items. Using resources and classes I created an Item class (and classes that extend it for categories of items). Rather than completely rewrite the inventory from the ground up, I decided to try and adapt the inventory system. originally I was going to change the logic in the inventory itself; however, since the item nodes in my scene were already “translating” data from a JSON to something workable by the inventory I decided to translate data from the TRES. This worked perfectly for getting the texture of the item; however, it is somehow breaking the coordinate system that the item grid relies on.
While troubleshooting I tried adapting the inventory itself, but it led to about as many bugs, they didn’t lead to crashes but the logic errors they caused were more confusing. I also decided to try and adapt the Item class to store an array of arrays ( that always has a size of two) instead of an array of Vector2’s and it did work, but it made creating new items with the godot GUI obtuse. This happens to be a collaborative project, so that solution won’t do for me.
the code for the inventory is here:
extends Control
@export var inventory_size = 150
@onready var slot_scene = preload("res://UI/Inventory/inventory_slot.tscn")
@onready var grid_container = $ColorRect/MarginContainer/VBoxContainer/ScrollContainer/GridContainer
@onready var item_scene = preload("res://UI/Inventory/item.tscn")
@onready var scroll_container = $ColorRect/MarginContainer/VBoxContainer/ScrollContainer
@onready var col_count = grid_container.columns
var grid_array := []
var item_held = null
var current_slot = null
var can_place := false
var icon_anchor : Vector2
var inventory_open := false
signal inventory_opened()
signal inventory_closed()
signal report_inventory_hotbar(hotbar : Array)
# Called when the node enters the scene tree for the first time.
func _ready():
MenuHandler.change_state.connect(_on_menu_handler_change_state)
MenuHandler.main()
for i in range(inventory_size):
create_slot()
func _process(delta):
if item_held:
if Input.is_action_just_pressed("rotate item"):
rotate_item()
if Input.is_action_just_pressed("select"):
if scroll_container.get_global_rect().has_point(get_global_mouse_position()):
place_item()
else:
if Input.is_action_just_pressed(("select")):
if scroll_container.get_global_rect().has_point(get_global_mouse_position()):
pick_item()
func create_slot():
var new_slot = slot_scene.instantiate()
new_slot.slot_ID = grid_array.size()
grid_container.add_child(new_slot)
grid_array.push_back(new_slot)
new_slot.slot_entered.connect(_on_slot_mouse_entered)
new_slot.slot_exited.connect(_on_slot_mouse_exited)
pass
func _on_slot_mouse_entered(a_Slot):
#arbitrary values because they are always supposed to be changed before being used for calculations
icon_anchor = Vector2(10000,100000)
current_slot = a_Slot
if item_held:
check_slot_availability(current_slot)
set_grids.call_deferred(current_slot)
func _on_slot_mouse_exited(a_Slot):
clear_grid()
if not grid_container.get_global_rect().has_point(get_global_mouse_position()):
current_slot = null
func _on_button_spawn_pressed():
var new_item = item_scene.instantiate()
add_child(new_item)
new_item.load_item(load("res://Systems and Logic/item/repo/torch.tres")) #randomize this for different items to spawn
new_item.selected = true
item_held = new_item
func check_slot_availability(a_Slot):
for grid in item_held.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:
can_place = false
return
if grid_to_check < 0 or grid_to_check >= grid_array.size():
can_place = false
return
if grid_array[grid_to_check].state == grid_array[grid_to_check].States.TAKEN:
can_place = false
return
can_place = true
func set_grids(a_Slot):
for grid in item_held.item_grids:
var grid_to_check = a_Slot.slot_ID + grid[0] + grid[1] * col_count
if grid_to_check < 0 or grid_to_check >= grid_array.size():
continue
#make sure the check don't wrap around boarders
var line_switch_check = a_Slot.slot_ID % col_count + grid[0]
if line_switch_check <0 or line_switch_check >= col_count:
continue
if can_place:
grid_array[grid_to_check].set_color(grid_array[grid_to_check].States.FREE)
#save anchor for snapping
if grid[1] < icon_anchor.x: icon_anchor.x = grid[1]
if grid[0] < icon_anchor.y: icon_anchor.y = grid[0]
else:
grid_array[grid_to_check].set_color(grid_array[grid_to_check].States.TAKEN)
func clear_grid():
for grid in grid_array:
grid.set_color(grid.States.DEFAULT)
func rotate_item():
item_held.rotate_item()
clear_grid()
if current_slot:
_on_slot_mouse_entered(current_slot)
func place_item():
if not can_place or not current_slot:
return #put indication of placement failed, sound or visual here
#for changing scene tree
item_held.get_parent().remove_child(item_held)
grid_container.add_child(item_held)
item_held.global_position = get_global_mouse_position()
####
var calculated_grid_id = current_slot.slot_ID + icon_anchor.x * col_count + icon_anchor.y
item_held._snap_to(grid_array[calculated_grid_id].global_position)
item_held.grid_anchor = current_slot
for grid in item_held.item_grids:
var grid_to_check = current_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 = item_held
#put item into a data storage here
item_held = null
clear_grid()
func pick_item():
if not current_slot or not current_slot.item_stored:
return
item_held = current_slot.item_stored
item_held.selected = true
#move node in the scene tree
item_held.get_parent().remove_child(item_held)
add_child(item_held)
item_held.global_position = get_global_mouse_position()
####
for grid in item_held.item_grids:
var grid_to_check = item_held.grid_anchor.slot_ID + grid[0] + grid[1] * col_count # use grid anchor instead of current slot to prevent bug
grid_array[grid_to_check].state = grid_array[grid_to_check].States.FREE
grid_array[grid_to_check].item_stored = null
check_slot_availability(current_slot)
set_grids.call_deferred(current_slot)
func _on_add_slot_pressed():
create_slot()
func _on_menu_handler_change_state(MenuState: Variant) -> void:
if MenuState == MenuHandler.MenuStates.INVENTORY:
set_visible(true)
set_mouse_filter(Control.MOUSE_FILTER_STOP)
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
else:
set_visible(false)
set_mouse_filter(Control.MOUSE_FILTER_IGNORE)
the referenced slot code can be found here:
extends TextureRect
signal slot_entered(slot)
signal slot_exited(slot)
@onready var filter = $StatusFilter
var slot_ID
var is_hovering := false
enum States {DEFAULT, TAKEN, FREE}
var state = States.DEFAULT
var item_stored = null
func _process(delta: float) -> void:
if get_global_rect().has_point(get_global_mouse_position()):
if not is_hovering:
is_hovering = true
emit_signal("slot_entered", self )
else:
if is_hovering:
is_hovering = false
emit_signal("slot_exited", self)
func set_color(a_state = States.DEFAULT) -> void:
match a_state:
States.DEFAULT:
filter.color = Color(Color.WHITE, 0.0)
States.TAKEN:
filter.color = Color(Color.RED, 0.1)
States.FREE:
filter.color = Color(Color.GREEN, 0.1)
and here’s the original item code:
extends Node2D
@onready var IconRect_path = $Icon
var item_ID : int
var item_grids := []
var selected = false
var grid_anchor = null
# Called when the node enters the scene tree for the first time.
func _ready():
pass
# 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)
func load_item(a_ItemID : int) -> void:
var Icon_path = "res://Assets/" + DataHandler.item_data[str(a_ItemID)]["Name"] + ".png"
IconRect_path.texture = load(Icon_path)
for grid in DataHandler.item_grid_data[str(a_ItemID)]:
var converter_array := []
for i in grid :
converter_array.push_back(int(i))
item_grids.push_back(converter_array)
#print(item_grids)
#rotate 90 degress CW
func rotate_item():
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):
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
and here is the function that I tried to change in adapting the code:
func load_item(a_Item : ITEM) -> void:
IconRect_path.texture = a_Item.ITEM_TEXTURE_GRID
$Area2D/CollisionShape2D.scale = Vector2(IconRect_path.texture.get_width(), IconRect_path.texture.get_height())
for grid in a_Item.ITEM_GRID:
var converter_array := []
converter_array.append(int(grid.y))
converter_array.append(int(grid.x))
item_grids.push_back(converter_array)
#print(item_grids)
I also made an ever so slight change to the inventory code, just to load in a tres instead of loading from the json at the _on_button_spawn_pressed() function here:
func _on_button_spawn_pressed():
var new_item = item_scene.instantiate()
add_child(new_item)
new_item.load_item(load("res://Systems and Logic/item/repo/torch.tres")) #randomize this for different items to spawn
new_item.selected = true
item_held = new_item
The error is: “Invalid access of index ‘200047’ on a base object of type: ‘Array’” at line 123 of the inventory.
Due to my own troubleshooting, I have come to realize that this is due to the icon_anchor value not being reset in the set_grids
function, as it should be. Considering that the function itself is unmodified the only conclusion that I can come to is that grid[1] (essentially the x value) and grid[0] (essentially the y value) are being set improperly meaning the if statements at line 97 and 98 aren’t triggered. I genuinely have no idea how to fix this, I don’t see how from a perspective of raw data I am passing in something different.
a video of how the system is supposed to function can be found in the video above, but here’s a video of the issue:
as you can see the slot doesn’t have its usual green color rect to indicate that something could be placed there.
I know this is a really long issue, but any help would be much appreciated!