Table of ContentsDealing with DamageConclusion

Saving and Loading

Star Assault is starting to look like a real game now. If you put together the fun of trying to stay alive, progressing to new levels, and encountering new types of enemies, you'll quickly find yourself getting engrossed in the game.

The next thing you need to do is provide a way for the player to save the game and resume progress at a later stage. To do this, you'll save the game state to a record store, and then load it back again.

As you saw in Chapter 5, record stores are simple to use. You just open or create a store, construct an array of bytes, and then write it out. To keep things simple, you'll save your game at the start of a new level, rather than midway through it. This means you only need to store the tile map (rather than the attributes of all the actors as well). To accomplish this, you add a saveLevel method to the World class.

public boolean saveLevel()
{
    RecordStore store = null;

    try
    {
        try
        {
            // Our save/load system only supports a single current saved level so
            // to make room you need to delete any current saved level first.
            RecordStore.deleteRecordStore("Level");
        }
        catch (RecordStoreNotFoundException rse)
        { }

        store = RecordStore.openRecordStore("Level", true);
    ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
    DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);

    // write out the number of tiles we have
    dataOutputStream.writeInt(tilesWide);
    dataOutputStream.writeInt(tilesHigh);

    // write the tile map
    for (int ty=0; ty < tilesHigh; ty++)
        for (int tx=0; tx < tilesWide; tx++)
            dataOutputStream.writeByte(tileMap[ty][tx]);

    dataOutputStream.flush();

    // delete the old one and add the new record
    byte[] recordOut = byteOutputStream.toByteArray();
    try
    {

        // try to set the existing record (we'll get an invalid exception
        // if it doesn't exist)
        store.setRecord(1, recordOut, 0, recordOut.length);
    }
    catch (InvalidRecordIDException ir)
    {
        // add the record
        store.addRecord(recordOut, 0, recordOut.length);
    }

    dataOutputStream.close();
    byteOutputStream.close();

    return true;
}

catch (IOException io)
{
    System.out.println("IOException: " + io);
    return false;
}
catch (RecordStoreException rse)
{
        System.out.println("RSException: " + rse);
        return false;
    }
    finally
    {
        try
        {
            if (store != null)
                store.closeRecordStore();
        }
        catch (RecordStoreNotOpenException e)
        {
        }
        catch (RecordStoreException e)
        {
        }
    }
}

In this method you create an output stream and then write the size and contents of the map. You then convert this DataOutputSteam into a byte array and use the record store methods to write it out.

Loading the data back up is even easier.

public void loadLevel()
{
    // load up the tilemap from RMS for the current level
    RecordStore store = null;
    try
    {
        store = RecordStore.openRecordStore("Level", true);

        ByteArrayInputStream byteInputStream = new
ByteArrayInputStream(store.getRecord(1));
        DataInputStream dataInputStream = new DataInputStream(byteInputStream);

        tilesWide = dataInputStream.readInt();
    tilesHigh = dataInputStream.readInt();

    tileMap = new byte[tilesHigh][tilesWide];
        for (int ty = 0; ty < tilesHigh; ty++)
            for (int tx = 0; tx < tilesWide; tx++)
                tileMap[ty][tx] = dataInputStream.readByte();

        // now restart the world for this level
        restart();

        dataInputStream.close();
        byteInputStream.close();
    }

    catch (IOException io)
    {
        System.out.println("IOException: " + io);
    }
    catch (RecordStoreException rse)
    {
        System.out.println("RSException: " + rse);
    }

    finally
    {
        try
        {
            if (store != null) store.closeRecordStore();
        }

        catch (RecordStoreNotOpenException e)
        {
        }

        catch (RecordStoreException e)
        {
        }
    }
}

As you can see, it's basically the reverse of the save method. I've tried to keep things simple for the moment; however, in the final version of Star Assault you will need to write out and then read back the player's starting position, current number of lives, the level number, and anything else you need.

There are a few other things you need for handling save games as well. First, I've designed Star Assault so that when the player loses all of his lives and the game ends, you delete the current save game automatically. (This might be a bit harsh for some other game types so feel free to let them play onmaybe adding some other type of penalty rather than having to restart the game.) You can just wrap up a call to delete the record store to do this.

public void removeSavedGame()

{

    try

    {

        RecordStore.deleteRecordStore("Level");

    }

    catch (RecordStoreNotFoundException rse)

    {

    }

    catch (RecordStoreException e)

    {

    }

}

Next you need to be able to determine whether a save game currently exists. (You'll be using this in the menu system to display a Resume Game menu item if a save game exists.) To do this, you need a method that checks whether it can get the save record. Since this method is called every time the menu is displayed, you don't want to reload the entire level using the loadLevel method; this is not only slow but it would overwrite their existing game!

The following getSavedLevel method checks for the existence of a saved level without actually loading it. If a level is found it returns true, otherwise false.

public static boolean getSavedLevel()
{
    RecordStore store = null;
    try
    {
        store = RecordStore.openRecordStore("Level", false);
    ByteArrayInputStream byteInputStream = new
ByteArrayInputStream(store.getRecord(1));
        byteInputStream.close();
        return true;
    }

    catch (IOException io)
    {
        System.out.println("IOException: " + io);
        return false;
    }
    catch (RecordStoreNotOpenException rse)
    {
        return false;
    }
    catch (RecordStoreException rse)
    {
        System.out.println("RSException: " + rse);
        return false;
    }

    finally
    {
        try
        {
            if (store != null) store.closeRecordStore();
        }

        catch (RecordStoreNotOpenException e)
        {
        }

        catch (RecordStoreException e)
        {
        }
    }
}

    Table of ContentsDealing with DamageConclusion