AS3 Avoider Game Tutorial, Part 11: Saving and Loading

by Michael James Williams on March 18, 2009 · 36 comments

in Avoider Game Base,Tutorial

In the previous part of this tutorial, I spoke about the importance of progression within a game. Adding multiple levels allows you to massively increase the size of your game, possibly beyond the point where a player can complete the whole thing in a single sitting. If you don’t allow the player to save their progress and return later, they won’t bother playing a second time.

We need a way of saving and loading data about the player’s progress even when they are not at their computer. In this part of my AS3 and Flash CS3 conversion of Frozen Haddock’s avoider game tutorial, we’ll use Flash’s SharedObject class to do just this, using a High Score to demonstrate it.

Click the image below to try this new feature out:

screenshot

If you’ve not been following the tutorial, grab the zip file of the game so far here and extract it somewhere. Otherwise, copy the files you’ve been working on so far to a new folder, as usual. Open the FLA file, and let’s get started.

It’s Really Easy, Actually

This is refreshingly simple to get to work. So simple, in fact, that I think we should just dive in and start implementing it.

Open the GameOverScreen movie clip in the library, and add a couple of text fields. One should be static text, and say “Best score:” or something similar, and the other should be dynamic text with an instance name of bestScore. Put some random number in the dynamic text to fill up space. If you can’t remember how to do all that, check Part 5.

Here’s mine:

screenshot

Obviously you should alter yours to fit with your game’s own style.

Data we want to save will be kept in a special folder on the user’s hard drive. To save and access this data, we use the built-in SharedObject class. For simplicity’s sake, I’m going to put all the relevant code inside the GameOverScreen.as file, so open that now.

