Godot Version
4.2.2
Question
I’ve followed this video https://youtu.be/V79YabQZC1s?si=MWnAHYEllFRONo2_ and I’ve been able to tweak it to what I need but I just can not get it to remove an item. Even calling out the items specifically like ‘press E to remove 1 apple’, I can’t get it to work. Has anyone used this video as a reference and knows how to remove an item?
wchc
January 11, 2025, 10:45am
2
Show your code here using the preformatted text ```, and screenshots of your scene structure.
1 Like
inventory_interface script:
extends Control
signal drop_slot_data(slot_data: SlotData)
signal force_close
var grabbed_slot_data: SlotData
var external_inventory_owner
@onready var player_inventory: PanelContainer = $PlayerInventory
@onready var grabbed_slot: PanelContainer = $GrabbedSlot
@onready var external_inventory: PanelContainer = $ExternalInventory
@onready var equip_inventory: PanelContainer = $EquipInventory
@onready var bank_inventory: PanelContainer = $BankInventory
@onready var hotbar_inventory: PanelContainer = $HotBarInventory
func _physics_process(delta: float) -> void:
if grabbed_slot.visible:
grabbed_slot.global_position = get_global_mouse_position() + Vector2(2, 2)
if external_inventory_owner \
and external_inventory_owner.global_position.distance_to(Global.get_global_position()) > 40:
force_close.emit()
func set_player_inventory_data(inventory_data: InventoryData) -> void:
inventory_data.inventory_interact.connect(on_inventory_interact)
player_inventory.set_inventory_data(inventory_data)
func set_bank_inventory_data(inventory_data: InventoryData) -> void:
inventory_data.inventory_interact.connect(on_inventory_interact)
bank_inventory.set_inventory_data(inventory_data)
func set_equip_inventory_data(inventory_data: InventoryData) -> void:
inventory_data.inventory_interact.connect(on_inventory_interact)
equip_inventory.set_inventory_data(inventory_data)
func set_hotbar_inventory_data(inventory_data: InventoryData) -> void:
inventory_data.inventory_interact.connect(on_inventory_interact)
hotbar_inventory.set_inventory_data(inventory_data)
func clear_external_inventory() -> void:
if external_inventory_owner:
var inventory_data = external_inventory_owner.inventory_data
inventory_data.inventory_interact.disconnect(on_inventory_interact)
external_inventory.clear_inventory_data(inventory_data)
external_inventory.hide()
external_inventory_owner = null
func on_inventory_interact(inventory_data: InventoryData, index: int, button: int) -> void:
match [grabbed_slot_data, button]:
[null, MOUSE_BUTTON_LEFT]:
grabbed_slot_data = inventory_data.grab_slot_data(index)
[_, MOUSE_BUTTON_LEFT]:
grabbed_slot_data = inventory_data.drop_slot_data(grabbed_slot_data, index)
[null, MOUSE_BUTTON_RIGHT]:
inventory_data.use_slot_data(index)
[_, MOUSE_BUTTON_RIGHT]:
grabbed_slot_data = inventory_data.drop_single_slot_data(grabbed_slot_data, index)
update_grabbed_slot()
func update_grabbed_slot() -> void:
if grabbed_slot_data:
grabbed_slot.show()
grabbed_slot.set_slot_data(grabbed_slot_data)
else:
grabbed_slot.hide()
func _on_visibility_changed():
if not visible and grabbed_slot_data:
drop_slot_data.emit(grabbed_slot_data)
grabbed_slot_data = null
update_grabbed_slot()
inventory_data script:
extends Resource
class_name InventoryData
signal inventory_updated(inventory_data: InventoryData)
signal inventory_interact(inventory_data: InventoryData, index: int, button: int)
@export var slot_datas: Array[SlotData]
func grab_slot_data(index: int) -> SlotData:
var slot_data = slot_datas[index]
if slot_data:
slot_datas[index] = null
inventory_updated.emit(self)
Global.item_grabbed = true
return slot_data
else:
return null
func drop_slot_data(grabbed_slot_data: SlotData, index: int) -> SlotData:
var slot_data = slot_datas[index]
var return_slot_data: SlotData
if slot_data and slot_data.can_fully_merge_with(grabbed_slot_data):
slot_data.fully_merge_with(grabbed_slot_data)
else:
slot_datas[index] = grabbed_slot_data
return_slot_data = slot_data
inventory_updated.emit(self)
Global.item_grabbed = false
return return_slot_data
func drop_single_slot_data(grabbed_slot_data: SlotData, index: int) -> SlotData:
var slot_data = slot_datas[index]
if not slot_data:
slot_datas[index] = grabbed_slot_data.create_single_slot_data()
elif slot_data.can_merge_with(grabbed_slot_data):
slot_data.fully_merge_with(grabbed_slot_data.create_single_slot_data())
inventory_updated.emit(self)
if grabbed_slot_data.quantity > 0:
return grabbed_slot_data
else:
Global.item_grabbed = false
return null
func use_slot_data(index: int) -> void:
var slot_data = slot_datas[index]
if not slot_data:
return
if slot_data.item_data is ItemDataConsumable:
slot_data.quantity -= 1
if slot_data.quantity < 1:
slot_datas[index] = null
inventory_updated.emit(self)
Global.use_slot_data(slot_data)
func pick_up_slot_data(slot_data: SlotData) -> bool:
for index in slot_datas.size():
if slot_datas[index] and slot_datas[index].can_fully_merge_with(slot_data):
slot_datas[index].fully_merge_with(slot_data)
inventory_updated.emit(self)
return true
for index in slot_datas.size():
if not slot_datas[index]:
slot_datas[index] = SlotData.new()
var picked_up_slot_data = slot_datas[index]
picked_up_slot_data.item_data = slot_data.item_data
picked_up_slot_data.quantity = slot_data.quantity
inventory_updated.emit(self)
return true
return false
func on_slot_clicked(index: int, button: int) -> void:
inventory_interact.emit(self, index, button)
inventory script:
extends PanelContainer
const Slot = preload("res://Inventory/slot.tscn")
@onready var item_grid: GridContainer = $MarginContainer/ItemGrid
func set_inventory_data(inventory_data: InventoryData) -> void:
inventory_data.inventory_updated.connect(populate_item_grid)
populate_item_grid(inventory_data)
func clear_inventory_data(inventory_data: InventoryData) -> void:
inventory_data.inventory_updated.disconnect(populate_item_grid)
func populate_item_grid(inventory_data: InventoryData) -> void:
for child in item_grid.get_children():
child.queue_free()
for slot_data in inventory_data.slot_datas:
var slot = Slot.instantiate()
item_grid.add_child(slot)
slot.slot_clicked.connect(inventory_data.on_slot_clicked)
if slot_data:
slot.set_slot_data(slot_data)
item_data script:
extends Resource
class_name ItemData
@export var name: String = ""
@export_multiline var description: String = ""
@export var stackable: bool = false
@export var texture = Texture2D
func use(target) -> void:
pass
player script:
extends CharacterBody2D
class_name Player
const SPEED = 100.0
const PickUp = preload("res://Inventory/Item/pick_up_ore_iron.tscn")
@export var inventory_data: InventoryData
@export var equip_inventory_data: InventoryDataEquip
@export var bank_inventory_data: InventoryDataBank
@export var hotbar_inventory_data: InventoryDataHotBar
signal toggle_inventory()
@onready var player: CharacterBody2D = $"."
@onready var inventory_interface: Control = $InventoryUI/InventoryInterface
@onready var hot_bar_inventory: PanelContainer = $InventoryUI/InventoryInterface/HotBarInventory
@onready var player_interface: PanelContainer = $InventoryUI/InventoryInterface/PlayerInventory
@onready var equip_interface: PanelContainer = $InventoryUI/InventoryInterface/EquipInventory
@onready var bank_interface: PanelContainer = $InventoryUI/InventoryInterface/BankInventory
func _ready() -> void:
Global.player = self
player.toggle_inventory.connect(toggle_inventory_interface)
inventory_interface.set_player_inventory_data(player.inventory_data)
inventory_interface.set_equip_inventory_data(player.equip_inventory_data)
inventory_interface.set_bank_inventory_data(player.bank_inventory_data)
inventory_interface.force_close.connect(toggle_inventory_interface)
inventory_interface.set_hotbar_inventory_data(player.hotbar_inventory_data)
for node in get_tree().get_nodes_in_group("external_inventory"):
node.toggle_inventory.connect(toggle_inventory_interface)
func toggle_inventory_interface(external_inventory_owner = null) -> void:
player_interface.visible = not player_interface.visible
equip_interface.visible = not equip_interface.visible
if external_inventory_owner and inventory_interface.visible:
inventory_interface.set_external_inventory(external_inventory_owner)
else:
inventory_interface.clear_external_inventory()
func _unhandled_input(event: InputEvent) -> void:
if Input.is_action_just_pressed("ui_cancel"):
get_tree().quit()
if Input.is_action_just_pressed("inventory"):
if Global.item_grabbed == false:
toggle_inventory.emit()
if Global.can_bank == true and Global.bank_open == false and Input.is_action_just_pressed("interact"):
$InventoryUI/InventoryInterface/BankInventory.show()
$InventoryUI/InventoryInterface/PlayerInventory.show()
$InventoryUI/InventoryInterface/EquipInventory.show()
Global.bank_open = true
elif Global.bank_open == true and Input.is_action_just_pressed("interact"):
$InventoryUI/InventoryInterface/BankInventory.hide()
Global.bank_open = false
func heal(heal_value: int) -> void:
if heal_value > Global.level_health or heal_value > Global.level_health - Global.health_current:
Global.health_current = Global.level_health
else:
Global.health_current += heal_value
func _on_inventory_interface_drop_slot_data(slot_data) -> void:
var pick_up = PickUp.instantiate()
pick_up.slot_data = slot_data
pick_up.position = Global.player.global_position
add_child(pick_up)
Player scene that has the inventory nodes:
It feels like a lot to paste sorry. I have a static body that’s on a timer and when that timer ends I’m looking to add whatever item is being used. Something like below that’s in the world scene along with the player.
func _on_smelt_timer_timeout():
remove_item(iron_ore)
I already detect the player in the forge using this
func _on_can_forge_area_body_entered(body):
if body is Player:
player = body
I have a way to add items but its absolutely terrible but it works and I would at least like to get some way to remove items working and then I can continue. I’ve been stuck on removing items for a while and any help would be appreciated thanks.
wchc
January 12, 2025, 3:06pm
5
I don’t see the remove_item()
function anywhere in your code. You mentioned that you tried doing it, but it doesn’t work - please share what you tried and we can debug it together.
Yea that was more, ‘can you help me make it like that’ request. What connections would I need in my forge scene in order to remove an item (in this case iron ore). Below is how I would think it could be done using an ItemData resource called ore_iron.
this would be in the forge scene
extends StaticBody2D
@export var ore_iron: ItemData(? Not sure which resource to pull from)
var player = null
func _on_can_forge_area_body_entered(body):
if body is Player:
player = body
func _on_forge_timer_iron_timeout():
player.remove(ore_iron)
While the player would have the inventory nodes and something like the below.
@export var inventory_data: InventoryData
func remove(item):
inventory_data.remove(item)
wchc
January 12, 2025, 3:45pm
7
Gotcha. Can you also share the code of the SlotData
class? Seems to be important in the code how it handle merges and what not.
slot_data script:
extends Resource
class_name SlotData
const MAX_STACK_SIZE: int = 99
@export var item_data: ItemData
@export_range(1, MAX_STACK_SIZE) var quantity: int = 1: set = set_quantity
func can_merge_with(other_slot_data: SlotData) -> bool:
return item_data == other_slot_data.item_data \
and item_data.stackable \
and quantity < MAX_STACK_SIZE
func can_fully_merge_with(other_slot_data: SlotData) -> bool:
return item_data == other_slot_data.item_data \
and item_data.stackable \
and quantity + other_slot_data.quantity <= MAX_STACK_SIZE
func fully_merge_with(other_slot_data: SlotData) -> void:
quantity += other_slot_data.quantity
func create_single_slot_data() -> SlotData:
var new_slot_data = duplicate()
new_slot_data.quantity = 1
quantity -= 1
return new_slot_data
func set_quantity(value: int) -> void:
quantity = value
if quantity > 1 and not item_data.stackable:
quantity = 1
push_error("%s is not stackable, setting quantity to 1" % item_data.name)
wchc
January 12, 2025, 4:07pm
9
I think something like that should work, this goes to your InventoryData
class script.
func remove_item(item_data: ItemData, quantity: int) -> bool:
var counter: int = -1
for slot_data in slot_datas:
counter += 1
if slot_data == null:
continue
elif slot_data.item_data != item_data:
continue
elif slot_data.quantity < quantity:
continue
elif slot_data.quantity == quantity:
slot_datas[counter] = null
print("SlotData removed")
return true
else:
slot_data.quantity -= quantity
print("SlotData quantity updated")
return true
print("No item of this type found")
return false
Not sure though if you are allowed to remove the SlotData
from the slot_datas
array, so in case anything breaks with this code - let me know.
Getting an error, was it a bad paste?
wchc
January 12, 2025, 4:13pm
11
Added 2 lines at the end of my code in the previous message, try with these.
Error is gone. Would I still call it like in the example I sent? Call player.remove(item) in the forge and this in player? In short, where would I need to call this new remove_item function.
I was able to at least get past that last error I sent but getting one here now. Is this what you was worried about?
I changed the player function to look like this, in case it’s wrong as well
Ah, so it’s only looking at the first slot? I moved an iron ore to the first slot and it printed ‘SlotData removed’ but the next attempt threw that error again because the slot was now empty.
wchc
January 12, 2025, 4:38pm
15
I need to go out for a couple hours now, I’ll get back to it later when I return.
No worries! Thanks for the help so far. If we can get it looking over the whole inventory that should be enough for me to start looking at it on my own again.
wchc
January 12, 2025, 10:54pm
17
I updated the code again in this reply.
I think something like that should work, this goes to your InventoryData class script.
func remove_item(item_data: ItemData, quantity: int) -> bool:
var counter: int = -1
for slot_data in slot_datas:
counter += 1
if slot_data == null:
continue
elif slot_data.item_data != item_data:
continue
elif slot_data.quantity < quantity:
continue
elif slot_data.quantity == quantity:
slot_datas[counter] = null
print("SlotData removed")
return true
else:
slot_data.quantity …
I didn’t realize before that a SlotData
item in the slot_datas
array can be null, so I added checks for it now.
The only scenario where it won’t work, which I think you might still need, is when you have the same item in multiple slots, e.g. 10 iron ores in slot 1, 20 iron ores in slot 2, and when you want to remove 30 iron ores - it’ll tell you you don’t have enough. You’d need to combine it into one stack for this to work.
Let me know if you need this functionality to check all stacks, we can add it in.
And in your calling function, you can check if the removal was successful by checking if the return value is true or false, that’s why I added the boolean return type. Instead of just calling the function like that:
inventory_data.remove_item(item, 1)
you can do that:
if inventory_data.remove_item(item, 1):
# removal was successful, so do something
else:
# removal was not successful, so do something else
We have some progress. I thought it wasn’t working at all but then I noticed I couldn’t move the ore around and it looks like it’s just not updating visually. I mined 2 iron ore, I smelt them both but the inventory still shows iron ore x2. I couldn’t move the item like you should be able to so I went and mined another ore and the iron ore x2 was replaced with a single iron ore. Any thoughts on why it’s not updating visually? Hope this explanation makes sense. If not I can try some screen shots.
I think I got it by adding an update inventory in what you sent (the highlighted section)
Let me tinker with it tonight and I’ll get back to you. I really appreciate you taking the time to help me out. I have spent a lot of time getting this to work to where it is and I was very much stuck on just getting an item to remove. Wish me luck!
1 Like
wchc
January 12, 2025, 11:09pm
20
Good point. You might want to add this signal emit in this next elif section as well.
Does it all work correctly now?