Godot Version 4.2.1.
Question
I have an inventory (as a resource) that contains slots which is an array of inventory slots (a resource), and every slot can have an inventory item (also a resource). I watched DevWorm’s YT tutorial on this, if it could help you with the code. Now, the items do add and they are shown in the inventory and can stack. However, I want to now be able to remove a specific item once a signal from a different script has been sent (an action is performed → it sends a signal with information on what exact material to remove and how many (amount). This is the thing – i want exact amount and material to remove on a signal emission. How could I achieve this?
Thanks in advance
Assuming an inventory slot knows how many of which material it holds:
- loop over the inventory slots.
- If an inventory slot contains the material, subtract as much as possible and save the remaining amount to be subtracted
- Continue the loop and if another slot has the material to be subtracted, subtract that also
You’d need to pass the material and the amount into the signal. You can see a function like that here: CataX/Scripts/item_manager.gd at 609df49686625167d8e63233434874efdd028e33 · snipercup/CataX · GitHub
But it uses item_id instead of material.
Thx for the help, however, my code looks a bit different already and I don’t have an idea how can I adapt this function to my inventory scripts:
This is the code:
Inventory.gd:
extends Resource
class_name Inventory
signal update
@export var slots: Array[InvSlot]
func insert(item: InvItem):
var itemslots = slots.filter(func(slot): return slot.item == item)
if !itemslots.is_empty():
if itemslots[0].can_add_to_stack(1):
itemslots[0].amount += 1
else:
var emptySlots = slots.filter(func(slot): return slot.can_accept_item(item))
if emptySlots != null:
emptySlots[0].item = item
emptySlots[0].amount = 1
update.emit()
inventory_item.gd:
extends Resource
class_name InvItem
@export var name: String = ""
@export var texture: Texture2D
@export var item_id: int
inv_slot.gd:
extends Resource
class_name InvSlot
@export var item: InvItem
@export var amount: int
@export var maxStackSize: int = 20
@export var isFull: bool = false
func can_add_to_stack(amount_to_add: int) -> bool:
return amount + amount_to_add <= maxStackSize
func add_item(item_to_add: InvItem) -> bool:
if can_add_to_stack(1):
item = item_to_add
amount += 1
if amount == maxStackSize:
isFull = true
return true
return false
func remove_item():
if amount > 0:
amount -= 1
if amount < maxStackSize:
isFull = false
return item
return null
func can_accept_item(item_to_add: InvItem) -> bool:
return item == null or (item == item_to_add and !isFull)
inv_ui.gd:
extends Control
@onready var inv: Inventory = preload("res://players_inventory.tres")
@onready var slots: Array = $NinePatchRect/GridContainer.get_children()
var is_open = false
func _ready():
inv.update.connect(update_slots)
update_slots()
close()
func update_slots():
for i in range(min(inv.slots.size(), slots.size())):
slots[i].update(inv.slots[i])
func _process(_delta):
if Input.is_action_just_pressed("i"):
if is_open:
close()
else:
open()
func close():
visible = false
is_open = false
func open():
self.visible = true
is_open = true
inv_ui_slot.gd:
extends Panel
@onready var item_visual: Sprite2D = $CenterContainer/Panel/item_display
@onready var amount_text: Label = $CenterContainer/Panel/Label
func update (slot: InvSlot):
if !slot.item:
item_visual.visible = false
amount_text.visible = false
else:
item_visual.visible = true
item_visual.texture = slot.item.texture
if slot.amount > 1:
amount_text.visible = true
amount_text.text = str(slot.amount)
I have 7 materials (item types); material1.tres, material2. tres and so on
So you’d adapt this function to remove an item:
# Remove the specified amount of the specified item from the inventory
func remove(item: InvItem, amount: int) -> bool:
var was_removed: bool = false
var amount_to_remove: int = amount
# Get all itemslots that have this item
var itemslots = slots.filter(func(slot): return slot.item == item)
if !itemslots.is_empty():
for slot in itemslots:
if amount_to_remove <= 0:
was_removed = true
break # Stop if we have removed enough of the item.
if slot.amount <= amount_to_remove:
# This slot doesn't have enough of this item to satisfy the removal. Remove everything we can
amount_to_remove -= slot.amount
slot.clear()
else:
# If the current item stack has more than we need, reduce its stack size.
slot.amount -= amount_to_remove
amount_to_remove = 0 # Set to 0 as we have removed enough.
if was_removed:
update.emit()
return was_removed
The problem with this function is that it will reduce the amount from the inventory and still returns false if the amount wasn’t satisfied. This could lead to items getting lost. You could return the remaing amount_to_remove instead and handle it a different way. You might need a new function that calculates if the reduction can be satisfied beforehand.
Another small thing in this function: Take the if statement that checks if amount_to_remove <= 0, and move it down after the else branch of the next if statement. Otherwise, if we’re trying to move exactly the amount that was present, it will also return false.
Calculating the amount present in the inventory is easy enough once you have itemslots:
var amount_present = 0
for slot in item_slots:
amount_present += slot.amount
if amount_present < amount:
return false
In full:
# Remove the specified amount of the specified item from the inventory
func remove(item: InvItem, amount: int) -> bool:
var was_removed: bool = false
var amount_to_remove: int = amount
# Get all itemslots that have this item
var itemslots = slots.filter(func(slot): return slot.item == item)
if !itemslots.is_empty():
# check if it's possible to remove the full amount
var amount_present = 0
for slot in item_slots:
amount_present += slot.amount
if amount_present < amount:
return false
# actually remove
for slot in itemslots:
if slot.amount <= amount_to_remove:
# This slot doesn't have enough of this item to satisfy the removal. Remove everything we can
amount_to_remove -= slot.amount
slot.clear()
else:
# If the current item stack has more than we need, reduce its stack size.
slot.amount -= amount_to_remove
amount_to_remove = 0 # Set to 0 as we have removed enough.
if amount_to_remove <= 0:
was_removed = true
break # Stop if we have removed enough of the item.
if was_removed:
update.emit()
return was_removed
A bit of a gnarly function at this point, and I haven’t tested it (as I don’t have an inventory system lying around that it would fit into…), but I think it’s probably pretty close. It’s also possible to get rid of was_removed, and just fire the signal + return true as soon as enough items are removed (and then return false or throw an error after the loop - if we get that far, something has gone wrong, since we checked in advance that it’s possible to remove the items).
But where do I specify the item: invitem and amoun:int first? Like lets say for a test: in a separate script where i press a button and then it’s:
inventory.remove(). So what’s in the parentheses?