First, we need to import the SharedObject class, and create a new instance of it as a class-level variable (lines 8 and 12):

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
12
package 
{
	import flash.display.MovieClip;
	import flash.display.SimpleButton;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.ui.Mouse;
	import flash.net.SharedObject;
 
	public class GameOverScreen extends MovieClip 
	{
		public var sharedObject:SharedObject;

Now we have to link this to a specific shared object on the user’s hard drive. As the name implies, it’s possible for different Flash files to share the same object, so use a name that’s unlikely to be chosen by anyone else. Also, the name cannot contain any spaces. I’ve chosen “mjwScores”.

?View Code ACTIONSCRIPT3
14
15
16
17
18
19
public function GameOverScreen() 
{
	Mouse.show();
	restartButton.addEventListener( MouseEvent.CLICK, onClickRestart );
	sharedObject = SharedObject.getLocal( "mjwScores" );
}

Note that that’s a little s on the first sharedObject of line 18 and a big S on the second. The first is the instance, the second is the class. Just as we can make static variables that belong to the class rather than to a specific instance, we can make static functions, too, and that’s what’s going on here.

By the way, don’t worry too much about choosing a name that no-one else will have thought of. Flash takes the URL of the SWF into account as well, so two shared objects with the same name being accessed by SWFs on different websites will not accidentally change each other’s data. Portals like Newgrounds, Kongregate and Whirled have ways of preventing this too.

So now we have access to a file on the user’s hard drive where we can save some data, let’s actually put something in there. The sharedObject instance has a property called data which we can use for this; we can put any variable we like inside sharedObject.data without having to write var sharedObject.data.variableName = new Whatever(). It’s easier to explain with an example, and the best place to do this is in the setFinalScore() function, since that’s the point where we actually know the player’s score:

?View Code ACTIONSCRIPT3
26
27
28
29
30
public function setFinalScore( scoreValue:Number ):void
{
	finalScore.text = scoreValue.toString();
	sharedObject.data.bestScore = scoreValue;
}

You can add anything to this sharedObject.data just by writing sharedObject.data.whatever = something;. The only catch is, it’s not automatically saved to the user’s hard disk until they close the SWF — but we can shortcut this using sharedObject.flush():

?View Code ACTIONSCRIPT3
26
27
28
29
30
31
public function setFinalScore( scoreValue:Number ):void
{
	finalScore.text = scoreValue.toString();
	sharedObject.data.bestScore = scoreValue;
	sharedObject.flush();
}

That will save the score to disk. Of course, you only have my word for that at the minute, because we’re not displaying anything. Let’s do that now:

?View Code ACTIONSCRIPT3
26
27
28
29
30
31
32
public function setFinalScore( scoreValue:Number ):void
{
	finalScore.text = scoreValue.toString();
	sharedObject.data.bestScore = scoreValue;
	bestScore.text = sharedObject.data.bestScore.toString();
	sharedObject.flush();
}

If you save and run this, you’ll find your best score is always the same as your current score:

screenshot

This shows that sharedObject is kept up to date with what’s going on inside the Flash, not what’s being saved on the hard drive — otherwise, the “best score” would be the same as the score from the previous game, not the current one, since the display is updated before the flush() function is called. If you want to find out what data is on the user’s hard drive, you’ll have to use SharedObject.getLocal() again.

Anyway, we want the code to only update the best score if it’s higher than the previous best score. That seems pretty simple:

?View Code ACTIONSCRIPT3
26
27
28
29
30
31
32
33
34
35
public function setFinalScore( scoreValue:Number ):void
{
	finalScore.text = scoreValue.toString();
	if ( scoreValue > sharedObject.data.bestScore )
	{
		sharedObject.data.bestScore = scoreValue;
	}
	bestScore.text = sharedObject.data.bestScore.toString();
	sharedObject.flush();
}

Easy enough. When I save and run this, it works:

screenshot

However, there is a bug in the code that may cause an error for you, if you hadn’t played the game before. The first time the game is played, when the code gets to line 29 (above) it’ll find that sharedObject.data.bestScore hasn’t been set to anything yet, so it can’t compare it to scoreValue to see which is bigger. To reproduce this error, we can use the clear() function, which removes all data from within the shared object, resetting it to a state as though it had never been used:

?View Code ACTIONSCRIPT3
26
27
28
29
30
31
32
33
34
35
36
public function setFinalScore( scoreValue:Number ):void
{
	sharedObject.clear();	// for demonstrating a bug
	finalScore.text = scoreValue.toString();
	if ( scoreValue > sharedObject.data.bestScore )
	{
		sharedObject.data.bestScore = scoreValue;
	}
	bestScore.text = sharedObject.data.bestScore.toString();
	sharedObject.flush();
}

If you play it now, then when your avatar hits an enemy, the game will freeze, and this error will pop up in your Output window:

TypeError: Error #1010: A term is undefined and has no properties.

What we need to do is check whether a high score has been saved before and, if not, save the current score without bothering to check. We can do this by checking whether or not currentScore.data.bestScore is null:

?View Code ACTIONSCRIPT3
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public function setFinalScore( scoreValue:Number ):void
{
	sharedObject.clear();	//for demonstrating a bug
	finalScore.text = scoreValue.toString();
	if ( sharedObject.data.bestScore == null )
	{
		sharedObject.data.bestScore = scoreValue;
	}
	else if ( scoreValue > sharedObject.data.bestScore )
	{
		sharedObject.data.bestScore = scoreValue;
	}
	bestScore.text = sharedObject.data.bestScore.toString();
	sharedObject.flush();
}

Test this out now, and you’ll get no error. You’ll find the best score is always the same as the current score again, but that’s because of the sharedObject.clear() line we put in. Remove that, and it’ll work fine.

screenshot

By the way, try closing the SWF — closing Flash itself, even — and then re-testing it. The best score is still there!

More Details

That’s pretty much all you need to know to use SharedObject. There are a few more things it’s worth looking at, though.

First, you might be wondering where exactly on the hard drive all this data is kept:

  • Windows XP — C:\Documents and Settings\userName\Application data\Macromedia\Flash Player\#SharedObjects
  • Windows Vista — C:\Users\userName\AppData\Roaming\Macromedia\Flash Player\#SharedObjects
  • Mac OS X — ~/Library/Preferences/Macromedia/Flash Player/#SharedObjects/
  • Unix/Linux — ~/.macromedia/Flash_Player/#SharedObjects/

They’re usually inside another folder whose name is a seemingly random sequence of letters and numbers, and then grouped into folders based on the domain name of the site they’re from. The actual shared objects are .sol files.

Second, you should be aware that in certain cases the code above might not work. If the player’s computer has very tight security settings, Flash might not be permitted to save to disk. Also, if you plan to store a lot of data, you may run into problems, since by default a Flash application is only allowed to save up to 100KB. (And in theory the user might not have enough space, but that’s very unlikely.)

Fortunately there are ways of detecting this. We’ve done a lot of work with Events, attaching listeners to them that run specific functions when triggered, but Flash objects can also fire out information by means of exceptions, or Error objects.

Earlier on, we saw an example of this when we triggered a TypeError:

TypeError: Error #1010: A term is undefined and has no properties.

The shared object “threw” this TypeError, and because we didn’t detect it at any level (we weren’t looking for it), it got reported in the Output window. To detect an Error object, we don’t add an “error listener” to a specific object; instead, we use something called a try-catch block on a chunk of code.

Here’s an example:

?View Code ACTIONSCRIPT3
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public function setFinalScore( scoreValue:Number ):void
{
	finalScore.text = scoreValue.toString();
	try
	{
		if ( sharedObject.data.bestScore == null )
		{
			sharedObject.data.bestScore = scoreValue;
		}
		else if ( scoreValue > sharedObject.data.bestScore )
		{
			sharedObject.data.bestScore = scoreValue;
		}
		bestScore.text = sharedObject.data.bestScore.toString();
		sharedObject.flush();
	}
	catch ( sharedObjectError:Error )
	{
		trace( "Caught this error:", sharedObjectError.name, sharedObjectError.message );
	}
}

Flash tries to run every line of code within the try block. If any of them throw an error, Flash immediately stops running code from the try block, and starts running code from the catch block. The sharedObjectError:Error lets us access the actual Error object that was thrown; this object usually contains two variables: name and message. The code inside the catch block will not be run unless an error was thrown.

If you run the game now, it’ll all work fine, since nothing is throwing an error. But if you change the code inside the try block to match the code that caused the error earlier, you’ll get this in your Output window:

Caught this error: TypeError Error #1010: A term is undefined and has no properties.

Big deal, right? So we managed to trace something different, who cares? Well, for a start, notice that the game hasn’t crashed — unlike before, it hasn’t tried to set the bestScore text to a non-existent value, so it hasn’t found a problem. In fact, it hasn’t changed the best score at all:

screenshot

Plus, we’re not restricted to simply tracing the error. We can pop up a message letting the user know that their data has not been saved, or run a different set of code that saves a smaller set of data, or automatically generate an email to us to let ourselves know what’s happened. In this case, I’m just going to change the best score display:

?View Code ACTIONSCRIPT3
43
44
45
46
47
catch ( sharedObjectError:Error )
{
	trace( "Caught this error:", sharedObjectError.name, sharedObjectError.message );
	bestScore.text = "???";
}

screenshot

Not the most elegant of solutions, but it works well enough.

Try-catch blocks and Error objects can be very useful tools when debugging a program, but they’re kind of clumsy and messy. Attempt to remove as many as you can from your final code, and keep the ones that remain around single lines, not huge selections as I’ve done above.

Remember, your goal should be to write code that has no errors, rather than code that can deal with errors. I do realise that this is far easier said than done :)

Challenges

Other than score, you might also want to save and display the player’s best time and top level. You’ll have to decide whether to store these pieces of information separately, or as a group, e.g. the time and level the player got to when they got their highest score.

Other information you could keep track of includes total number of games played and total amount of time spent playing (across all sessions). Perhaps you could pop up a special screen if your user plays for over ten minutes total.

You’re not limited to storing simple numbers in the shared object. You could store an array of the scores the user got in the last five games they played — and moving on from this, the best five scores they ever got. A local high scores table, basically. You could also use an array to keep count of how many times the player has made it to each level.

Speaking of levels, one of the most useful things you can do with a shared object is allow the user to continue at the level they were last on. If you built a level select screen for the last part‘s challenges, you could stop the buttons doing anything if the player hasn’t got to the corresponding level yet. Or just start the game at whatever level the player got to last time.

Finally, sometimes players will want to remove their saved games. You could provide a button that uses sharedObject.clear() to do this for them, so that they don’t have to delete the file from their hard drive.

Wrapping Up

So, savegames aren’t very hard at all. Take a look at the LiveDocs entry for SharedObject if you’re interested; there are a few more places errors can pop up, but they’re not hard to deal with. It’s actually possible to use a product called Flash Media Server to share your shared objects with other computers across the Internet. This would allow for multiplayer games… but that’s a topic for another tutorial ;)

