I'd love to be able to continue these blogs indefinitely, but unfortunately I can't... I've been writing them retro-actively until now, but with this weeks news we are up to date with the development of my game Skein, and so further development diaries will be published as and when I have something new worth sharing. However, before I bow out for a few weeks, I'll leave you with a discussion of how I've been dealing with saving the game information and what different solutions I've come up with for things like player statistics, high scores, and level/checkpoint saves.
It's A Numbers Game
What is it about statistics that players love? Is it being able to track their progress throughout the life of the game? Is the fact they can brag that they've captured more flags than anyone else on their team? Is it related to hoarding and that part of our brain that loves to just keep adding to our stock of "things" - material or otherwise? I honestly don't know, but it seems that everybody loves statistics - myself included - and a lot of games track them, so I had to have them in my game.
In my game there are six playable characters and I wanted to track things like the number of times they have been played or killed, or the amount of total gold they have found, and then show them to the player in some way. I figured that it would be a good way for them to see which character they have favoured as well as their strengths and weakness' while playing, so I sat down and made a list of things that I thought worthy of following:
- Total number of enemy kills
- Total number of enemy "nests" destroyed
- Total number of times the character has died
- Total time played with this character
- Total number of player tombs looted
- Total number of spells found
- Total number of spells cast
- Total amount of loot found
- Total number of potions taken
- Total number of keys found
Apart from those base stats, I will probably add further, sillier, ones like "total number of sheep exploded" and things, but they'll have to wait until development is almost complete. However I could create the mechanism for storing them just now and use these base figures to get started.
My chosen method for dealing with almost all types of save data is to use a DS map. Not only are they easy to use (especially with accessors), they are also generally very fast to read and write to and even save. However, I don't use the ds_map_save() function to create a string then write that string to a text file, as you might expect. Instead I take advantage of the ds_map_secure_save() function, which saves the map in a custom GameMaker: Studio secure format which can be easily loaded back up with the companion ds_map_secure_load() function. Although the secure format part is important, I do this simply beacuse it's much faster and simpler than the alternative. That's not to say you can't use other methods for saving, like buffers or text files, but I find this method to be the best compromise between functioning speed and easy code readability and maintenance.
So, at the very start of my game I check for a DS map file, and if one isn't found a new map is initialised with some default values and then saved out, and if one is found it's simply loaded up for use. The resulting DS map ID I store in a global scope variable, and from then on all I have to do is change the map values and call the secure save function to update all the game information - all very easy and clean, and simple to code.
I set up a map to hold the required statistics for each of the player characters, and while I was at it I also set up a map to store the different player achievements...
What Have You Achieved
Achievements were supposed to have started when Activision offered "Patches For High Scores". This was a system by which game manuals instructed players to achieve a particular high score, take a photo of the score display on the television, and then send in the photo to Activision to receive a physical, iron-on style patch. This has mutated over time to become the Badges/Patches/Trophies/Achievements that we have now in almost every game.
Achievements are like statistics in that they are coveted by players for many reasons. Unlike stats, however, they can form a goal in themselves and greatly extend the gameplay for a given title. If you look at a game like Lara Croft And The Guardian Of The Light, you can see what I mean by this, as in that game each stage has bonus achievements that unlock new weapons and power-ups, giving the player an incentive to go back to a previously completed level and try it again.
What achievements did I want in Skein? Being honest, I wasn't sure - and should point out that I still am not sure! - but I knew I wanted them as they really can enhance the gameplay. What I didn't want though were pointless achievements. Personally I hate it when you get an achievement for something so mundane as clearing the first level or for opening the inventory, and I find these kinds of achievements insulting. I haven't achieved anything by simply playing the game as it's meant to be played! in my mind achievements should mean something, and in Skein I wanted my achievements to challenge the player.
However, I wasn't sure that achievements could be created just yet since the game really needs to be almost finished, but I could certainly put the framework in place since I was working on the saving and loading of stats anyway. The important thing was to get the framework in place and working so that later I could just "drop in" the information and it'll just work. So, I set about tracking certain statistics and created a another set of map entries to store whether a given achievement has been granted or not. Then I programmed in my first (and currently only) achievement to test the system - reach level 5 in under a minute. This was an easy one to set up as I'm tracking the player time anyway, so it was simply a case of checking the time on level start against the current level then writing to the DS map achievement key if the criteria has been met.
Saving Data And Player Feedback
Apart from dealing with stats and achievements, my save map also contains general game data for things like fullscreen mode, music on/off, last player character used, etc... Most of these are simple key/value pairs that flag a setting as on or off or having a single value, but a few of them I have written as longer strings - specifically the bestiary and spell entries in the player guide, and the tutorials controller.
For the spells and bestiary entries I needed a way to track whether an enemy or spell had been found, and if it has whether the journal entry for it has been viewed or not. Feedback for the player is very important, and I wanted the journal to flag newly found spells and enemies with a marker so that the player could browse through them and identify those that they've not seen before. So, what I did was generate a string for every item entry like this "000000000000000". This was done the first time the player runs the game, and each "0" represents a single entry in the journal for that item (so I have two strings like this - one for the enemies and one for the spells found).
Each enemy has a number value associated with it, and this number value corresponds to a position in that string of zeros. Then when the player kills an enemy a quick check is done using string_char_at() on the map string to see if the enemy has been found or not. If it has been found then nothing happens, but if it hasn't been found then a popup message is shown to the player in-game to let them know a new entry has been added to the journal, and the value at the position in the string is set to 2.
Why 2? Well, in the bestiary and spell controller objects, I have it parse this string and if it is set to 2 it will display a little "new" tag beside the name. Once the player has viewed this entry, the string is set to "1" to show that it has been found and viewed, so no "new" is displayed the next time the journal is opened. This might seem like a minor thing, and the player will probably not even notice, but if it wasn't there I'm sure that they most certainly would notice as they trawl through the journal trying to decide if a spell or enemy is something they haven't seen before or not.
Another great thing to note about using a long string in this way is that it is really easy to maintain. If I create a new enemy, then I simply have to add another "0" to the default string, and give the enemy a corresponding number value (then write the text and stuff for it, of course). The scripts I wrote to deal with this will adapt automatically to suit the length of the string, and I can change the order that enemies are shown in by simply assigning them different numbers. If I haven't said it before (and I'm sure I have!), making your scripts self-contained, modular, and "future proof" is essential, especially when working on larger projects like this where you could be writing something now that you won't actually be coming back to until months later. You don't want to be puzzling over how to adapt old code, and really want it to "just work".
Saving The Game
At first I wasn't going to add in any type of save game mechanism for Skein, since it was originally just meant to be a bit of casual time-wasting fun for mobile devices, but with the change in focus to desktop, I realised that I needed some way to save the game. I knew I didn't want any type of checkpoint or lives so that if the player dies they had to start all over again, but at the same time I didn't want to punish the player for being called to dinner or for going to work. I also had to take into account the fact that you could be playing a 1 player or a 2 player game and adapt accordingly.
But what to save and when? Given the procedural nature of the game, saving an entire level and then trying to recreate it would be pretty awkward, but was that even necessary? I decided that, no, not really... Each "level" in Skein should only take about thirty seconds to a minute and a half, so I went with saving the game state at the start of each level and then if the player quits the game he'll just start again from that point. Yes, they'll lose the time they've invested in that last level, but in a proceedural game that's no big deal, as long as the general progress is maintained.
I also took the decision to destroy any save data when the player dies. This might seem a bit harsh, but I wanted to maintain that old arcade/roguelike feel to it, and it didn't feel right that the player can die and simply re-load from their previous position. So, while you can choose to quit the game and the level data will be saved, you can't go back and re-load a previous game if you die.
As for what was saved, I think I can say not much! The current player stats for health, mana, loot, etc... plus a level value and a flag value for one or two player gameplay. This flag value was important as I actually decided to save one player and two player games separately and give the player the option of which to load so that you can have a 2P game going with a friend as well as a 1P game going for yourself with no "overlap".
And that's the end of another IndieDB news post! Like I said at the start, there may not be another one of these for a while (or there may be another one next week...) as we are all up to date with Skeins progress for now. I hope that these blogs have been informative, fun and helpful to everyone and I've enjoyed writing them immensely. Thanks for reading and good luck with your games!