0 votes

Hi,

I'm trying to use visited checkpoints to load the correct level and place the player at the last visited checkpoint. For this I'm saving the key (level name) and value (checkpoint name) in a dictionary on config file.

I figure I could return the last Level name and last Checkpoint name to archieve this, but how? (back() should work with an array, but how to do this with a dictionary?)

Or is there a better way to archieve what I'm after?

Code below. Many thanks in advance for any suggestions.

Game (including dictionary) initiates level loader:

extends Node

export(PackedScene) var StartLevel := preload("res://src/Levels/Level1.tscn")

const SAVE_PATH = "res://config.cfg"

var level: Node2D = null
var config = ConfigFile.new()
var _savedata = {
    "SAVEDATA": {
        "VisitedCheckpoints": { }
    }
}

func _ready() -> void:
    load_checkpoints()
    LevelLoader.setup(self, $Player, StartLevel)
    Events.connect("checkpoint_visited", self, "_on_Events_checkpoint_visited")

Level loader:

extends Node

onready var scene_tree := get_tree()

var _game: Node = null
var _player: Player = null
var _level: Node2D = null

func setup(game: Node, player: Player, Level: PackedScene) -> void:
    _game = game
    _player = player
    trigger(Level)

func trigger(NewLevel: PackedScene, portal_name: String = "") -> void:
    _game.remove_child(_player)

    if _level:
        scene_tree.paused = true
        _level.queue_free() #poistaa levelin
        yield(_level, "tree_exited")

    _level = NewLevel.instance()

    var player_position_node: Node2D = (
        _level.get_node("Checkpoints").get_child(0)
        if portal_name.empty()
        else _level.get_node("Portals/%s" % portal_name))
    _player.global_position = player_position_node.global_position

    for checkpoint_name in _game._savedata["SAVEDATA"]["VisitedCheckpoints"].get(_level.name, []):
        var checkpoint: Area2D = _level.get_node("Checkpoints/%s" % checkpoint_name)
        checkpoint.is_visited = true

    _game.level = _level
    _game.add_child(_level)
    _game.add_child(_player)

    scene_tree.paused = false
asked Apr 9 in Engine by anssiko (43 points)

Never use "res://" for saving it wont work once you export your game

1 Answer

+2 votes

Never use "res://" for saving it wont work once you export your game , use "user://"
Use these function to save / load dictionary

func save_data(save_path : String, data) -> void:
    var data_string = JSON.print(data)
    var file = File.new()
    var json_error = validate_json(data_string)
    if json_error:
        print_debug("JSON IS NOT VALID FOR: " + data_string)
        print_debug("error: " + json_error)
        return
    file.open(save_path, file.WRITE)
    file.store_string(data_string)
    file.close()


func load_data(save_path : String):
    var file : File = File.new()
    if not file.file_exists(save_path):
        print_debug('file [%s] does not exist; creating' % save_path)
        save_data(save_path, {})
    file.open(save_path, file.READ)
    var json : String = file.get_as_text()
    var data = parse_json(json)
    file.close()

    return data

Load / save data

#save data
save_data(SAVE_PATH , _savedata)

#load data
_savedata = load_data(SAVE_PATH)
answered Apr 9 by supper_raptor (667 points)

Thank you for your suggestion. If I understand correctly, you're suggesting a better way of save/load (json), but my main problem still stands: how to return the last key and value from a dictionary?

data is a dictionary

I'm sorry, but I don't get your meaning.

i mean the variable data is godot dictionary
for example

# godot dictionary
var x = {
    name = "bla",
    hp = 100,
}

#to save it to "user://save.sav"

save_data("user://save.sav", x)

#to load data

var new_x = load_data("user://save.sav")

#you can access data like you do for dictionary

print(new_x.name) #will print "bla"

anssiko,

Godot dictionary keys are ordered, according to the documentation (which is not always true in other language).

Godot's JSON serialization / parsing seems to keep them too. But I would advise you to use an Array instead of storing your checkpoint as a Dictionary, as you may lose key order at some point. It could be an Array of Dictionary of course.

Accessing the last element of a Dictionary could be accomplished that way:

var my_last_k = new_dic.keys()[-1]
var last_v = new_dic[my_last_k]

You get all the key name's array, and pickup the last element.

You could also timestamp your checkpoint record, and use the timestamp as the key. And also store the extra value of the last timestamp as a checkpoint reference to fetch.

The JSON could looks like:

{
  "last_checkpoint": 1234325432,
  "checkpoints": {
    "1234325430": {
      "player_pos": "(123, 345)",
      "player_weapon": "BFG",
      "player_heath": "30%"
    },
    "1234325432": {
      "player_pos": "(678, 345)",
      "player_weapon": "BFG",
      "player_heath": "28%"
    }
  }
}

But for sure, ensure that you don't rely on Dictonary ordered keys nor on JSON keeping Dictionary order too.

Hope that helps.
Sylvain.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.