As always, you can download a zip with all the files relating to this part of the tutorial here.

In next week’s final part of this tutorial, we’ll clean everything up and look at the important topic of garbage collection.

{ 33 comments… read them below or add one }

Mushyrulez March 18, 2009 at 4:00 am

Interesting tutorial.
Is there some way to “manually” let the player save the game, eg in a pause menu (even though I haven’t implemented it yet)? As in taking all the properties of the current game and saving it on a file that they can CHOOSE to reload?
This is for after levels start having more variety in them.

MichaelJWilliams March 19, 2009 at 9:58 pm

Cool idea :)

Yes, that’s completely possible. I just played about with it, though, and it’s not as simple as just saying “sharedObject.data.army = army” inside AvoiderGame (although really, that is almost all you’d need to save, along with avatar’s position, score, level, and time). The reasons for this are to do with how AS3 treats variables, and whether it passes them “by reference” or “by value”.

This is actually a topic I’m going to cover in Part 12, so we should talk more about it then :)

Lauren March 22, 2009 at 11:52 am

Hi Michael.
I’ve implimented my game win screen, how do I impliment a bonus score for reaching the winning element? At the moment you get the same score if you win as you do if you loose, which isn’t very good or rewarding.

MichaelJWilliams March 22, 2009 at 12:21 pm

