• Register

Skein is an old-school-style dungeon shooter currently in development. Taking a leaf from the Gauntlet school of games, Skein is a fast paced one or two player (co-op) game where you battle through hordes of monsters on a quest to save the soul of a talking sheep. The game features hundreds of enemies, a huge number of spells, 6 different playable characters and procedural dungeons with secret rooms, traps and all!

Post news Report RSS Saving The Day

In any game, you need to be able to store information away for retrieval later, but what information should you be saving? Where to save it to? Local or Online? In this article I discuss how statistics and other game information is squirreled away for later use in Skein.

Posted by on

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.

DS Maps

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".

Last Words

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!

Post comment Comments
gamelancer
gamelancer - - 8 comments

I like the decision to save 2P separately from 1P.

Consider adding a save file version number now, rather than later. In case later you decide to maintain save files, with different game versions. This has saved us lots of headaches and made it easier, even though maybe a bit verbose, to maintain game updates for different versions.

Recently we also implemented achievements for the first time in a mobile game, and realised that it has so much value to us as developers if they are chosen with purpose. You can actually get more out of them if you use them also as a means to track player retention, etc. And this made it worth our effort more as a nice surprise, since it gave us valuable player behavior statistics.

Man I've been following this game, and I think it is time you share a vertical slice demo - please. I know you said "soon" well, it cannot be soon enough!

Reply Good karma Bad karma+3 votes
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: