Search code examples
timergetgame-developmentgodotonready

Godot: Getting a node results in Nil Value


i am trying to get into Godot. In my project i want to access a Timer called Duration Timer in the Dash Script attached to the Dash Node.

Screenshot - Godot get Duration Timer

Already, i tried 3 ways of accessing the Node seen in line 4,5 and 6 of the screenshot.

onready var duration_timer = $DurationTimer
#onready var duration_timer = get_node("DurationTimer")
#onready var duration_timer = get_node("Movement/Dash/DurationTimer")

All 3 lines result in the highlighted errormessage

"Invalid set index 'wait_time' (on base: 'Nil') with value of type 'int'.

The error appears in line 14 by trying to set a wait_time to the Timer element.

duration_timer.wait_time = 1

Does someone have a clue why i cant get the Timer element?


Solution

  • I don't know what is causing the problem.

    I do not think get_node("DurationTimer") or $DurationTimer are wrong. But if that is the problem, we can solve it by using a NodePath:

    export var duration_timer_path:NodePath
    onready var duration_timer = get_node(duration_timer_path)
    

    Then set the NodePath from the inspector panel.


    If the path is not wrong. It could be that the code is running before _ready. or that something (I don't know what) removed the timer or set it to null. You can add an breakpoint and check on the remote tab of the scene tree panel when the game is running from the editor, to see if the timer is there.

    We can sidestep the problem by making a new timer if we don't have one. For example:

    if not is_instance_valid(duration_timer):
        duration_timer = Timer.new()
        duration_timer.name = "DurationTimer"
        add_child(duration_timer)
    
    # use duration_timer here
    

    However, if the timer is there but we had failed to get it, we would have two timers in the scene tree. You could also check in the remote tab of the scene tree panel if this resulted in two timers.

    So, let us try to get the node again, in case we failed the first time. And let us put it all in a method. Something like this:

    func get_duration_timer() -> Timer:
        if is_instance_valid(duration_timer):
            return duration_timer
    
        duration_timer = get_node("DurationTimer") # or use a NodePath
        if is_instance_valid(duration_timer):
            return duration_timer
    
        duration_timer = Timer.new()
        duration_timer.name = "DurationTimer"
        add_child(duration_timer)
        return duration_timer
    

    In fact, if we are in this scenario, then onready is not helping us. So we could have duration_timer defined without onready and without initialization. And always use the method to get the timer.

    If we are going to always use the method, why not let the method add the timer instead of adding it in the scene tree? We could then remove the step of trying to get it:

    func get_duration_timer() -> Timer:
        if is_instance_valid(duration_timer):
            return duration_timer
    
        duration_timer = Timer.new()
        duration_timer.name = "DurationTimer"
        add_child(duration_timer)
        return duration_timer
    

    What we have here is a lazy initialization pattern: The timer is created when we first need it, on demand, not before.