Hey Lauren. Basically all you need to do is call, “gameScore.addToValue( 250 );” in the part of the code that detects that the avatar has reached the element. Obviously you can change 250 to whatever value you want. Alternatively, you could add this in the win screen itself.

Lauren March 22, 2009 at 12:49 pm

Thank you :)

Paolo March 23, 2009 at 1:54 pm

This is great! I’ve been wondering how to do this, especially with storing save game/level data so players can progress to future levels without having to restart from scratch.

Mike March 24, 2009 at 4:37 am

Thanks very much for this MJW!! It’s saving me on my last AS3 assignment!

Clear, concise code and explanations, and easy to follow. Nice Work!

Maybe I’ll make a Flash game afterall!

-M.

MichaelJWilliams March 24, 2009 at 12:43 pm

Glad you like it, Paolo and Mike!

Ardavanski June 2, 2009 at 2:14 pm

With the source of this..I could make a max time to!
Thanks Michael ^^
Btw..could you do me favour?
Go to kongregate try my games 1 round (both) and rate 5 please? xD
Some1 gave me 1/5 for not knowing how to move …. <.<

Michael Williams June 3, 2009 at 2:05 pm

Max time — great idea!

gianfun June 3, 2009 at 6:41 pm

Thanksfor the tutorial once again!!

Also, it would be nice to put a comment informing that when you “getLocal”, the name has to be one word (as in “mjwScore”, and not “mjw score”).Since it’s a string, people might get confused (like me :D )

Michael Williams June 3, 2009 at 7:12 pm

Thanks Gianfun, I didn’t know about that restriction! I’ve added a note about it now :)

TJMGold June 7, 2009 at 9:02 am

What is up Michael. Hey man, would it be too much to ask to view the .fla file? It would be a great help. Thanks.

Michael Williams June 7, 2009 at 12:15 pm

Sure thing; my email address is on the About page.

FlashN00B September 8, 2009 at 6:33 pm

I can tell an awesome way to load.

Just follow those instructions:

pubilc var loadVars:Array;

This will be the array where we store all the vars needed to load.
If you don’t know how to create a sharedobject the tutorial above will help out.
Anyway I will continue:

Now put all the vars you want to load and their starting vars inside this array.
Just like this:

