Saving the player's progress

Polish in games is not always about the visual embellishments. Sometimes it's also about adding smaller features that aren't immediately noticeable but can drastically improve the overall experience. Currently the game looks good and plays well, but if we close down the browser and return to play at a later time, we will need to start all over again. Players these days expect that they can come back to a game and continue from where they left off. In order to do this, we need to save the player's progress.

Understanding local storage

Whenever a game needs to save data the only viable option is to write the data to a file outside of the game itself. This poses a potential problem for web-based games as any file that needs to be downloaded will require the user to explicitly allow it. This would mean the player would know the name of the file and where it is located, which in turn would mean they could easily hack their own saved file. To get around this hurdle, HTML5 offers a solution known as local storage.

Local storage allows web pages, or in our case a game embedded into a web page, to save data within the browser itself. This is similar to internet cookies but with the benefit of being faster, more secure, and potentially able to store a lot more information. Since this data is saved in the browser, the user is not notified of the file being created or accessed; they cannot see the data easily and it is only accessible from the domain that creates it. This makes it perfect for saving our game data.

Note

There are only two ways to clear the saved data. Overwrite the data or clear your browser's cache. It is recommended that you always test games in a private browser mode to guarantee the save system is working properly.

Writing to local storage

For this game, we are going to save all the relevant data related to the levels unlocked, the amount of cash accrued, and the equipment purchased. To save the game data we are going to need to write to a file. GameMaker: Studio has two file formats that it can handle for HTML5 games: Text files and Ini files . Text files are useful for reading or writing large amounts of data and can be structured in any way you choose. Ini files are intended for smaller amounts of data and use a section/key/value structure. This structure breaks the data into separate sections and in each section there will be key/value pairs that look something like this:

[section]
key = value
[playerData]
playerFirstName = Jason
playerLastName = Elliott

Local storage requires all data to be in key/value pairs so that we will be using the Ini file system. It is possible to use the text file system, but for the little amount of data we need to save and the amount of extra coding it would take, it's not very beneficial:

  1. The first thing any save system needs to do is to create a save file with the appropriate structure and settings desired. Create a new Script, scr_GameSave, and write the following code:
    theFile = argument0;
    ini_open(theFile);
    ini_write_real("Score","Cash", score);
    for (i = 0; i < totalLevels; i++)
    {
        ini_write_string("Levels", string("Level_" + i), level[i, 1]);
    }
    for ( j = 0; j < ds_grid_width(equip); j++ )
    {
        ini_write_real("Equipment",string("Equip_" + j), ds_grid_get(equip, j, AMOUNT));
    }
    ini_close(); 

    When we execute this script we will require the name of the file to be passed as an argument. We can then open the requested file, or if one is not found it will be created to open. Once the file is open we can write all of the necessary data. We start by writing to a Score section, with a key called Cash to set the value of score. We run a loop using the level array and in a Levels section we store each level and whether it has been unlocked or not. Next we run another loop, this time going through the equipment grid and writing how many of each item the player currently has in the game. Finally, after all the data has been written we close the file.

  2. Saving a game is only useful if we actually load the data into it. Create a new Script, scr_GameLoad, so we can read from the file.
    theFile = argument0;
    if (!file_exists(theFile)) 
    {
        scr_GameSave(theFile); 
    } else {
        ini_open(theFile);
        score = ini_read_real("Score","Cash", "");
        for (i = 0; i < totalLevels; i++)
        {
            level[i, 1] = ini_read_string("Levels", string("Level_" + i), "");
        }
        for ( j = 0; j < ds_grid_width(equip); j++ )
        {
            ds_grid_set(equip, j, AMOUNT, ini_read_real("Equipment",string("Equip_" + j), ""));
            if (ds_list_find_index(inventory, j) == -1 && ds_grid_get(equip, j, AMOUNT) > 0)
            {
                ds_list_add(inventory, j);
            }
        }   
        ini_close();
    }

    We start by checking whether the file, as passed through the argument, exists. If the file is not found in local storage, such as the first time the game is run, we run the save script to initialize the values. If the file is found we open the save file and read the data into the game just as we saved it. We set the score, unlock the appropriate levels, and load the equipment. We also run through the inventory to ensure that all the equipment is available to the player.

  3. We will want to load any game data at the start of the game. Open scr_Global_GameStart and add the following code at the end of the script:
    globalvar saveFile;
    saveFile = "Default.ini";
    scr_GameLoad(saveFile);

    We create a global variable for the filename so we can save our data easily later. We then pass the string to the load script. This code must be at the end of the script because we need the default values for our grids and arrays to have been initialized first.

  4. The most logical place to start saving the game is after the player has completed each level. Open scr_ScoreCleanUp and just before the final brace, insert a call to scr_GameSave. The entire script is seen below:
    with (obj_Menu)
    {
        ds_grid_copy(equip, startEquip);
        ds_grid_destroy(startEquip);
        score += tempScore - tempCost;
        for ( i = 0; i < ds_grid_width(equip); i++)
        {
            e = ds_grid_get(equip, i, AMOUNT);
            
            if (e == 0) 
            {
                inv = ds_list_find_index(inventory, i);
                ds_list_delete(inventory, inv);
            }
        }
        scr_GameSave(saveFile);
    }
  5. We also need to save the game when the player purchases Equipment in the Shop. Open scr_Button_Buy_MousePressed and insert the call to scr_GameSave just before the final brace.
  6. Save the game and play the first few levels. After you have completed a few levels, refresh the browser. You should see that all your cash, equipment, and unlocked levels remain the same.

Saving multiple game profiles

We now have a game that can save a player's progress, but no way of clearing the data if they want to replay the game. As we mentioned already, the only option for removing the data is to have the user clear their browser's cache or overwrite the data, both options having drawbacks. Most users won't want to clear their cache as it will remove all the data in local storage, not just the game data. Overwriting the data is problematic if multiple people want to play the game in the same browser. Having only a single save file becomes meaningless. We do have a third option available which is that we don't clear the data at all, but rather we create additional save files that can be loaded at any time. Our current save/load system is already prepared for us to have multiple user profiles, we just need to add an input system to capture the name of the user. We will keep the system fairly simple, placing it on the front end and limit the user names to a maximum of eight characters. When the player clicks the start button, it will load the proper profile before it switches rooms:

  1. We will start by adding one more global variable for the player's name. Open scr_Global_GameStart, initialize the playerName variable, and set it to an empty string at the end of the script.
    globalvar playerName;
    playerName = "";
  2. We will need to create a new Object, obj_NameInput, which we can use for tracking the player's input. It does not need a sprite since we will be drawing the text onto the screen.
  3. Add a Create event with a new Script, scr_NameInput_Create, to initialize variables for the length of the string and how many characters have been typed.
    nameSpace = 0;
    nameMax = 8;
  4. Next we will add a Draw | Draw event with a new Script, scr_NameInput_Draw, attached to draw the player's name as it is typed and a simple instruction telling the player to type in their name:
    draw_set_color(c_black);
    draw_set_halign(fa_center);
    draw_set_font(fnt_Small);
    draw_text(320, 280, "Type In Your Name");
    draw_set_font(fnt_Large);
    draw_text(320, 300, playerName);
    draw_set_font(-1);
  5. Now that we have everything displayed on screen we need to gather the keyboard input. Add a Key Press | Any Key event and attach a new Script called scr_NameInput_KeyPressed.
    if (nameSpace < nameMax) 
    {    
        if (keyboard_key >= 65 && keyboard_key <= 90) 
        {
            playerName = playerName + chr(keyboard_key);
            nameSpace++;
        }
    }

    We only want the name to be a maximum of eight letters, so we first check to see if there is still space available in the current name. If we can input another letter, we then check if the key that is being pressed is a letter. If a letter has been pressed, we add that letter to the end of the string and then indicate that another space has been used.

  6. If we ran the game now we would be able to enter letters, but we have no ability to undo any letters. We can fix that with the following code:
    if (keyboard_key == vk_backspace) 
    {  
        lastLetter = string_length(playerName);
        playerName = string_delete(playerName, lastLetter, 1)
        if (nameSpace > 0)
        {
            namespace--;
        }
    }

    If the user presses backspace, we grab the length of the string to find out where the last space of the string is. Once we know that, we can then remove the letter at the end of the string. Finally we check to see if there are still letters remaining and if so, reduce the space count by one. This is needed so that we can't go into negative spaces.

  7. Open MainMenu and place a single instance of obj_NameInput somewhere in the room, location does not matter.
  8. Save and play the game. In the front end you should be able to enter a name of up to eight letters and by clicking backspace, delete all those letters. It should look like the following screenshot:
    Saving multiple game profiles
  9. The save system is now complete; all that is left to do is to load the data when the player clicks the START button. Since we use the START button in the Shop as well as the Main Menu, we will need to run a check to ensure we only load the game data at the beginning of the game. Open scr_Button_Start_MousePressed and before the room is changed, add the following code:
    if (room == MainMenu)
    {
        saveFile = string(playerName + ".ini");
        scr_GameLoad(saveFile);
    }
  10. Save and play the game. Use your name and play the game, completing a few levels. Then refresh the page and enter a different name. When you get to the level selection, only the first room should be available.
  11. Refresh the browser a second time and use your name once again. This time when you get to the level selection you should see all your unlocked levels. The save system works!
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.205.235