Unable to access my global script function in another script

Godot Version

Godot_v4.2.2-stable_win64.exe

Question

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?

I think we need some more information:

  • Can you verify that your autoload script is being loaded into the scene tree at run time?
  • Can you post your autoload script?
  • Can you post the script you are attempting to access that autoloaded script?
  • Does running “Project > Reload Current Project” fix the issue?

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(): ...

No, none of my function is marked static. I already posted my script but it’s pending. Please check back to see it in a few minute…

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)

Here’s the code in my GameManager.gd global script:

extends Node

var score = 0
var player_health = 10
@onready var coin_counter = $"../CanvasLayer/CoinCounter"
@onready var health_counter = $"../CanvasLayer/HealthCounter"

func add_point():
	score += 1
	coin_counter.text = "Coin: " + str(score)

func minus_health():
	player_health -= 1
	health_counter.text = "Health: " + str(player_health)

I accessed the add_point function in another script just fine. But the minus_health function is inaccessible. Why?

What is the exact error you are getting?

It’s basically acting as if the minus_health function doesn’t exists. It can’t find it, but it’s finding the other functions in the same file.

When i do: GameManager.add_point(), its auto completing it. But when i do GameManager.minus_health(), it’s not auto completing.

If i add it myself, it simply says the function doesn’t exist

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.

1 Like

Answer to your Questions

  • I did some refactoring and I realized `$"../CanvasLayer/HealthCounter` path is not correct
  • There are no multiple copies of same script, I also have no Built-in script
  • Yes, `GameManager.gd` is in the scene tree at run time
  • Here is my scene tree:

    scenetree

    Here is my enemy scene:

    enemy

    I attach a script named KillPlayer.gd to an Area2D node. I’ve figured out that the HealthCounter file path isn’t correct.

    My final question is, how do I get access to it in the KillPlayer.gd script?

    Here is my KillPlayer.gd script:

    extends Area2D
    
    @onready var enemy = $".."
    @onready var ray_cast_right = $"../RayCastRight"
    @onready var ray_cast_left = $"../RayCastLeft"
    @onready var health_counter = $CanvasLayer/HealthCounter
    
    func _on_body_entered(body):
    	if body.name == "Knight":
    		print(GameManager.minus_health())
            health_counter.text = GameManager.minus_health()
    
    func _on_detect_player_body_entered(body):
    	if body.name == "Knight":
    		enemy.play("Waking")
    
    1 Like

    In the screen shot of the World scene, your GameManager node is not an autoload. It is called a Scene Unique Node.

    Scene unique nodes are not global. They are only accessible from the same scene.

    It will help you to review the get_node() method and the NodePath description.

    Steps

    1. Make your GameManager a proper Autoload node. Once its an autoload, then you can access it as expected.
    2. 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))
    
    
    1. 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.

    References

    1 Like

    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.

    1 Like

    Sorry for my rookie mistakes, I’m just 2 weeks into Godot. I’m learning a lot just from this single thread I made.

    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()
    
    1 Like

    This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.