loadVars = new Array();
loadVars[1] = "maxScore"; // name of the variable we will load
loadVars[2] = 0; // assigned value if it hadn't been saved before
loadVars[3] = "highestLevel"
loadVars[4] = 0;

Fine. Now we have an array where anything we need is stored in.
I named the shared object we will use here “saveFile”.

saveFile = new SharedObject.getLocal("mjwavoidergamesave");
for(var i = 1; i <= loadVars.length-1; i += 2)
{
      if(saveFile.data[loadVars[i]] == undefined) // check if the saved variable is undefined
      {
            // if it is, change it
            saveFile.data[loadVars[i]] = loadVars[i + 1];
      }
}

This works fine so far.
But we only changed the values of our savestate!
Now, let's execute loading:

maxScore = saveFile.data.maxScore;
highestLevel = saveFile.data.highestLevel;

Done!
Now flash will change any not saved value to anything we defined in the loadvars.

Michael Williams September 9, 2009 at 12:20 pm

That took me a few minutes to work out exactly what was going on, but actually that’s a really neat way of doing things, FlashN00b! Thanks for sharing it :)

So basically you just set loadVars[2] to the highest score and loadVars[4] to the best level, and these are then automatically saved to (and can be retrieved from) saveFile.data[maxScore] and saveFile.data[highestLevel]?

FlashN00b September 9, 2009 at 1:03 pm

no, not exactly.
I actually just use them as “fillers” if saveFile.data[maxScore] or saveFile.data[highestLevel] aren’t in the savefile.
If they aren’t I just set saveFile.data[maxScore] to loadVars[2] and/or saveFile.data[highestLevel] to loadVars[4].

Well, anyways I don’t know a way how I could save those last 2 lines.
Since we just have a pretty small game that doesn’t matter, but in my game (i told you, it has tons of lines) it sure is annoying to set the variables manually.
Can’t you tell me a way of how I can use something like “set” and “eval” in AS 3?

Michael Williams September 10, 2009 at 2:48 pm

Oh, I see. Unfortunately there’s no eval in AS3 — the closest we’ve got is the code you wrote in this format: someObject["variableName"] = "whatever". What does the set function do?

Maybe you could write a function like this: setDefaults( "maxScore", 0, "highestLevel", 0, "playersName", "Guest", "bestTime", 0 ) that can take as many arguments as you like and sets them all using the same kind of code as you shared.

FlashN00b September 10, 2009 at 7:16 pm

Set allows me to use strings to set a variable (just like “=” does, but I can call the variable by a string).

Yu-Chung Chen September 11, 2009 at 1:03 pm

Clear tutorial, thanks.

Is it possible to save local SharedObjects (like above) and upload hi-scores? I’m not sure because that’s another sandbox, isn’t it?

I’m looking into the issue but if you could share any experience for this typical game usage, I’d highly appreciate it.

Cheers,
YC

Michael Williams September 11, 2009 at 1:09 pm

@FlashN00b: but you can do that with square brackets as you’ve done already, can’t you?
E.g. data["bestScore"] = 100;

@Yu-Cheng: If you want to share SharedObjects online, you’ll have to check out Flash Media Server. I’ve never used this, and I don’t know anything about it, I’m afraid. However, you could use a third-party high score leaderboards service, like Mochi Scores.

FlashN00b September 11, 2009 at 1:24 pm

It isn’t possible to save it like this. With highscores… I guess I’m the wrongest guy you could ask, I couldn’t do that in AS 2, so in AS 3 I’m also not able to figure out how I could do this.

FlashN00b September 11, 2009 at 1:34 pm

@Michael
Well I guess this might work, so that I’m currently experimenting with it.
If it works, anything will be fine and I will be able to save & load with ridicolous less lines.

FlashN00b September 11, 2009 at 1:40 pm

Gotcha!
This code works fine for execute loading:

for(var i = 1; i <= loadVars.length-1; i += 2)
{
      if(!isFinite(this[loadVars[i]]))
      {
            this[loadVars[i]] = saveFile.data[loadVars[i]];
      }
}

