Sorry if I ask a lot of questions, I can't access ones from the old forum, so answers are hard to come by for simple things.

I have a node2D called SaveData which is supposed to save everything. It worked for a while, and then it decides to just return null on what feels like everything. I've been trying to tackle save/load systems for a long time.

Note: I don't like the JSON method.



  • Actually, I made a mistake there. Try this instead.

    extends Node2D
    
    
    # I haven't mastered arrays and dictionaries yet. My code sort of does the same thing still after rearranging the variables.
    
    
    const FILE_NAME = "user://save_file.file"
    
    var file
    var air: float
    var bullets: int
    var tanks: int
    var lives: int
    var health: float
    var player: int
    var level
    
    var var_names := {
    	player = 1,  # player must come first.
    	air = 1,
    	bullets = 10,
    	tanks = 1,
    	lives = 0,
    	health = 1,
    	level = 1,
    	}
    
    func save_data():
    	file = File.new()
    	file.open(FILE_NAME, File.WRITE)
    	for n in var_names.keys():
    		file.store_var(get(n))
    	file.close()
    
    
    func load_data():
    	file = File.new()
    	if file.file_exists(FILE_NAME):
    		file.open(FILE_NAME, File.READ)
    		for n in var_names.keys():
    			set(n, file.get_var())
    		file.close()
    	else:
    		for n in var_names.keys():
    			set(n, var_names[n])
    		save_data()
    
    func start_game():
    	save_data()
    #	get_tree().change_scene("res://Maze" + str(level + 1) +".tscn")
    	print('change maze %d ' % [level + 1])
    	load_data()
    
    
    func _ready():
    	load_data()
    
    
    func reset():
    	# reset the variables, then load player over them.
    	for n in var_names.keys():
    		set(n, var_names[n])
    
    	file = File.new()
    	file.open(FILE_NAME, File.READ)
    	player = file.get_var()  # The first variable will be player.
    	file.close()
    
    	save_data()
    #	get_tree().change_scene("res://Maze" + str(level) +".tscn")
    	print('change maze %d' % [level])
    
    
    func print_data():
    	for n in var_names.keys():
    		print('%s: %s' % [n, str(get(n))])

I would add an error check for file.open. That may not be the issue, but I think it's good practice.

I believe you have to write and read the data in the same order.

Apparently, ensuring everything is read/written correctly, it still doesn't work.

