Robot Foot Games Helping you improve your games as we improve our own. :)

30May/110

Saving/Loading with EasyStorage using XNA 3.1

This tutorial uses EasyStorage version (57440).

Trying to get saving and loading to work on a 360 can be a pain at first, but it's pretty easy as long as you set it up correctly. If you plan on releasing your game on the 360 you also have to make sure it doesn't crash while saving/loading if someone pulls out the memory card. I'm using Nick Gravelyn's EasyStorage which you'll have to download in order to follow along. I'm using a project that has the Platform Starter Kit combined with the Game State Management for this tutorial. You must have the Game State Management sample incorporated into your project in order to follow this tutorial. You could just use a blank copy of the GSM if you want to wanted to get this working, then try to move it into your own project.


 

Adding the files to your project

First off, we need to transfer all of the necessary files to our blank copy of the GSM (game state management). All you have to do is drag the EasyStorage folder (found at GS3.1 > EasyStorage in the download above) to the very base of your project. Your base folder should look like this after the move. NOTE:  I'm still using XNA 3.1 in this tutorial so don't let the name in the image through you for a loop (it's from the 4.0 tutorial).

EasyStorageFolder

 

Inside of Visual Studio we need to add the project and add a reference to it. In the solution explorer right click on the VERY TOP item (mine's named "Solution 'EasyStorage_3.1'"). Navigate to Add > Existing Project... > (find the location of the EasyStorage folder that you moved in the step above) EasyStorage > EasyStorage [Xbox].csproj. When you select that you should see an "EasyStorage [Xbox]" project alongside the others.

Let's add the reference now. In the solution explorer expand your main project ("EasyStorage_3.1" for me) and right click on the "References" folder. Click on Add Reference and when the popup opens click on the Projects tab at the top. You should see 'EasyStorage [Xbox]' and just add that. We can move onto setting up our game now.

NOTE: The EasyStorage folder also contains projects for windows and windows phone but we're just focusing on Xbox for this tutorial. If you're also making a windows or windows phone copy of the game you'll have to repeat the step above and add the specific projects and references.


 

Global.cs

Now right-click on the Saving folder and click Add>Class and name it 'Global.cs'. The purpose of this file is to have a set of global variables so we can access them from wherever we want in our code. It should look like the following but you need to change the namespace to match the namespace of YOUR game project. Also change fileName1 to the name of your game.

using System;
using System.Collections.Generic;
using System.Text;
using EasyStorage;
namespace YOUR_NAMESPACE_HERE!
{
    public class Global
    {
        // A generic EasyStorage save device
        public static ISaveDevice SaveDevice;
        //We can set up different file names for different things we may save.
        //In this example we're going to save the items in the 'Options' menu.
        //I listed some other examples below but commented them out since we
        //don't need them. YOU CAN HAVE MULTIPLE OF THESE
        //public static string fileName_options = "YourGame_Options";
        public static string fileName_game = "YourGame_Game";
        //public static string fileName_awards = "YourGame_Awards";
        //This is the name of the save file you'll find if you go into your memory
        //options on the Xbox. If you name it something like 'MyGameSave' then
        //people will have no idea what it's for and might delete your save.
        //YOU SHOULD ONLY HAVE ONE OF THESE
        public static string containerName = "YourGame_Save";
    }
}

 

PressStartScreen.cs

Now we have the framework laid out and we just have to set our SaveDevice before we can use it. I have the following code on my 'Press start to begin' screen, when you press start, which should be a part of any game because you can also find out which controller is controlling everything. The struct above our Global class has the variables we'll need to save, replace them with anything that you'll want to save in your own game. You need to make a new class (preferably in the Screens folder) called PressStartScreen.cs and replace the contents of that with the class below. Make sure you change YOUR_NAMESPACE_HERE and YOUR_GAME_NAME.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using EasyStorage;
namespace YOUR_NAMESPACE_HERE!
{
    class PressStartScreen : MenuScreen
    {
        ISaveDevice saveDevice;
        public PressStartScreen()
            : base("")
        {
            MenuEntry startMenuEntry = new MenuEntry("Press A to start");
            startMenuEntry.Selected += StartMenuEntrySelected;
            MenuEntries.Add(startMenuEntry);
        }
        void StartMenuEntrySelected(object sender, PlayerIndexEventArgs e)
        {
            PromptMe();
        }
        private void PromptMe()
        {
            // we can set our supported languages explicitly or we can allow the
            // game to support all the languages. the first language given will
            // be the default if the current language is not one of the supported
            // languages. this only affects the text found in message boxes shown
            // by EasyStorage and does not have any affect on the rest of the game.
            EasyStorageSettings.SetSupportedLanguages(Language.English);
            // on Windows Phone we use a save device that uses IsolatedStorage
            // on Windows and Xbox 360, we use a save device that gets a
            //shared StorageDevice to handle our file IO.
#if WINDOWS_PHONE
            saveDevice = new IsolatedStorageSaveDevice();
            Global.SaveDevice = saveDevice;
            // we use the tap gesture for input on the phone
            TouchPanel.EnabledGestures = GestureType.Tap;
#else
            // create and add our SaveDevice
            SharedSaveDevice sharedSaveDevice = new SharedSaveDevice();
            ScreenManager.Game.Components.Add(sharedSaveDevice);
            // make sure we hold on to the device
            saveDevice = sharedSaveDevice;
            // hook two event handlers to force the user to choose a new device if they cancel the
            // device selector or if they disconnect the storage device after selecting it
            sharedSaveDevice.DeviceSelectorCanceled +=
                (s, e) => e.Response = SaveDeviceEventResponse.Force;
            sharedSaveDevice.DeviceDisconnected +=
                (s, e) => e.Response = SaveDeviceEventResponse.Force;
            // prompt for a device on the first Update we can
            sharedSaveDevice.PromptForDevice();
            sharedSaveDevice.DeviceSelected += (s, e) =>
            {
                //Save our save device to the global counterpart, so we can access it
                //anywhere we want to save/load
                Global.SaveDevice = (SaveDevice)s;
                //Once they select a storage device, we can load the main menu.
                //You'll notice I hard coded PlayerIndex.One here. You'll need to
                //change that if you plan on releasing your game. I linked to an
                //example on how to do that but here's the link if you need it.
                //http://blog.nickgravelyn.com/2009/03/basic-handling-of-multiple-controllers/
                ScreenManager.AddScreen(new MainMenuScreen(), PlayerIndex.One);
            };
#endif
#if XBOX
            // add the GamerServicesComponent
            ScreenManager.Game.Components.Add(
                new Microsoft.Xna.Framework.GamerServices.GamerServicesComponent(ScreenManager.Game));
#endif
        }
    }
}

 

Now that you have that in your game you'll need to go into Game.cs and find the line that says

screenManager.AddScreen(new MainMenuScreen(), null);

and change it to

screenManager.AddScreen(new PressStartScreen(), null);

This will now start your game with a press start to begin screen and when you continue it prompts you to choose a storage device (if you have more then just the hard drive connected) and the main menu screen is created.


 

A few notes about the above code, because there's a lot going on that will confuse you if you don't understand it. If you're playing on the PC then your SaveDevice is chosen automatically and your save file will be saved in My Documents/SavedGames I believe. Otherwise it makes a call to PromptMe() which does all the heavy lifting of choosing a save device. If you have only the hard drive connected to your Xbox then it will seem like nothing happened because it chooses that automatically as well. If you have multiple storage devices plugged in (hard drive, memory card) then you will get a pop-up asking you to choose a storage device. Look at the 2 lines in the PromptMe method that end with SaveDeviceEventResponse.Force. This has 3 different arguments that you can use:

  • Nothing means nothing happens if you cancel the device selector screen. No save device will be chosen and if you try saving later you'll get errors.
  • Prompt means it will ask you to either choose a storage device, or continue without saving.
  • Force means that you MUST choose a save device in order to continue. No ifs, ands, or butts about it!

The first line (from the 2 options we're talking about) happens when you cancel the device selector. The 2nd line happens if you disconnect the storage device at some point after choosing it.

Finally, we set our Global.SaveDevice to the sharedSaveDevice so we have access to it later when we want to save/load.

NOTE: I'm using SharedSaveDevice in this example, so anything you save is accessible to anyone playing the game on your console. If you want to save data and attach it to a certain player, as most AAA games do, you'll want to look into PlayerSaveDevice instead, but it's a little bit more work.


 

Player.cs

So now onto saving. The Platform Starter Kit has a Player.cs class, and that's where I do my saving. We need to add the saving/loading methods. You'll also need to add a using statement: using System.IO;

public void SaveGame()
{
    // make sure the file exists
    if (Global.SaveDevice.FileExists(Global.containerName, Global.fileName_game))
    {
        // save a file asynchronously. this will trigger IsBusy to return true
        // for the duration of the save process.
        Global.SaveDevice.Save(
            Global.containerName,
            Global.fileName_game,
            stream =>
            {
                using (StreamWriter writer = new StreamWriter(stream))
                {
                    writer.WriteLine(lives);
                    writer.WriteLine(doIHaveTheKey);
                    writer.WriteLine(score);
                }
            });
    }
}

 

Take note of the order you save stuff. Since the variable "lives" is the first to be saved, it MUST be the first to be loaded!

So here's our load methods which look much the same as SaveGame() but instead of writing to a file, we're reading from it.

public void LoadGame()
{
    if (Global.SaveDevice.FileExists(Global.containerName, Global.fileName_game))
    {
        Global.SaveDevice.Load(
            Global.containerName,
            Global.fileName_game,
            stream =>
            {
                using (StreamReader reader = new StreamReader(stream))
                {
                    lives = int.Parse(reader.ReadLine());
                    doIHaveTheKey = bool.Parse(reader.ReadLine());
                    score = int.Parse(reader.ReadLine());
                }
            });
    }
}

 

And that's everything! When we want to save our game we make a call to SaveGame() in our Player.cs class (or whatever you used) and when I load up my game I call LoadGame() in my LoadContent() method.

I know this can be pretty confusing at first, but once you finally get it, it'll be smooth sailing from there on!

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

(required)

No trackbacks yet.