Why is Timer.time_left read-only ?

Godot Version

V4.3 77dc97d8

Hi, I’m new to Godot and learning.

I’m making a small game about picking carots when they are ripe. Each Carot scene has a Timer on it related to its growth. When a Carot is instanced a function is called to set a random value to the Timer.wait_time, so that each Carot grows at a different pace. The Sprite2D animation is based on Timer.time_left.

So far so good. But when the game is initiated, I need the Carots not to all start with their full time_left. The game has to start with some Carots already ripe, some almost ripe, etc. But if I set a smaller wait_time, the animation looks like the Carot is not already ripe but instead grows faster. Therefore I would need to set the Timer.time_left property manually, but I can’t because it’s read-only.

So my questions are:

  • Why is Timer.time_left read-only ? It seems unnecessary
  • What would be the normal way in Godot to address my need ?

Here is my current code:

extends Area2D

@export var minWaitTime = 5.0
@export var maxWaitTime = 10.0

# images of the different stages of the carot

const texture1 = preload("res://assets/carot/stage-1.png")
const texture2 = preload("res://assets/carot/stage-2.png")
const texture3 = preload("res://assets/carot/stage-3.png")

# between 1 and 0, time LEFT when the carot changes state

const grow2 = 0.4
const grow3 = 0.0

# carot state

var state = 1

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	reset( true )

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
	# check if the timer is passed the time when the carot changes state, and if so changes state
	if ( $GrowTimer.time_left / $GrowTimer.wait_time <= grow3 ):
		state = 3
		$Sprite2D.texture = texture3
	elif ( $GrowTimer.time_left / $GrowTimer.wait_time <= grow2 ):
		state = 2
		$Sprite2D.texture = texture2

func reset( random_progress: bool = false ):
	var waitTime = minWaitTime + ( ( maxWaitTime - minWaitTime ) * randf_range( 0, 1 ) )
	print( waitTime )
	$GrowTimer.wait_time = waitTime
	$GrowTimer.start()
	$Sprite2D.texture = texture1
	# if random_progress is true is means that we want to set the timer to a random progress value.
	if ( random_progress ):
		pass
		# this is where I would like to set $GrowTimer.time_left

func _on_area_entered(area: Area2D) -> void:
	reset()

I am not sure what you mean by this, but you can normally change the wait time of the timer.

What I want to say is just set or randomize the wait time, by codes instead of changing the time left.

Thank you for your reply, that’s what I did initially, but it messes with the Carot animation.

Basically the animation checks in the loop at what percent the Carot’s Timer is (
time_left / wait_time ). If it’s above 30% it set a given texture, if it’s above 50% it sets the next texture, etc.

If I just set a smaller wait_time, the animation will start from the beginning, whereas what I want is the start the animation from the state of the timer (if it’s 50%, it’s going to be the right texture).

I worked around it by setting state and restarting the Timer every time a new state is reached, but it seems unnecessarily complex. Please let me know if there is a better way.

extends Area2D

signal picked

@export var minWaitTime = 5.0
@export var maxWaitTime = 10.0

# images of the different stages of the carot

const textures = [
	null,
	preload("res://assets/carot/stage-1.png"),
	preload("res://assets/carot/stage-2.png"),
	preload("res://assets/carot/stage-3.png")
]

# carot state

var state

# percents of the Timer value to wait to reach each new state

const grow1 = 0.2
const grow2 = 0.4
const grow3 = 0.4

func _ready() -> void:
	reset( true )


func _process( delta: float ) -> void:
	# check if the timer is passed the time when the carot changes state, and if so changes state
	if ( state == 0 && $GrowTimer.time_left / $GrowTimer.wait_time <= 1.0 - grow1 ):
		setState( 1 )
	if ( state == 1 && $GrowTimer.time_left / $GrowTimer.wait_time <= 1.0 - grow2 ):
		setState( 2 )
	if ( state == 2 && $GrowTimer.time_left / $GrowTimer.wait_time <= 1.0 - grow3 ):
		setState( 3 )


func reset( random_progress: bool = false ):
	# reset the timer with a new wait_time value, so that this Carot grows at a different pace.
	var waitTime = minWaitTime + ( ( maxWaitTime - minWaitTime ) * randf_range( 0, 1 ) )
	$GrowTimer.wait_time = waitTime
	$GrowTimer.start()
	if ( random_progress ):
		setState( randi_range( 0, 3 ) )
	else:
		setState( 0 )


func setState( newState ):
	state = newState
	$Sprite2D.texture = textures[ newState ]
	$GrowTimer.stop()
	$GrowTimer.start()

func _on_area_entered(area: Area2D) -> void:
	if ( state == 3 ):
		picked.emit()
	reset()

I am not sure what you mean by this

What I mean is you can’t manually set the property:

timer.time_left = 2.2
1 Like

Then just define a variable like this:

extends Area2D

signal picked

@export var minWaitTime = 5.0
@export var maxWaitTime = 10.0

# images of the different stages of the carot

const textures = [
	null,
	preload("res://assets/carot/stage-1.png"),
	preload("res://assets/carot/stage-2.png"),
	preload("res://assets/carot/stage-3.png")
]

# carot state

var state

# percents of the Timer value to wait to reach each new state

const grow1 = 0.2
const grow2 = 0.4
const grow3 = 0.4

var timer_wait_time = 0

func _ready() -> void:
	reset( true )


func _process( delta: float ) -> void:
	# check if the timer is passed the time when the carot changes state, and if so changes state
	if ( state == 0 && $GrowTimer.time_left / timer_wait_time <= 1.0 - grow1 ):
		setState( 1 )
	if ( state == 1 && $GrowTimer.time_left / timer_wait_time <= 1.0 - grow2 ):
		setState( 2 )
	if ( state == 2 && $GrowTimer.time_left / timer_wait_time <= 1.0 - grow3 ):
		setState( 3 )


func reset( random_progress: bool = false ):
	# reset the timer with a new wait_time value, so that this Carot grows at a different pace.
	var waitTime = minWaitTime + ( ( maxWaitTime - minWaitTime ) * randf_range( 0, 1 ))
    timer_wait_time = waitTime
	$GrowTimer.wait_time = 2.2 #time_left (change it)
	$GrowTimer.start()
	if ( random_progress ):
		setState( randi_range( 0, 3 ) )
	else:
		setState( 0 )


func setState( newState ):
	state = newState
	$Sprite2D.texture = textures[ newState ]
	$GrowTimer.stop()
	$GrowTimer.start()

func _on_area_entered(area: Area2D) -> void:
	if ( state == 3 ):
		picked.emit()
	reset()

Actually I am in mobile, as I am out of home. So my brain is not working properly :sweat_smile: but the codes should work. Let me know what happened?