I thought I could solve this on my own but after spending a few hours, I decided to ask for help here. I’m pretty new to Godot, but I’m trying a project which requires me to Autoload a script and make it Global.
But whenever I tried to some functions or variables in another script, it simply won’t detect them. It will return with an error like Function or Variable is null. It let’s me access some functions and some is inaccesable.
Already search the internet, can someone tell me why it’s doing this?
Seeing some code would help. But as a first thing to check, if some functions are working and not others: Did you mark all of your functions as “static”? If some are working but not others, you may have forgotten to mark the non-working functions as static, by writing e.g. static func my_function(): ...
Thanks for your reply, I already added my codes as a reply under the main thread but it isn’t approved yet.
I already tried the Project > Reload Current Project, it didn’t work. My Autoload script file name is GameManager.gd:
extends Node
var score = 0
var player_health = 10
@onready var coin_counter = $"../CanvasLayer/CoinCounter"
@onready var health_counter = $"../CanvasLayer/HealthCounter"
# I can access this function in another script
func add_point():
score += 1
coin_counter.text = "Coin: " + str(score)
# But I can't access this. When I try GameManager.minus_health(), it's not working and it returns an error
func minus_health():
player_health -= 1
health_counter.text = "Health: " + str(player_health)
Please post the exact error messages you are getting. The more context the better. Keep in mind that we have to build this scenario locally in our own minds from the information available.
From the current information, there is nothing wrong.
Information such as
exact error messages
line numbers where errors are occurring
the block of code where an error occurs
relevant scripts that interact with an error source
scene tree
is greatly appreciated.
Questions
Is $"../CanvasLayer/HealthCounter" path correct?
The paths are a little curious. Autoloads are added to the scene tree’s root. The counter paths suggest that you either have a CanvasLayer with ui elements under scene tree’s root or your main scene’s root is CanvasLayer. Both are atypical, not wrong, just atypical.
Are there multiple copies of the same script? Scripts can be in memory only and not yet associated with a scene or file. There could also be a script saved in the scene, called a “Built-in Script”, attached to the node, and there’s a copy on the file system that you may be editing.
Can you verify “GameManager.gd” autoload is in the scene tree at run time? Go to the “Scene dock” and click “Remote” while the game is running.
Make your GameManager a proper Autoload node. Once its an autoload, then you can access it as expected.
Instead of having GameManager reference HealthCounter and CoinCounter, the functions GameManager.add_point() and GameManager.minus_health() should emit signals. The CoinCounter and HealthCounter can connect to the GameManager signals.
# GameManager.gd
signal score_changed(new_score)
signal player_health_changed(new_health)
var score = 0
var player_health = 10
func add_point():
score += 1
score_changed.emit(score)
func minus_health():
player_health -= 1
player_health_changed.emit(player_health)
# ------------------------
# HealthCounter.gd
func _ready():
GameManager.player_health_changed.connect(
func(health): text = str("Health: ", health))
# ------------------------
# CoinCounter.gd
func _ready():
GameManager.score_changed.connect(
func(score): text = str("Coin: ", score))
You may want to create and attach a script to your Enemy node. You can move the entire KillPlayer script to the Enemy node. My recommendation would be:
# Enemy.gd
@onready var ray_cast_right = $RayCastRight
@onready var ray_cast_left = $RayCastLeft
func _on_kill_player_body_entered(body):
if body.name == "Knight":
GameManager.minus_health()
func _on_detect_player_body_entered(body):
if body.name == "Knight":
play("Waking")
I recommend step 3 especially because in KillPlayer.gd you are grabbing its parent with $".." and its siblings RayCastRight and RayCastLeft. When you see this happen, that code can probably be moved up the scene tree. In this case moving it to a script on “Enemy” cleans up the code and provides a clearer hierarchy.
Thanks for the many suggestions. But I’m sure GameManager is an autoload, i made it unique and also Autoloaded it for global accessibility Project Settings > Autoload.
I will try out your suggestions and see if it works.
If GameManager is an autoload there is no reason to also have it as a child node of World.
As an autoload, GameManager is already in the scene tree.
A Singleton (Autoload) is an always loaded node that can be accessed directly by any node regardless of the scene. These are useful when some data or functionality is shared globally
Doubling the node up is going to lead to confusion at best.
Your World root node is an object and only that which belongs in a World should be nodes in it.
A GameManager seems to me to be above World, not a child of it.
Hey, after reading a lot of documentation you referenced, refactoring my codes and file system, I finally get it to work. Thanks so much to you and everybody for all the valuable inputs. I learnt a lot on this single thread, I really appreciate.
What I finally did is, I attached a script to Enemy node and then dragged in the HealthCounter label. Then, I called a body_entered signal in the Enemy.gd script. The code is like so:
extends AnimatedSprite2D
@onready var health_counter = $"../CanvasLayer/HealthCounter"
func _on_kill_player_body_entered(body):
if body.name == "Knight":
health_counter.text = GameManager.minus_health()