This worked fine for me. He executed loading just as I wanted it.

FlashN00b September 11, 2009 at 1:43 pm

Sorry for spamming another code here, but if anyone’s having difficulty, here’s how to save:

public function save()
{
      for(var i = 1; i <= loadVars.length-1; i += 2)
      {
            saveFile.data[loadVars[i]] = this[loadVars[i]];
      }
}

Nik February 12, 2010 at 3:35 am

i add a button to restart/ clear the hiscore, using this code under gameover.as:

public function GameOverScreen() 
        {
            Mouse.show();
            restartButton.addEventListener( MouseEvent.CLICK, onClickRestart );
            clearButton.addEventListener( MouseEvent.CLICK, onClickClear );
            sharedObject = SharedObject.getLocal( "mjwScores" );
        }

    public function onClickClear( mouseEvent:MouseEvent ):void 
    {
        sharedObject.clear();
        sharedObject.data.bestScore = 0
    }</pre>

But how to updatedisplay the hi score to "0" at gameoverscreen so the player can get a feedback??

thank you

Michael Williams February 13, 2010 at 2:25 am

@FlashN00b: Somehow I missed your comments last September! Sorry. Cheers for posting them :)

@Nik: You’ll have to change the value of bestScore.text when the player hits the Clear button.

Chenee A. Albo March 17, 2010 at 11:45 am

i have a problem with my software project in flash can you hel me..
please respond

Jan March 19, 2010 at 12:03 am

Hey MJW, great tutorial!! I like how it wraps up AS3 development so concisely and instructively.

I was curious – Flash Media Server seems really heavyweight… and expensive. Do you think it’s possible to find use php or some way to ‘POST’ the sharedObject over to a server in order to synchronize players over the ‘net?

Michael Williams March 20, 2010 at 12:00 pm

@Chenee: That’s… that’s pretty vague. Going to need more info.

@Jan: Thanks! I have to admit — I don’t know much about that topic. Have you seen GamerSafe? It’s a service that’ll let you keep savegames in the cloud. Check it out :)

Takahami November 1, 2010 at 3:29 am

Very nice tutorial. Well written, easyly to understand. Thank you and keep up the good work.
Best regards,
Takahami

oh while reading the comments something popped my mind. i havn´t tested your tutorial yet but i think i can handle it, still…
i wonder if it is possible to store whole objects inside the SharedObject. if it is possible, this might save a lot of work for many people i guess.

something like

sharedObject.data.inventory = playerChar.getInventory();

and getInventory() is likely to return an object containing all inventorydata etc. well, i will give it a try tomorrow. too late tonight.

hm, livedocs says “Each attribute can be an object of any ActionScript or JavaScript type”. but if you create your own .as-class… well, tomorrow will show i guess, but i got a bad feeling about this.

ChubbyApe July 19, 2011 at 5:49 pm

MichaelJWilliams,

I see this tutorial goes back a couple years, but I have a couple questions about sharedObject that pertain to app development for iOS and Android. My game is still in the works but I’m trying to look into the road ahead for final development solutions for score tracking. You mention the data gets saved on the users hard drive where flash player is installed. But if I port the game for Android, it’s no longer a .swf playing in a browser, but a .APK file that is packaged with all the data and files used to create it.

So my question is, will sharedObject still work as a packaged form for mobile devices running iOS or Android? If not, do you know of any other solutions to track data specifically for .swfs that get packaged for mobile? I’ve been scouring the internet for solutions and keep running into sharedObject, but don’t know if it will work for my needs. When I get to that point I’ll definitely give it a shot and let you know otherwise. But in advance I’m looking for a solution already in use that works.

Thanks for the great tutorial. It has helped me expand my AS3 knowledge.
-ChubbyApe

suppressor March 26, 2013 at 1:35 pm

Hi there everyone, it’s my first go to see at this site, and paragraph is truly fruitful designed for me, keep up posting such posts.

Leave a Comment

Writing code? Write <pre> at the start and </pre> at the end to keep it looking neat.

Anti-Spam Protection by WP-SpamFree

{ 3 trackbacks }

Previous post:

Next post: