Getting a base object of type 'null instance' when trying to create object persistence between scenes

Godot Version

4.6

Question

Hey everyone, I’m currently trying to implement a persistent data handler scene which is based on a Node 2D to keep collectables from re spawning when a player changes from scene to scene.

class_name PersistentDataHandler
extends Node2D

signal data_loaded()
var value : bool = false

func _ready() -> void:
	print(_get_name())
	get_value()
	pass

func set_value() -> void:
	SaveManager.add_persistent_value(_get_name())
	pass

func get_value() -> void:
	value = SaveManager.check_persistent_value(_get_name())
	data_loaded.emit (value)
	pass

func _get_name() -> String:
	return get_tree().current_scene.scene_file_path + "/" + get_parent(). name + "/" + name

it seems to work fine when I pick up collectables or change to the next scene, but it crashes once I try to go back to the scene where the collectables have been picked up, it gives me the following error.

Invalid access to property or key ‘scene_file_path’ on a base object of type ‘null instance’.

For reference, it refers to my global save manager, which I doubt is the main problem.

extends Node

const SAVE_PATH = "user://"

signal game_loaded
signal game_saved

var current_save : Dictionary={
	high_score_1= [],
	high_score_2= [],
	high_score_3= [],
	persistence= []
}


func save_game() -> void:
	update_high_scores()
	var file := FileAccess.open(SAVE_PATH + "save.sav", FileAccess.WRITE)
	var save_json = JSON.stringify(current_save)
	file.store_line ( save_json)
	game_saved.emit()
	pass

func load_game() -> void:
	var file := FileAccess.open(SAVE_PATH + "save.sav", FileAccess.READ)
	var json := JSON.new()
	json.parse(file.get_line())
	var save_dict : Dictionary = json.get_data() as Dictionary
	current_save = save_dict
	ScoreTracker.hs_1 = current_save.high_score_1
	pass

func update_high_scores() -> void:
	current_save.high_score_1 = ScoreTracker.hs_1
	
func add_persistent_value(value : String) -> void:
	if check_persistent_value(value) == false:
		current_save.persistence.append(value)
	pass

func check_persistent_value( value :String)-> bool:
	var check =current_save.persistence as Array
	return check.has(value)

I do however, feel like the problem might be located in the collectable area script that is plugged in to the persistent data handler, but I can’t figure out where/ how with my current understanding

extends Area2D

@export_subgroup("Nodes")
@export var collectable : Node2D
@export var collectable_animated : AnimatedSprite2D
@export var score_sprite:Sprite2D
@export var death_countdown : Timer
@export var animation :AnimationPlayer
@export var is_collected: PersistentDataHandler 
@export var bug : Node2D

@export_subgroup("Settings")
@export var points :int =100



func _ready() -> void:
	is_collected.data_loaded.connect(remember)
	score_sprite.visible= false


func _on_body_shape_entered(body_rid: RID, body: Node2D, body_shape_index: int, local_shape_index: int) -> void:
	if body.is_in_group("player"):
		self.monitoring=false
		self.monitorable =false
		collectable_animated.visible = false
		is_collected.set_value()
		ScoreTracker.score_1 += points
		animation.play("blinking")
		print("Picked!")
		death_countdown.start()
		#self.set_collision_layer_value(1,false)
		self.set_collision_mask_value(1,false)

func remember() ->void:
	var recall = is_collected.value
	if recall:
		self.queue_free()
		bug.visible = false
	else:
		pass

Any ideas are always helpful, and thank you for helping out.

This error means that during this call the current_scene is null.

Which usually means that you’re trying to access it right after the old scene has already been removed from the tree and the new scene has not yet been loaded.
Try to trace the call stack where exactly the initial call happens that generates the error and if that makes sense, try to delay it until the new scene is loaded, e.g. with the signal SceneTree::scene_changed.

2 Likes

Oh thanks, I think I figured out,here is the rundown for future reference. I used a global script for scene transitions that looked like this:

extends Node

#SceneSwitcher.switch_scene(scene_path)

var current_scene = null

func _rady()-> void:
	var root = get_tree().root
	current_scene = root.get_child(-1)
	print_debug(current_scene)
	
func switch_scene(res_path):
	current_scene = get_tree().current_scene
	_deferred_switch_scene.call_deferred(res_path)

func _deferred_switch_scene(res_path):
	current_scene.free()
	var s = ResourceLoader.load(res_path)
	current_scene = s.instantiate()
	get_tree().root.add_child(current_scene)
	get_tree().current_scene = current_scene

All I had to do was to change the

current_scene.free()

into

current_scene.queue_free()

it seems to be working for now, thank you so much for the tip, I would never have thought to check my scene transition script otherwise…

Why aren’t you using the built-in SceneTree::change_scene_to_packed() method?

Seems to me like you reinvented the wheel with your SceneSwitcher class.

1 Like