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.
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.
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:
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.
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.
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.
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); }
scr_Button_Buy_MousePressed
and insert the call to scr_GameSave
just before the final brace.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:
scr_Global_GameStart
, initialize the playerName
variable, and set it to an empty string at the end of the script.globalvar playerName; playerName = "";
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.scr_NameInput_Create
, to initialize variables for the length of the string and how many characters have been typed.nameSpace = 0; nameMax = 8;
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);
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.
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.
MainMenu
and place a single instance of obj_NameInput
somewhere in the room, location does not matter.scr_Button_Start_MousePressed
and before the room is changed, add the following code:if (room == MainMenu) { saveFile = string(playerName + ".ini"); scr_GameLoad(saveFile); }
18.118.205.235