• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

GML Saving ds_map within ds_map

A

Aaron Walton

Guest
I'm making a metroidvania-style game with upgrades items that delete themselves if you've already picked them up. I have a ds_map (global.powerUpIndex) which, each time you pick up one of these items, adds a boolean "true" entry to powerUpIndex. So the power up objects (which each have a unique "index" variable) check for their index in the powerUpIndex ds_map, and if the index equals true, the object deletes itself. This currently works fine within the game, but I can't figure out how to save and load the powerUpIndex map with other global variables. My save system is based on Shaun Spalding's "Better Saving and Loading (JSON)" tutorial, and I admit I'm still a bit hazy on using data structures myself.

My save and load scripts look like this:



GML:
///@desc save game
function saveGame() {


    //Create a root list

    var _root_list = ds_list_create();

    //for every instance, create a map
    with (oPlayer)
    {
        var _map = ds_map_create();
        ds_list_add(_root_list,_map);
        ds_list_mark_as_map(_root_list,ds_list_size(_root_list)-1);
    
        var _obj = object_get_name(object_index);
        ds_map_add(_map, "obj", _obj);
        ds_map_add(_map, "y", y);
        ds_map_add(_map, "x", x);
        ds_map_add(_map, "layer", layer);
        ds_map_add(_map, "room", room);
    

    }

        //Global variables
        ds_map_add(_map, "playerMaxHP", global.playerMaxHP);
        ds_map_add(_map, "playerHP", global.playerHP);
        ds_map_add(_map, "jumpMax", global.jumpMax);
        ds_map_add(_map, "hasJetpack", global.hasJetpack);
        ds_map_add(_map, "powerUpIndex", global.powerUpIndex);
       ds_list_mark_as_map(_map, ds_list_size(_map)-1);


    //wrap the root LIST up in a MAP
    var _wrapper = ds_map_create();
    ds_map_add_list(_wrapper, "ROOT", _root_list);

    //save all of this to a string
    var _string = json_encode(_wrapper);
    SaveStringToFile(global.saveSlot, _string);


    //Delete the data
    ds_map_destroy(_wrapper); //destroy all lists and maps

    show_debug_message("Game saved!");


}
GML:
///@desc load game
///@arg saveSlot
function loadGame() {

    global.saveSlot = argument[0];

    with (oPlayer) instance_destroy();

    if (file_exists(argument[0]))
    {
        var _wrapper = LoadJsonFromFile(global.saveSlot);
        var _list = _wrapper[? "ROOT"];
    
        for (var i = 0; i < ds_list_size(_list); i++)
        {
            var _map = _list[|i];
        
            var _obj = _map[? "obj"];
            with (instance_create_layer(0,0,layer,asset_get_index(_obj)))
            {
                x = _map[? "x"];
                y = _map[? "y"];
                room = _map[? "room"];
                layer = _map [? "layer"];           
            }
        
        
        }
        global.playerMaxHP = _map[?"playerMaxHP"];
        global.playerHP = _map[?"playerHP"];
        global.jumpMax = _map[?"jumpMax"];
        global.hasJetpack = _map[?"hasJetpack"];
        global.powerUpIndex = _map[?"powerUpIndex"];

    
        ds_map_destroy(_wrapper);
        show_debug_message("Game loaded");
    }



}
The save/load system works totally fine except for the powerUpIndex. I'm unsure if the problem lies in the save script, load script, or both. I'm guessing I probably need to tell Game Maker that this global variable is a map, but I'm not sure how. I tried using ds_list_mark_as_map (this is in the code above), but it didn't seem to have any effect. I could definitely be using it incorrectly though.

