For some unknown reason gdscript suddenly decided that for certain objects, every variable that does not have its type explicitly declared becomes nil, despite having its value provided via assignment. See this:
var max_health : float = 2.0 # works correctly
var flash_duration = 0.1 # does not work, value is always "nil" and causes script crashes due to type mismatch
I am unable to find out why this happens. The only thing that comes to mind is moving from GD 4.5.1 to 4.6, but I have reopened the last 4.5.1 backup and the problem occurs there too. The bottom declaration worked properly up until now. Nothing else possibly comes to mind but I’ll provide any additional info as I remember / know it.
My guess is it is based on the load order of things. Potentially you are trying to access the variable before it is loaded. It could be a race condition that always existed and 4.6 just exacerbates it.
I’d recommend going into Project → Project Settings and turning Debug → GDScript → Untyped Declarations to Warn. Then you can fix everything with implicit assignment. It’s a good practice.
If you want to track down why it’s happening, post more code like @gertkeno said.
extends Area2D
var minspeed : int = 75
var text_red = preload("res://world1/gemwing_red.png")
var max_health = 2.0
var flash_duration = 0.1
var current_health
var shot_the_bullit = false
var is_alive = true
var cycle_distance = 1565.29 / 7.0
var move_target = 0.0
var cycle_target
var flash_CD = 0.0
@export var bullet : PackedScene
var min_shot_delay = 2.0
var max_shot_delay = 7.0
var _core_node
var _map_node
@onready var _follow : PathFollow2D = get_parent()
# Called when the node enters the scene tree for the first time.
func _ready():
current_health = max_health
cycle_target = cycle_distance * 0.75
$AnimationPlayer.set_speed_scale(1.2)
_core_node = NodeTracker.GetCoreNode()
_map_node = NodeTracker.GetStageNode()
bullet_tosser()
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
if flash_CD > 0.0:
flash_CD -= delta
if flash_CD <= 0.0:
$Sprite2D.material.set_shader_parameter("active", false)
if is_alive:
_follow.set_progress(_follow.get_progress()+(delta*minspeed))
if _follow.get_progress_ratio() >= 1.0:
queue_free()
func hit_by_player_bullet(damage):
current_health -= damage
if current_health <= 0:
_die()
else:
$HitAudio2D.play()
$Sprite2D.material.set_shader_parameter("active", true)
flash_CD = flash_duration
func _die():
is_alive = false
remove_from_group("enemies")
MiscFunctions.score_update.emit(50, 0.6)
$CollisionPolygon2D.set_deferred("disabled", true)
$AnimationPlayer.play("kabloom")
func _discard():
owner.delete_along_children()
func _on_body_entered(body):
if body.is_in_group("player"):
body.call_deferred("hit_by_enemy")
func set_angry_mode():
min_shot_delay = 1.0
max_shot_delay = 3.0
$Sprite2D.set_texture(text_red)
max_health = 3.0
current_health = 3.0
func _on_visible_on_screen_notifier_2d_screen_exited():
owner.delete_along_children()
func bullet_tosser():
while is_alive:
var shot_delay=RngProvider.RNG_RangeF(min_shot_delay, max_shot_delay)
await get_tree().create_timer(shot_delay).timeout
if is_alive:
var ply = NodeTracker.GetPlayerNode()
var direction_vector = self.global_position.direction_to(ply.position)
direction_vector = direction_vector.normalized()
var bullit = bullet.instantiate()
_map_node.add_child(bullit)
bullit.position = self.global_position
bullit.rotation = direction_vector.angle()
$ShootAudio2D.play()
Interestingly, not all variables are null value, only these:
max_health
min_shot_delay
max_shot_delay
flash_duration
cycle_distance, move_target, and flash_cd are all initialized properly.
As you can see, this is not the case. I get a crash when RNG is called and given two null values.
Of course, I can go over every script and make the explicit type declaration, and I guess I’ll end up doing it not matter waht, but obviously it’s extra work for something that worked just fine until today.
This might be part of it, use a Timer node instead. Keeping while loops alive through coroutines is a bad habit and leads to plenty of use-after-free issues.
It’s not “in” the RNG line. The RNG line is merely the earliest line that touches either of the four variables mentioned. I explicitly cast min_shot_delay and max_shot_delay at declaration, and the crash instead occurs when max_health or flash_duration are processed, see below.
Invalid type in function ‘randf_range’ in base ‘RandomNumberGenerator’. Cannot convert argument 1 from Nil to float.
When bullet_tosser() is fixed by explicit casting the variables passed to RNG, the crash happens in hit_by_player_bullet(), at current_health -= damage, with message:
Invalid operands ‘Nil’ and ‘float’ in operator ‘-’.
For the record, issue is solved, godot editor itself was the culprit.
All the nulled variables were initially @export. What I was doing just before upgrading was removing @export and simply inserting proper final values.
Today, trying to nail the problem down, i added @export to one of the vars, ran the project, then removed it and ran it again. Upon this removal, the variables are now initialized properly. As far as I know something went pear shaped with godot cache.