Remove Item from Inventory (GD3/4)
Asked Answered
C

12

0

Hello, I'm trying to discard an item on the inventory. I created a button to remove the slot's item (child). It seems to remove only the item icon but not actually empty the slot (New added item will appear on the slot after it). It also removes any slot that has item on it.

I only want to remove an item that is being selected (when in focused). How can I do it correctly? Thank you.
(I'm doing in GD4 but answer in 3 is also ok)

Here are the codes when the "Add" button adds an Item.

#Inventory.gd
func _on_get_item_pressed():
	var slot = getFreeSlot();
	if slot:
		var item = itemDictionary[randi() % itemDictionary.size()];
		var itemName = item.itemName;
		var itemIcon = item.itemIcon;
		var itemFocused = item.itemFocused
		var itemValue = item.itemValue;
		slot.setItem(ItemClass.new(itemName, itemIcon, null, itemFocused, itemValue, null, null));

#ItemSlot.gd
func setItem(newItem):
	add_child(newItem);
	item = newItem;
	item.itemSlot = self;

Here is the code I tried on the "Discard" Button on Inventory.gd

func _on_discard_pressed(): # DISCARD BUTTON SIGNAL
	for slot in slotList:
		if slot.item:
			slot.remove_child(slot.item)
Chu answered 27/7, 2022 at 10:8 Comment(0)
H
0

Chu Edit: for some reason, the spaces are not properly shown for code sections, so I added these {} for clarity.

First, for the queue_free() error:
It basically means giving the queue_free() order several times. Since it's already freed, it's trying to free "null" (although it's not really null, the message is kind of misleading... it's rather an "invalid instance"), hence the error. It doesn't really damage things but indicates something wrong with the code (could cause slowdown though). It happened to me when I once put the command inside the func _process(delta):, which caused the code to run every frame. To solve it:
A) Make sure you only call the command once (maybe by some made-up variable check).
OR
B) Add this simple check:
if is_instance_valid(slot.item): slot.item.queue_free()
TIP: Usually, finding out what's causing the error on your own is easy, since there are probably other people who had the same error. Just google the error...

Chu "Attempt to call queue_free() in base previously_free on a null"

...and you should find something that will help explain things in detail, like this:
https://gamedev.stackexchange.com/questions/192362/what-is-happening-during-queue-free-process

As for the "setItem()" role, it's obviously a made-up function. To understand that, you should know how made-up functions work first. They can be called whenever and assigned a value, for example:
_some_words_representing_a_function(*value, could be anything from a number to a whole scene instanced)
Then, here's how to define what the function actually does:
func _some_words_representing_a_function(something):
*some lines of code doing something)
Now, whatever value you sent is represented by the exact word something.
NOTE: There are also void functions that do NOT send any values when called, e.g. _some_words_representing_another_function()

By this understanding, here's what the function setItem(newItem) does:
func setItem(newItem):{
add_child(newItem);
item = newItem;
item.itemSlot = self;
}

This is obviously NOT a void function, it receives a certain variable represented by newItem. This variable is supposed to be an instanced scene (of a particular item's icon) because it's added as a child of the ItemSlot node in add_child(newItem);. This is apparent in this slot.setItem(ItemClass.new(itemName, itemIcon)); code line, in "Inventory.gd".

The next line of code is simply assigning whatever variable newItem represents to the previously defined variable item. Despite being simple, this here is the key to the solution to your problem.

The last line of code is again defining the variable itemSlot in "Item.gd" to self, which represents the node to which "ItemSlot.gd" is attached. What this is used for is not very clear... maybe I'm missing something. Anyway, this shouldn't affect how to solve your problem.

Now here is the solution that I believe will do the trick, though there is a chance I'm still missing something. It's in 'spoiler', in case you want to try on your own given the above explanation and my previous answer.
The only thing that defines whether a "slot" is full or not is the variable item. If it's null, then the slot is empty. If it's not null, the slot is full. Here's the catch, when the instanced scene is freed, the variable is still NOT NULL (as mentioned above); it's only "invalid". To make it null...

Click to reveal Click to hide

Add the following line of code to the _on_button_pressed() function of the DISCARD BUTTON:
slot.item = null, so the overall code becomes something like:
func _on_button_pressed():{
for slot in slotList:{
if is_instance_valid(slot.item):{
slot.item.queue_free();
call_deffered("nullSlotItem");
}}}
func nullSlotItem():{
slot.item = null
}

I hope this actually works... If it does work, then you can ask me why I wrote the code this way.

Hight answered 28/7, 2022 at 7:31 Comment(0)
C
0

I looked at your post for awhile and am mostly confused, but I think your problem might be that your "slot" has saved references in two places which would explain why you're having trouble removing it. add_child(newItem); looks like one place and item.itemSlot = self; another.

In situations where you're keeping track of something in two places, I suggest writing the create and remove functions side by side to make sure you are clearing out whatever you added in both places.

Cue answered 27/7, 2022 at 12:40 Comment(0)
H
0

Just to be clear, how the code exactly works is not very clear, but the problem should be easy to solve. For getting an item and setting it into a slot, you:
1) add the icon of the item as a child,
2) define a number of variables and set them to specific values.

HOWEVER, when discarding an item, you:
1) remove the icon of the item (which is the child of the slot),
2) ......... NOTHING?!

Can you see what I'm getting at? The variables responsible for defining whether a slot is full containing a specific item with specific values have NOT been changed when the item is discarded. So, the program thinks the slot is still full (probably with all other values related still intact).

Removing the child, being the icon of the item, only means that. If you want to stop it from being 'full', you MUST tell it to do so.

P.S. I see you're using remove_child(), instead of queue_free(). Just wanted to note that you can actually reuse a child removed using remove_child(), meaning data is NOT really deleted. If you're not using it anymore, just use queue_free() instead.

Hight answered 27/7, 2022 at 13:57 Comment(0)
C
0

The code is from an inventory project that I striped away and slowly add each function to study how it works. Here are the minimum codes that I have now, to help clear things up.

#Item.gd

extends TextureButton

var itemIcon;
var itemName;
var itemSlot;

func _init(_itemName, _itemIcon):
	self.itemName = _itemName;
	texture_normal = _itemIcon;

#ItemSlot.gd

extends Panel

var item = null;
var style;

func _init():
	custom_minimum_size = Vector2(34, 34);
	style = StyleBoxFlat.new();
	style.set_border_width_all(2);
	set('custom_styles/panel', style);
	
func setItem(newItem):
	add_child(newItem);
	item = newItem;
	item.itemSlot = self;

#Inventory.gd

extends Control;

const ItemClass = preload("res://Scripts/Item.gd");
const ItemSlotClass = preload("res://Scripts/ItemSlot.gd");
const MAX_SLOTS = 15;
const itemImages = [
	preload("res://images/Ring.png"),
];
const itemDictionary = {
	0: {
		"itemName": "Ring",
		"itemIcon": itemImages[0],
	},
};
var slotList = Array();

func _ready():
	var slots = get_node("Panel/ScrollContainer/GridContainer");
	for _i in range(MAX_SLOTS):
		var slot = ItemSlotClass.new();
		slotList.append(slot);
		slots.add_child(slot);
		
func getFreeSlot():
	for slot in slotList:
		if !slot.item:
			return slot;
			
func _on_get_item_pressed():
	var slot = getFreeSlot();
	if slot:
		var item = itemDictionary[randi() % itemDictionary.size()];
		var itemName = item.itemName;
		var itemIcon = item.itemIcon;
		slot.setItem(ItemClass.new(itemName, itemIcon));

#MY DISCARD BUTTON
func _on_button_pressed():
	for slot in slotList:
		if slot.item:
			slot.item.queue_free()

Cue Hight

I used slot.item.queue_free() before. It worked but I got error "Attempt to call queue_free() in base previously_free on a null" after a few presses. What does the error mean? How to fix?

I'd also like to know what func setItem(newItem) is actually doing?

Sorry please explain to me as a non coder Thank you πŸ˜…

Chu answered 27/7, 2022 at 16:36 Comment(0)
H
0

Chu Edit: for some reason, the spaces are not properly shown for code sections, so I added these {} for clarity.

First, for the queue_free() error:
It basically means giving the queue_free() order several times. Since it's already freed, it's trying to free "null" (although it's not really null, the message is kind of misleading... it's rather an "invalid instance"), hence the error. It doesn't really damage things but indicates something wrong with the code (could cause slowdown though). It happened to me when I once put the command inside the func _process(delta):, which caused the code to run every frame. To solve it:
A) Make sure you only call the command once (maybe by some made-up variable check).
OR
B) Add this simple check:
if is_instance_valid(slot.item): slot.item.queue_free()
TIP: Usually, finding out what's causing the error on your own is easy, since there are probably other people who had the same error. Just google the error...

Chu "Attempt to call queue_free() in base previously_free on a null"

...and you should find something that will help explain things in detail, like this:
https://gamedev.stackexchange.com/questions/192362/what-is-happening-during-queue-free-process

As for the "setItem()" role, it's obviously a made-up function. To understand that, you should know how made-up functions work first. They can be called whenever and assigned a value, for example:
_some_words_representing_a_function(*value, could be anything from a number to a whole scene instanced)
Then, here's how to define what the function actually does:
func _some_words_representing_a_function(something):
*some lines of code doing something)
Now, whatever value you sent is represented by the exact word something.
NOTE: There are also void functions that do NOT send any values when called, e.g. _some_words_representing_another_function()

By this understanding, here's what the function setItem(newItem) does:
func setItem(newItem):{
add_child(newItem);
item = newItem;
item.itemSlot = self;
}

This is obviously NOT a void function, it receives a certain variable represented by newItem. This variable is supposed to be an instanced scene (of a particular item's icon) because it's added as a child of the ItemSlot node in add_child(newItem);. This is apparent in this slot.setItem(ItemClass.new(itemName, itemIcon)); code line, in "Inventory.gd".

The next line of code is simply assigning whatever variable newItem represents to the previously defined variable item. Despite being simple, this here is the key to the solution to your problem.

The last line of code is again defining the variable itemSlot in "Item.gd" to self, which represents the node to which "ItemSlot.gd" is attached. What this is used for is not very clear... maybe I'm missing something. Anyway, this shouldn't affect how to solve your problem.

Now here is the solution that I believe will do the trick, though there is a chance I'm still missing something. It's in 'spoiler', in case you want to try on your own given the above explanation and my previous answer.
The only thing that defines whether a "slot" is full or not is the variable item. If it's null, then the slot is empty. If it's not null, the slot is full. Here's the catch, when the instanced scene is freed, the variable is still NOT NULL (as mentioned above); it's only "invalid". To make it null...

Click to reveal Click to hide

Add the following line of code to the _on_button_pressed() function of the DISCARD BUTTON:
slot.item = null, so the overall code becomes something like:
func _on_button_pressed():{
for slot in slotList:{
if is_instance_valid(slot.item):{
slot.item.queue_free();
call_deffered("nullSlotItem");
}}}
func nullSlotItem():{
slot.item = null
}

I hope this actually works... If it does work, then you can ask me why I wrote the code this way.

Hight answered 28/7, 2022 at 7:31 Comment(0)
C
0

Hight
First of all Thanks for the explanation, it really helps me visualize how codes are working. I also remember I went through that stackexchange question before (on other issue) but all I got from that thread was it doesn't fully "delete" and has some "leftovers" πŸ˜„

Anyway I got discard button working with is_instance_valid() as you said. But when you said item = newItem is the key to the solution along with spoiler tag (which I have not open honestly). Do you mean is_instance_valid() is not the solution? As I understand slot.item.queue_free() would destroy whatever setItem() created (item = newItem = ItemClass.new(itemName, itemIcon)) correct? But if not can you give me some more hints so I can try myself before I open the spoiler tag.

Also I have Item.gd to extend TextureButton because I want highlight (texture_focus) on the item. How can I queue_free() an item that is on focused only? I read of using get_focus_owner()? but I can't find the equivalent for GD4.

Chu answered 28/7, 2022 at 16:4 Comment(0)
H
0

Chu Edit: added "answer" to each hint, covered by spoiler.
First, I need to clarify I'm not 100% sure of the answer, but more like 90%, because I haven't personally tried it (my answer is only theoretical). As for is_instance_valid(), this only removes the error, but doesn't solve your actual problem.
Now for the hints (more like understanding points):
1)What actually counts as the slot being "empty"? Computers are stupid, so there must be some variable whose values indicate the state of the slot. What's that variable?
Answer1: This can be known if you look at what the getFreeSlot() function does.

2) In the setItem(newItem) function, you can see the child is added directly in one code line add_child(newItem);, then what's the use of the second line item = newItem?
Answer2: if !slot.item: return slot; This line of code means that if there is no slot.item ("!" means "not"), in other words if the variable item is null, then return slot. This code does the opposite of _func_name(value). _func_name(value) is used to INSERT a value INTO a particular function (as explained above). However, return slot will SEND a value OUTSIDE a particular function. In this case, getFreeSlot() will EQUAL the variable slot. So, this func _on_get_item_pressed(): var slot = getFreeSlot(); means that the variable slot inside the _on_get_item_pressed() equals the function getFreeSlot(), which in turn was previously equalized to the variable slot created in the _ready() function and mentioned in the getFreeSlot().

3) What you got...

Chu all I got from that thread was it doesn't fully "delete" and has some "leftovers"

...is actually important! Remember when I wrote ...

Hight Since it's already freed, it's trying to free "null" (although it's not really null, the message is kind of misleading... it's rather an "invalid instance"), hence the error.

This means the variable item is NOT NULL, but ONLY an 'invalid instance'. queue_free() actually destroys the data, BUT for some deep programming philosophies (explained in that stackexchange question) it's NOT considered "null". How does that exactly pose a problem?
Answer3: Simply write slot.item = null. How this is exactly done is shown in my previous answer.