The only other thing I should mention: I could have *sworn* this was working properly before the 2.3 update. However, looking through their post regarding changes (https://help.yoyogames.com/hc/en-us/articles/360011980018), I was unable to find anything that would effect it. Probably I'm just misremembering and it never worked at all, but I figured I should mention this just in case.
 

FrostyCat

Redemption Seeker
What you had there should not have worked even before GMS 2.3. You saved only the map ID number, which is not reusable across sessions. If it ever worked, it's a fluke.

When you save, make a copy of that map and add it with the mark:
GML:
var powerUpIndex_copy = ds_map_create();
ds_map_copy(powerUpIndex_copy, global.powerUpIndex);
ds_map_add_map(_map, "powerUpIndex", powerUpIndex_copy);
When you load, copy it out the other way:
GML:
ds_map_copy(global.powerUpIndex, _map[?"powerUpIndex"]);
 

Nidoking

Member
I think the problem is that you were using ds_list_mark_as_map on a map. ds_list_mark_as_map is for lists. Note the word "list" in there, when you are not using a list at all. I believe there was a "ds_map_mark_as_map" function in 2.2 that marks a map entry as a map, which is what you want.
 
A

Aaron Walton

Guest
What you had there should not have worked even before GMS 2.3. You saved only the map ID number, which is not reusable across sessions. If it ever worked, it's a fluke.

When you save, make a copy of that map and add it with the mark:
GML:
var powerUpIndex_copy = ds_map_create();
ds_map_copy(powerUpIndex_copy, global.powerUpIndex);
ds_map_add_map(_map, "powerUpIndex", powerUpIndex_copy);
When you load, copy it out the other way:
GML:
ds_map_copy(global.powerUpIndex, _map[?"powerUpIndex"]);
Thanks for the help! However, the issue persists. It seems to be saving and loading the map correctly now, which I can tell by adding this to my GUI: draw_text(50, 130, json_encode(global.powerUpIndex));
But for some reason the objects still don't destroy themselves when loading the game. So the powerUpIndex map works fine within the game, and upon leaving and re-entering the room the items do not respawn. But for some reason they don't respond correctly to the powerUpIndex when it's loaded, even though I can tell it is loading correctly. Would you take a look at this code and see if you can see a reason that would be? This is the code for one of my upgrade items.

GML:
//Collision with player event

global.playerMaxHP += 5;
global.playerHP = global.playerMaxHP;
ds_map_add(global.powerUpIndex, index, true); //Adds this power up to index ds_map

instance_destroy();
GML:
//Step event

var _index = index; //index variable is set in variable definitions, with a unique number for each upgrade item instance

if global.powerUpIndex[?_index] == true
    instance_destroy(); //If the power up with this index exists in the DS_map (IE, has been picked up by player), deletes instance
Nidoking: good point about that being for lists rather than maps; should have realized that. Unfortunately it seems like ds_map_mark_as_map is not a function that exists, at least not anymore.
 

Nidoking

Member
Why are you using a map if you have a unique index for each instance? Couldn't you just use sequential (or nearly sequential) numbers and an array? It might require a bit more work to copy to and from the JSON, though.

As for why it doesn't trigger the condition, "== true" is never meaningful, and since the booleans are almost certainly encoded as decimals, it's probably throwing off the floating point comparison. Just check if global.powerUpIndex[?_index] or even drop the useless local variable and do global.powerUpIndex[?index]
 
A

Aaron Walton

Guest
I figured since the player may not pick up the upgrades in a specific order, what with it being a metroid-style map layout, a sequential thing wouldn't be the best way. And, as you say, a map is easier to make work with the JSON.

I just tried doing "global.powerUpIndex[?index]" like you said; still no change. That does work within one game, like it did before, but doesn't work when I load the game.

I honestly cannot even remember why I used that local variable... Game making is still very much a side thing so unfortunately tend to take more breaks in between programming days than I ought to so I sometimes forget my reasoning behind some things!
 

Nidoking

Member
I figured since the player may not pick up the upgrades in a specific order, what with it being a metroid-style map layout, a sequential thing wouldn't be the best way.
I don't think you quite understand what sequential means. Your indices would be 0, 1, 2, 3, etc. Those would be the indices into the array as well. The array size is equal to the number of items you have, regardless of how many there are. The indices correspond to items. You should know that. You assigned them. The order is no more relevant to the array than it is to the map. It's just that instead of the overhead of a map, it's an array. A map with sequential indices is functionally no different from an array whatsoever.

Anyway, sounds like you need to do some debugging. Is that step event running? If so, what is the value you pull out of the map for an instance that should be destroyed?
 

curato

Member
just to me I like descriptive things. If I had power ups like sword shield etc. I would would add an entry for each item and make it true then just check for the item instead of having a list inside another structure. would just be more more readable and straight forward to me. otherwise, every power up should be able to look at that ds_map and see the true and delete itself.
 

Nidoking

Member
Well, sure, and you probably don't see a problem making fifty identical items that are "health_upgrade_01" through "health_upgrade_50", but it's much cleaner to have one health upgrade item and give it a unique ID each time you make a distinct one.
 
A

Aaron Walton

Guest
I don't think you quite understand what sequential means. Your indices would be 0, 1, 2, 3, etc. Those would be the indices into the array as well. The array size is equal to the number of items you have, regardless of how many there are. The indices correspond to items. You should know that. You assigned them. The order is no more relevant to the array than it is to the map. It's just that instead of the overhead of a map, it's an array. A map with sequential indices is functionally no different from an array whatsoever.

Anyway, sounds like you need to do some debugging. Is that step event running? If so, what is the value you pull out of the map for an instance that should be destroyed?
Ah, okay. But if the array is functionally identical to the map, why use the array instead if the map should (theoretically) be easier to save as one variable rather than dealing with saving the array to the JSON?

just to me I like descriptive things. If I had power ups like sword shield etc. I would would add an entry for each item and make it true then just check for the item instead of having a list inside another structure. would just be more more readable and straight forward to me. otherwise, every power up should be able to look at that ds_map and see the true and delete itself.
If all my upgrades were single upgrades like (to use Metroid as an example), morph ball, bombs, super rockets, etc, then that would be fine. The item could just check the global variable for "hasMorphBall", "hasBombs", etc, and that would be fine. But since I'll also have identical upgrade items like multiple health upgrades, jump power upgrades, that sort of thing, I don't want to have a separate object for each of them.
 
Top