WP7 Saving/Loading Tutorial
For those that don't know about Easy Storage here's the description from the codeplex project "EasyStorage is a library for making it quicker and easier for XNA Game Studio developers to manage the StorageDevice without all the details. EasyStorage uses a simple API to enable you to add file saving to your game."
Nick Gravelyn updated it to support XNA 4.0/Windows Phone 7 and since I've used it with both my games for the 360 I figured I'd check it out for my WP7 game. It's pretty simple to set up on the 360, and I wrote a tutorial on how to do that, and SUPER simple to use on WP7.
First off, download the latest build (changeset 57440). It comes with windows/360/WP7 stuff and if you plan on supporting any multiple of those platforms then you'll have to look through the included sample to see how they get setup differently. For the purpose of this tutorial we'll just port the WP7 code into out project. From the easystorage-57440 folder go into GS4 and copy the EasyStorage folder in there into the very base of your project. The base folder should look like this after you copy the folder in.

Now we have to add the project and a reference to it. From the Solution Explorer in Visual Studio right click on the very top item (should be called "Solution ...") and Add > Existing Project. Navigate inside the EasyStorage folder you just copied into your project from the step above. Inside of that folder should be a file called "EasyStorage [Phone].csproj" and click open. Now you should see the EasyStorage [Phone] project along side your content project and the main project.
Now we need to add a reference to this project we just added in our main project. Expand your main project in the solution explorer, right click on the References folder and click Add Reference. Click on the "Projects" tab at the top and select the EasyStorage [Phone] file. Done!
NOTE: You'll notice that there's also project files in here for Xbox and Windows. If you plan on making a copy for either of those just follow the 2 steps above to add the specific project and reference.
Global.cs
I like to create a class called Global.cs that I can access anywhere so I can save/load where I need to. Right click on the main project and Add > Class. Name it "Global.cs." You can erase everything in there and replace it with this code. The comments should explain it pretty well.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using EasyStorage; namespace WP7_EasyStorageTutorial { public class Global { // A generic EasyStorage save device public static IAsyncSaveDevice 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"; } } |
Game.cs
Now we just need to setup our save device. In the constructor of your game, at the very bottom, add this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | // 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.French, Language.Spanish); // 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 #if WINDOWS_PHONE saveDevice = new IsolatedStorageSaveDevice(); Global.SaveDevice = saveDevice; #else // create and add our SaveDevice SharedSaveDevice sharedSaveDevice = new SharedSaveDevice(); 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(); #endif // we use the tap gesture for input on the phone TouchPanel.EnabledGestures = GestureType.Tap; #if XBOX // add the GamerServicesComponent Components.Add(new Microsoft.Xna.Framework.GamerServices.GamerServicesComponent(this)); #endif // hook an event so we can see that it does fire saveDevice.SaveCompleted += new SaveCompletedEventHandler(saveDevice_SaveCompleted); |
Here you can set your supported languages and we also set our isolated storage device to our global counterpart so we can access it if needed. You'll notice a bunch of cross platform code in here so if you don't have a need for it feel free to remove it. You'll notice a SaveCompleted event so if you want to let your users know if the game saved correctly you could do it in there. Here's the method for that:
1 2 3 4 5 | void saveDevice_SaveCompleted(object sender, FileActionCompletedEventArgs args) { //If you want a message along the lines of "Save Completed" //this is the place to do that. } |
When you want to save something you can simply use this block of code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // make sure the device is ready if (Global.SaveDevice.IsReady) { // save a file asynchronously. this will trigger IsBusy to return true // for the duration of the save process. Global.SaveDevice.SaveAsync( Global.containerName, Global.fileName_options, stream => { using (StreamWriter writer = new StreamWriter(stream)) { writer.WriteLine(mySavedInteger); writer.WriteLine(mySavedBool); writer.WriteLine(mySavedString); } }); } |
We want to make sure the save device is ready for a save before committing to it. You can query Global.SaveDevice.IsBusy if you want to know when your game is saving something (useful for a saving animation). Make sure the order of stuff you're writing to the save device because it MUST be read in that exact same order.
Now when you want to load something you can simply use this block of code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | if (Global.SaveDevice.FileExists(Global.containerName, Global.fileName_options)) { Global.SaveDevice.Load( Global.containerName, Global.fileName_options, stream => { using (StreamReader reader = new StreamReader(stream)) { mySavedInteger = int.Parse(reader.ReadLine()); mySavedBool = bool.Parse(reader.ReadLine()); mySavedString = reader.ReadLine(); } }); } |
Here we're checking that a fileName_options file exists and if it does we load the data inside. Since reader.ReadLine() returns a string you may need to cast it to a different type. There's a couple examples above (int.Parse and bool.Parse).
And that's everything! If you want to save some options (like music or sound effect volume) you could pass fileName_options as an argument while saving/loading. When you want to save some in-game stats you can pass in fileName_gameStats or whatever you'd like to. You should be able to use just the one container, but split up your save files based on what/where you're saving.
July 15th, 2011 - 20:54
Hello,
I did everything exactly as you said to in this tutorial but I keep getting an null pointer error for this line:
// make sure the device is ready
if (Global.SaveDevice.IsReady)<<<<
{
The cause is this line:
public static IAsyncSaveDevice SaveDevice;
I've been trying figure why it's not working but I haven't been able to fix it.
July 15th, 2011 - 22:47
It sounds like your ‘SaveDevice’ isn’t getting properly set then. Try putting a break point to make sure the following lines are getting hit (in the Game.cs constructor):
saveDevice = new IsolatedStorageSaveDevice();
Global.SaveDevice = saveDevice;
December 4th, 2011 - 09:36
I would like to use easy storage to save a custom class “Level”. My class has several references to other custom classes like enemies. I believe I understand how Easy Storage works to save common varable types, but I’m not sure how to save/load a custom class. Any tips would be greatly appreciated.
This line might not be doing anything, but Visual Studio doesn’t get mad at me for writing it.
writer.WriteLine(currentLevel);
However this line is not acceptable to Visual Studio.
savedLevel = reader.ReadLine();
“Cannot implicitly convert type ‘string’ to ‘ProjectName.Level’.
I’m not sure if I should continue to pursue using Easy Storage or research some kind of XML serialization.
December 4th, 2011 - 16:43
You should definitely look into XML to serialize an entire class. I learned the basics of XML from Nick Gravelyn’s Tile Engine videos so that might be a good place to check.
December 5th, 2011 - 04:39
Ok, thanks. I was already looking into XML Serialization as a back up plan. We’ll see ho complicated this gets.