Saving and loading the game is potentially a rat’s nest of bugs for a complex game if it’s not done right. This is my approach to it in IllFate, complete with source code and some explanation. If you’ve had experience with similar systems or have any questions about the approach I’ve taken, I’d like to hear about it.

package com.andrewtraviss.illfate.data
{
   import flash.utils.ByteArray;

   public class Terrain
   {
      public var tileIndex:int = -1;
      public var sheetIndex:int = -1;
      public var friction:Number = 1;
      public var passable:Boolean = true;
   }
}

This is the Terrain class that we are saving. This is pretty straightforward. Since IllFate is at an early stage there isn’t much data to worry about. This is why I wanted to get the system in place now. I am making it a general rule that all game objects store their state information in separate data structure classes. This makes it easier to create scenarios for testing and makes features like this one easier to implement.

package com.andrewtraviss.illfate.factories
{
   import com.andrewtraviss.illfate.data.Terrain;

   import flash.utils.ByteArray;

   public class TerrainFactory
   {
      public static function fromBytes(in_bytes:ByteArray):Terrain
      {
         var terrain:Terrain = new Terrain();
         terrain.passable = in_bytes.readBoolean();
         terrain.tileIndex = in_bytes.readInt();
         terrain.sheetIndex = in_bytes.readInt();
         terrain.friction = in_bytes.readFloat();
         return terrain;
      }
   }
}

The creation code is implemented in a factory. Why not put it into the Terrain class? Creation of the terrain instance should be an atomic operation if at all possible. It makes any code that has to create terrain much more readable. If a configureFromBytes method was implemented in Terrain itself, that would require two operations every time we needed to create an instance from data. Compare the following.

var terrain = new Terrain();
terrain.configFromBytes(terrainData);
var terrain = TerrainFactory.fromBytes(terrainData);

The abstraction of details will become more important if other subsystems need to get involved with Terrain creation. Any time I look at TerrainFactory it’s going to be because I am interested in the details of creating an instance. Once there are more complex relationships to manage, this will also protect Terrain from acquiring new dependencies.

package com.andrewtraviss.illfate.serialization
{
   import com.andrewtraviss.illfate.data.Terrain;

   import flash.utils.ByteArray;

   public class TerrainSerializer
   {
      public static function toBytes(in_terrain:Terrain):ByteArray
      {
         var bytes:ByteArray = new ByteArray();
         bytes.writeBoolean(in_terrain.passable);
         bytes.writeInt(in_terrain.tileIndex);
         bytes.writeInt(in_terrain.sheetIndex);
         bytes.writeFloat(in_terrain.friction);
         bytes.position = 0;
         return bytes;
      }
   }
}

For much the same reason that construction is in a separate class, serialization was implemented by TerrainSerializer. Keeping the underlying data structure classes free of dependencies is one of my general goals with this project as a whole.

package com.andrewtraviss.illfate.tests
{
   import com.andrewtraviss.illfate.data.Terrain;
   import com.andrewtraviss.illfate.factories.TerrainFactory;
   import com.andrewtraviss.illfate.serialization.TerrainSerializer;

   import flash.utils.ByteArray;

   import flexunit.framework.TestCase;

   public class SaveLoadTerrainTests extends TestCase
   {
      public function testBasic():void
      {
         var saveTerrain:Terrain = new Terrain();
         saveTerrain.friction ++;
         saveTerrain.passable = !saveTerrain.passable;
         saveTerrain.sheetIndex ++;
         saveTerrain.tileIndex ++;
         var terrainData:ByteArray = TerrainSerializer.toBytes(saveTerrain);
         var loadTerrain:Terrain = TerrainFactory.fromBytes(terrainData);
         assertEquals(saveTerrain.friction, loadTerrain.friction);
         assertEquals(saveTerrain.passable, loadTerrain.passable);
         assertEquals(saveTerrain.sheetIndex, loadTerrain.sheetIndex);
         assertEquals(saveTerrain.tileIndex, loadTerrain.tileIndex);
      }
   }
}

And here’s the unit test, to cap it off. Rather than injecting some magic numbers, all properties are just changed in some way from the default before saving. Numbers are incremented and booleans are inverted. This is enough to ensure that the values are changed from the defaults.

Eventually this approach will become awkward, that’s plainly obvious. I have to reference every property three times in the testing code, once in the loading code, and once in the saving code. This will add a constant drag as I need to update all of these places whenever I add a property to any game object that must be saved. I will need to generalize it in some way. I’ll include details with the Verse 1:4 release post if something happens.