Those are the hints. I did post a solution in my previous answer, but here is an alternative solution that could also work (see there's never only one method in programming):
the variable item is the one behind the logic of whether the slot is empty (at least from what I got). If it's NULL, the slot is empty. Maybe you could create var slot_full = false representing an empty slot (and true for full slot), hence skip over all that deep understanding of how queue_free() works. The variable is boolean (only two values; true or false), so takes a tiny bit of memory. An easy and efficient solution. The variable is changed in setItem() and in "discard" func

Chu Also I have Item.gd to extend TextureButton because I want to highlight (texture_focus) on the item. How can I queue_free() an item that is on focused only? I read of using get_focus_owner()? but I can't find the equivalent for GD4.

The discard button and the texture button here are different, right? just so you know, a button is in focus only when the cursor is on top of it. This means that as soon as you click on the discard button, the discard button is the one in focus, not the texture button of the item. You could create a variable var item_in_focus = *number or string value, and change the variable whenever the focused item is changed (maybe when the texture button is pressed it sends a signal or something).
Edit2: if you want, you could open a separate question discussion n the forum to tackle this problem, and then you could give more details. This is to keep things organized and problem solving more focused. If you post the question, I will make sure to check it out!

Last but not least, I wanted to mention this:
https://docs.godotengine.org/en/stable/classes/class_itemlist.html
I never used it myself, but it could help you design things more easily if you take the time to read about it. Though, I highly doubt it will make the programming part any easier.

Hight answered 28/7, 2022 at 17:0 Comment(0)
C
0

Hight

ok I seriously have not open any of the spoilers but I got this to work lol

for slot in slotList:
	if slot.item:
		slot.item.queue_free()
		slot.item = null

..since queue_free() doesn't make it null so I just make it null , Is it correct πŸ˜†? Not sure if it's same as your answer but I will check spoilers later on.

itemlist

I've checked out itemlist but from what I read on reddit it is not really "dynamic" and not recommend for inventory. And since no inventory tutorials (that I found) using it I kinda shelved it for now.

Delete only the one on focused

I will give it a try first before open another topic. I got a few things I want to understand how to do. Inventory is most confusing to code for me so far. (another is state machine, but I plan to learn it after inventory)

Again thanks for all this πŸ‘οΈ

Chu answered 29/7, 2022 at 7:53 Comment(0)
H
0

Chu That's basically it, congratulations! Choosing to solve it on your own was really a good move. Also, in my answer, I used call_deferred() to do the same effect, because I was afraid something could go wrong if both queue_free() and = null in the same frame would cause a problem (whatever is called using call_deferred() happens one frame later). But, since it works alright, then yeah no need for that. It's the same answer as mine (check answer3 in the "hints" comment).

I actually agree with inventory being difficult to code, because you're just making "imaginary logic" where the effect of your code is not directly apparent.

As for spoiler tags, just skip them all, and maybe only check out the alternative solution (in the hints comment too).

I just wanted to say I had fun solving this as well... after all, this what programming is all about!

Hight answered 29/7, 2022 at 8:20 Comment(0)
C
0

Hight

For me coding is fun when it is solved πŸ˜† . I didn't think it was right answer cause normally when I tell it to do something it won't.

Also just 2 quick questions if you *or anyone really don't mind:

1) When I tried delete var itemSlot from the item.gd, the setItem() on ItemSlot.gd will show error because it's missing itemSlot. Is it because inventory.gd has this?

const ItemClass = preload("res://Scripts/Item.gd");
const ItemSlotClass = preload("res://Scripts/ItemSlot.gd");

Does preload A and B on the C will link them all 3 together? I was assuming it only links A,B inside of C only, but this shows between A and B as well. If you know what I mean? (pls excuse my non programer terms)

2) Would using Bool in item.gd affects all item being set by setItem()? I'm wondering if I can use has_focus() instead to check for that one button in focus to delete.

Chu answered 30/7, 2022 at 11:29 Comment(0)
H
0

Chu

Chu I didn't think it was right answer cause normally when I tell it to do something it won't.

Yeah, thaaat feeling, it's something that probably every programmer agrees on (at least in the beginning, lol).

1) I don't think that's the cause. In essence, preload() does not link nodes together. My guess is that in func setItem(newItem) in ItemSlot.gd there's this line of code:
item.itemSlot = self;
This will try to find the "itemSlot" inside the - scene represented by the - variable "item" in ItemSlot.gd. Of course, the variable cannot be found, since you deleted it. (Somehow yes it's related to this const ItemClass = preload("res://Scripts/Item.gd"); but not actually. To solve the problem, I think you should simply remove the line of code I talked about... item.itemSlot = self;

2) Since the code is shared, yes, any variable set, in the beginning, is the same across all scenes, so affects all scenes sharing the code. However, that's only at the beginning. Later on, the variables of each scene can be changed independent of each other, according to how each scene interacts with other scenes. This is usually coded for via certain functions or signals. So, I guess you can use has_focus(). I personally never used it myself, but made a quick search about it. I wonder how you will integrate this function into your code!

Hight answered 30/7, 2022 at 15:45 Comment(0)
I
0

Hight

Chu I didn't think it was right answer cause normally when I tell it to do something it won't.

Yeah, thaaat feeling, it's something that probably every programmer agrees on (at least in the beginning, lol).

Except computers are dumb and do exactly what the programmer tells them to do, no nuance, just explicit instruction processing.

Ictinus answered 30/7, 2022 at 17:32 Comment(0)
W
1

Hight Hi how can you save the inventory data inside a file and load that file on the startup?

Whippersnapper answered 16/3 at 16:22 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.