And I have no idea, but are these errors caused by this issue? It looks like they could be.

    Not sure, but it would be more helpful if you posted text on the forum (code or error messages). Screenshots are hard to read, and can't be searched for. You can post code by typing 3 tildes "~~~" on a line by itself, directly above and directly below the pasted code or error message.

    Nerdzmasterz are these errors caused by this issue?

    Wild guess: Maybe the type of a variable is changing? I don't trust code that doesn't use static typing.

    Makes sense- 0 likes to become null. I'll look at it when I get a chance.

    Honestly, if you are just saving integers (or strings or basic data) then JSON makes a lot more sense.

    There are pros and cons to using JSON. I guess I'll just try to explain here. JSON will work, but there are a few things to consider.

    1: Dictionaries take up twice as much space as an int, string, etc.

    2: JSON is easier to hack. It was a note on a Udemy course, so I don't think I'm allowed to quote here, however, this may be the reason they mentioned below.

    3: According to https://kidscancode.org/godot_recipes/basics/file_io/:

    While Godot has JSON support, saving game data is not what JSON is for. JSON is a data interchange format - its purpose is to allow systems using different data formats and/or languages to exchange data between each other.

    This means JSON has limitations that are negatives for you when it comes to saving your game data.

    Does that mean it is bad? Not exactly, it depends on what you want to do with it. For some, it could be perfectly fine.

    I made some progress, maybe? I switched the code so the needed variables are static typed, ie var bullets: int. However, it still isn't saving things. I tried to move to the next scene, and I get this error for all my animations:

    E 0:00:06.003 set_animation: There is no animation with name 'player_0_idle_down'.
    <C++ Error> Condition "frames->get_animation_names().find(p_animation) == -1" is true.
    <C++ Source> scene/2d/animated_sprite.cpp:630 @ set_animation()
    <Stack Trace> AnimatedSprite.gd:33 @ get_input()
    AnimatedSprite.gd:10 @ _process()

    I was trying to use player 3. Additionally, the game doesn't even open anymore unless I delete the save file.

    Look at the example from the docs. It appears you should create a new File object for each operation and importantly close it as soon as you are done (in that same function right below the save/load operations). What it looks like you are doing is opening files that are already open, or may be null (because they exited the function) which is likely why your game is crashing. See here for the example.

    func save(content):
        var file = File.new()
        file.open("user://save_game.dat", File.WRITE)
        file.store_string(content)
        file.close()
    
    func load():
        var file = File.new()
        file.open("user://save_game.dat", File.READ)
        var content = file.get_as_text()
        file.close()
        return content

    Everything is null again, and it still crashes. Sorry, I'm terribly lost.

    `extends Node2D

    var file
    var air: float
    var bullets: int
    var tanks: int
    var lives: int
    var health: float
    var player: int

    var level

    func save_data():
    file = File.new()
    file.open("user://save_file.file", File.WRITE)
    file.store_var(level)
    file.store_var(air)
    file.store_var(health)
    file.store_var(player)
    file.store_var(tanks)
    file.store_var(lives)

    func load_data():
    file = File.new()
    if file.file_exists("user://save_file.file"):
    file.open("user://save_file.file", File.READ)
    level = file.get_var()
    air = file.get_var()
    bullets = file.get_var()
    tanks = file.get_var()
    lives = file.get_var()
    health = file.get_var()
    player = file.get_var()

    	file.close()
    else:
    	level = 1
    	air = 1
    	bullets = 10
    	tanks = 1
    	lives = 0
    	health = 1
    	player = 1
    	save_data()
    	file.close()

    func start_game():
    save_data()
    get_tree().change_scene("res://Maze" + str(level + 1) +".tscn")
    load_data()

    func _ready():
    file = File.new()
    file.open("user://save_file.file", File.READ)
    load_data()

    func reset():
    file = File.new()
    file.open("user://save_file.file", File.READ_WRITE)
    file.store_var(player)
    level = 1
    air = 1
    bullets = 10
    tanks = 1
    lives = 0
    health = 1
    player = file.get_var()
    save_data()
    get_tree().change_scene("res://Maze" + str(level) +".tscn")
    file.close()
    `

    And it appears I don't know how to format code on this, either. 🫢

    You're opening the file twice, though I don't know that it would change anything.

    I always thought file = File.new() made a brand new file, thus deleting the last file with that name. Maybe I'm wrong? If I'm right, though, then this means it deletes the file before even opening it.

    I would expect file = File.new() to create a new File object and place a reference to it in "file". I don't what happens to the old File object or the state of the disk file associated with it.

    You're not checking the result of file.open() for a possible error.

    Actually, I did, but it makes no sense. All I got was a print function to work if I got an error, which I did, but the only errors I found were the ones I printed above.

    Have you looked at the file outside of Godot to see if it has the right contents?

    And with a problem like this, I would use more print statements, or use the debugger, to narrow down the problem.

    Are you speaking of the logs you can pop open through project settings? And yeah, I'll have to keep digging. Something is weird here.

    No, I was referring to printing out more variables, or looking at them with the debugger, so you can determine the point at which something first goes wrong. For example:

    level = file.get_var()
    print_debug("level=", level)

    You can also set a debugger breakpoint at that statement, and then step though the code and look at variables, which avoids having to add print statements.

    You can also look at the file user://save_file.file outside of Godot using an editor or file dump utility. I don't remember which O/S you're using. In Linux, you can use mc or hexdump to examine files with binary data.

    You're still saving and loading the vars in a random order. I believe it has to be the same order.

    save(player)
    save(health)
    save(level)
    ...
    load(player)
    load(health)
    load(level)

    Do you understand what I mean?

    Yes. I sort of fried my brain, though. I'll have to do this once my head is no longer loaded with jello.

    Here's a small sample that works for me. Maybe it will help?

    # save_load.gd
    
    extends Reference
    class_name SaveLoad
    
    
    const SAVE_FILE = 'user://save_file.bin'
    
    var int_01:int = 1
    var int_02:int = 2
    var int_03:int = 3
    
    var var_names := [
    	'int_01',
    	'int_02',
    	'int_03',
    	]
    
    func save():
    	var f = File.new()
    	f.open(SAVE_FILE, File.WRITE)
    	for n in var_names:
    		f.store_var(get(n))
    	f.close()
    
    
    func load():
    	var f = File.new()
    	f.open(SAVE_FILE, File.READ)
    	for n in var_names:
    		set(n, f.get_var())
    	f.close()
    
    	for n in var_names:
    		print('I return: %s, %d' % [n, get(n)])
    # main.gd
    func _ready():
    	var sl = SaveLoad.new()
    	sl.int_01 = 99
    	sl.int_02 = 10023
    	sl.int_03 = -1
    	sl.save()
    	sl.load()
    # I haven't mastered arrays and dictionaries yet. My code sort of does the same thing still after rearranging the variables.
    
    extends Node2D
    
    var file
    var air: float
    var bullets: int
    var tanks: int
    var lives: int
    var health: float
    var player: int
    
    var level
    
    func save_data():
    	file = File.new()
    	file.open("user://save_file.file", File.WRITE)
    	file.store_var(air)
    	file.store_var(bullets)
    	file.store_var(tanks)
    	file.store_var(lives)
    	file.store_var(health)
    	file.store_var(player)
    	file.store_var(level)
    	
    	
    func load_data():
    	file = File.new()
    	if file.file_exists("user://save_file.file"):
    		file.open("user://save_file.file", File.READ)
    		air = file.get_var()
    		bullets = file.get_var()
    		tanks = file.get_var()
    		lives = file.get_var()
    		health = file.get_var()
    		player = file.get_var()
    		level = file.get_var()
    
    		file.close()
    	else:
    		air = 1
    		bullets = 10
    		tanks = 1
    		lives = 0
    		health = 1
    		player = 1
    		level = 1
    		save_data()
    		file.close()
    
    func start_game():
    	save_data()
    	get_tree().change_scene("res://Maze" + str(level + 1) +".tscn")
    	load_data()
    
    
    func _ready():
    	file = File.new()
    	file.open("user://save_file.file", File.READ)
    	load_data()
    
    
    func reset():
    	file = File.new()
    	file.open("user://save_file.file", File.READ_WRITE)
    	air = 1
    	bullets = 10
    	tanks = 1
    	lives = 0
    	health = 1
    	player = file.get_var()
    	level = 1
    	save_data()
    	get_tree().change_scene("res://Maze" + str(level) +".tscn")
    	file.close()

    Hi, did you already had a look here?

    This is good to know. However, if I switch to extends Resource, then I cannot access get_tree(), which I need to change scenes and save the new scene as current.

    If you are only saving integers, then you should use store_64(). I think store_var() is for classes.

    file.store_64(air)
    file.get_64(air)

    64() is a double- but it would still work. It will simply take up more space. Int size is 32. 🙂

    I switched all the get_var() to get_64. I can now open the game without constantly restarting, but my character is never reset- and it goes to player_0 by default, which does not exist. (The player characters start at 1).

    Thanks for the help so far, tho. While it hasn't fixed it yet, I do appreciate the efforts as I am terribly lost.

    This seems to work fine for me (based on your earlier post). Of course, I can't speak to any issues with scenes.

    extends Node2D
    
    
    # I haven't mastered arrays and dictionaries yet. My code sort of does the same thing still after rearranging the variables.
    
    
    var file
    var air: float
    var bullets: int
    var tanks: int
    var lives: int
    var health: float
    var player: int
    var level
    
    var var_names := {
    	player = 1,  # player must come first.
    	air = 1,
    	bullets = 10,
    	tanks = 1,
    	lives = 0,
    	health = 1,
    	level = 1,
    	}
    
    func save_data():
    	file = File.new()
    	file.open("user://save_file.file", File.WRITE)
    	for n in var_names.keys():
    		file.store_var(get(n))
    	file.close()
    
    
    func load_data():
    	file = File.new()
    	if file.file_exists("user://save_file.file"):
    		file.open("user://save_file.file", File.READ)
    		for n in var_names.keys():
    			set(n, file.get_var())
    		file.close()
    	else:
    		for n in var_names.keys():
    			set(n, var_names[n])
    		save_data()
    
    func start_game():
    	save_data()
    #	get_tree().change_scene("res://Maze" + str(level + 1) +".tscn")
    	print('change maze %d ' % [level + 1])
    	load_data()
    
    
    func _ready():
    	load_data()
    
    
    func reset():
    	file = File.new()
    	file.open("user://save_file.file", File.READ)
    	player = file.get_var()  # The first variable will be player.
    	file.close()
    
    	for n in var_names.keys():
    		set(n, var_names[n])
    	save_data()
    #	get_tree().change_scene("res://Maze" + str(level) +".tscn")
    	print('change maze %d' % [level])
    
    
    func print_data():
    	for n in var_names.keys():
    		print('%s: %s' % [n, str(get(n))])

      As Michael Corleone said to Moe Greene, "you're unlucky."

      duane

      It's not the same code. I made that based on your code.

      Actually, I made a mistake there. Try this instead.

      extends Node2D
      
      
      # I haven't mastered arrays and dictionaries yet. My code sort of does the same thing still after rearranging the variables.
      
      
      const FILE_NAME = "user://save_file.file"
      
      var file
      var air: float
      var bullets: int
      var tanks: int
      var lives: int
      var health: float
      var player: int
      var level
      
      var var_names := {
      	player = 1,  # player must come first.
      	air = 1,
      	bullets = 10,
      	tanks = 1,
      	lives = 0,
      	health = 1,
      	level = 1,
      	}
      
      func save_data():
      	file = File.new()
      	file.open(FILE_NAME, File.WRITE)
      	for n in var_names.keys():
      		file.store_var(get(n))
      	file.close()
      
      
      func load_data():
      	file = File.new()
      	if file.file_exists(FILE_NAME):
      		file.open(FILE_NAME, File.READ)
      		for n in var_names.keys():
      			set(n, file.get_var())
      		file.close()
      	else:
      		for n in var_names.keys():
      			set(n, var_names[n])
      		save_data()
      
      func start_game():
      	save_data()
      #	get_tree().change_scene("res://Maze" + str(level + 1) +".tscn")
      	print('change maze %d ' % [level + 1])
      	load_data()
      
      
      func _ready():
      	load_data()
      
      
      func reset():
      	# reset the variables, then load player over them.
      	for n in var_names.keys():
      		set(n, var_names[n])
      
      	file = File.new()
      	file.open(FILE_NAME, File.READ)
      	player = file.get_var()  # The first variable will be player.
      	file.close()
      
      	save_data()
      #	get_tree().change_scene("res://Maze" + str(level) +".tscn")
      	print('change maze %d' % [level])
      
      
      func print_data():
      	for n in var_names.keys():
      		print('%s: %s' % [n, str(get(n))])

      Oh my gosh, you saved me there. I'm going to study this code. Thank you!

      Very quick, though. Suppose I need to change a variable, or add one? Ie, I want to add a max_bullets variable to represent the amount of bullets the player had by the time they started the new level. At this stage, all the enemies will respawn and not the ammunition. Or, I want to add a keys variable to show the player they have a way to unlock a door?

        Nerdzmasterz

        + var max_bullets:int
        
        var var_names := {
        	player = 1,  # player must come first.
        	air = 1,
        	bullets = 10,
        	tanks = 1,
        	lives = 0,
        	health = 1,
        	level = 1,
        +	max_bullets = 1000,
        	}

        I'm going to have to change the section on saving in my tut now. It took me a bit, but I found a way to only save data at given times-which is a little different than what we see here. This method is useful to avoid continuing a level with extra/missing keys and bullets, damage dealt, oxygen missing in your air tank, and whatever from the last time you played that level. It should only save after you go to a new level, so you can restart each time you try that level again.

        Again, thank you so much for the help!