I have a system in place that has a dictionary with different ways in-game items can be combined in the player's inventory as a sort of look-up table. In a script attached to the Globals node, there is this (small, as I'm still testing the system) dictionary:
I have an abstract item class with an itemName property, as seen here:
There are too many weird links to explain in full right now, but I'm trying to check whether or not there is an entry in combineDict with the name of the item I'm trying to check for (the itemName property of that item). I have tried both if globals.combineDict.has(items[indexDraggedOnto].itemName)
and if items[indexDraggedOnto].itemName in globals.combineDict
to no avail, I get the same error: Invalid access to property or key 'combineDict' on a base object of type 'Nil'. I have even added a line immediately before this to print items[indexDraggedOnto].itemName
, and it prints TestItem as expected. I have no clue what could be going wrong here. If I need to post more details about my code, I will, I just need to get this fixed.
Getting Nil key error, despite key not being Nil
telescopica you can't make it a const.
also, you are supposed to use :
with dictionaries, not =
var combineDict : Dictionary = {"TestItem" : {"TestDoc" : "erase"}}
https://docs.godotengine.org/en/stable/classes/class_dictionary.html
what is globals
? autoloads must have PascalCase names like other classes. if this is not an autoload, whatever you are doing you are doing it wrong.
- Edited
Jesusemora @onready var globals := get_node("/root/MainScene/Globals")
, which has the script globals.gd attached.
It's odd that the colon/equate issue only cropped up now, I used = instead of : to make a different dictionary which is still working as expected. It also isn't the problem. Making all changes you suggested still returns the same error.
At the time of the error, the parameter I'm checking is exactly what it should be, and definitely not null.
(On a different note, if there is a better way to have a global variables/references, please share, this is my first project with Godot.)
- Edited
telescopica The error means that globals
is null. You may be trying to access it before it was assigned in that @onready
declaration. Are you trying to access it from some other node's _ready()
?
- Edited
xyz The error doesn't occur until I try dragging one inventory item onto another, which is where the error takes place. Plenty of references to the globals node are made before this successfully (loading the mesh and inventory images use paths specified in globals.gd
).
It seems to be specific to this one script. I added a line to simply print the outcome of globals.combineDict.has("TestItem")
in a different script and it works as expected, so for whatever reason globals
in this specific function is being improperly assigned or something, even though it's the exact same line as it is in other perfectly functional scripts. I'll look into it a bit more and figure it out on my own, then post whatever the solution is here so that it is documented, but if anyone has advice it would be much appreciated.
Edit: globals
is in fact null, but only in the script that is causing errors (in my case inventory_container.gd
, which is defined as a class named InventoryContainer
). It looks like it may be an issue with treating it as a class instead of instancing the actual script, as all external node variables are null in instances of InventoryContainer
.
- Edited
telescopica The error message literally tells you what's wrong - variable globals
is null. You just have to determine why and where it went null. It may be because that @onready assignment happens before the /root/MainScene/Globals
was instantiated. Or it was overridden by another assignment. Instead of doing @onready assignment, put it in the _ready()
function and check its value immediately after assignment. In the same place print the path of the node running the script so see if it wasn't accidentally run as an autoload.
I'm just guessing here. You haven't provided enough information for more than that.
- Edited
- Best Answerset by telescopica
@telescopica
Not sure I understand your explanation
@onready var foo = 1
is equivalent of
var foo
func _ready():
foo = 1
_ready()
can only be implemented in Node
derived classes and it will only run after the node is added to the scene tree and all of its children's _ready()
have been executed. It's not called immediately upon object instantiation. You can instantiate a node but never add it to the scene tree. In that case _ready()
as well as any @onready
assignment will never run.
If you need to do some initialization immediately upon instantiation, use _init()
instead. It's equivalent of class' constructor in other oo languages.
- Edited
The issue was that orphan nodes do not behave like I thought they did. I had neglected to set the instance of inventory_container.gd
as a child of another node, and orphan nodes are unable to find nodes using get_node()
, even with an absolute path. I needed to change how the node was instanced for the debugger to tell me that because it was an orphan, it could not find the Globals node, which is why I didn't know about this behavior.
Apologies for not posting more code, in hindsight I should have but I would be basically uploading the whole project here with how tightly weaved everything is.
- Edited
telescopica orphan nodes are unable to find nodes using get_node()
Of course they are not because they're not a part of the scene tree.
https://docs.godotengine.org/en/4.3/classes/class_node.html#class-node-method-get-node
As stated in the docs, the absolute path fetch only works when the node is in the tree. Although the relative path will still work for fetching existing descendants regardless of whether the node is in the scene tree or not.
Orphaned nodes do not have a reference to the SceneTree
object, hence they cannot know which node is the root.
- Edited
telescopica this is clearly something that has to be global, so USE AN AUTOLOAD and stop messing with paths.
you can add an autoload in project settings. autoloads act as singletons, they are nodes that are added before the game starts and exist outside the main scene, they are meant for these things, to exist after the scene is destroyed and store data.
there will ONLY be ONE instance of each autoload at all times.
in your case what you were looking for was to make these static. all you had to do was register the class with class_name, but I would just use an autoload.
name the autoload a unique name in snake_case, like item_combinations
, it will be automatically registered in PascalCase, as ItemCombinations
. you can then access this node from anywhere, from any script, like:
item_combinations.gd
extends Node
var combine_dict : Dictionary = {}
my_script.gd
ItemCombinations.combine_dict.get("TestItem", null)
and PLEASE follow the gdscript style guide.
Jesusemora Followed your advice, no longer using the Lua-style dictionaries and put globals.gd
into autoloads instead of a Globals node.
I have been forgetting to give the AbstractItems a parent, so when I made an AbstractInventory the child of an AbstractItem, it didn't know where the nodes were, since it wasn't in the main tree. I fixed this issue and now all is working as expected.
I'm sorry that I had not been posting as much information as needed. It was late and my explanation skills are garbage. Thank you for your time.