0

I am attempting to use the 'File' function in ActionScript 3 to save the following information:

I have varying draggable display objects in the scene, the amount and type can vary. I want to save the amount and their position and then load them back in a future session.

I am struggling to use File to save anything, I have searched the Adobe documentation and cannot get my head round how to use it.

I have not yet developed any code using it.

Any help would be appreciated.

Thank you.

Adam McKenna
  • 2,295
  • 6
  • 30
  • 41
  • `File` class is for AIR, use `FileReference` in order to save something to disk, and you'd better use SharedObjects to save Flash-specific data. – Vesper May 08 '15 at 13:47
  • I'm using AIR for my app – Adam McKenna May 08 '15 at 13:48
  • @Vesper, can I use SharedObjects even if my application is using AIR? – Adam McKenna May 08 '15 at 13:57
  • You can use SharedObject with AIR. But first in what format do you want to save that scene? Binary? XML or else to rebuild the scene later? – BotMaster May 08 '15 at 15:04
  • @BotMaster I want to be able to rebuild the scene later, if that's possible – Adam McKenna May 08 '15 at 15:21
  • So someone can answer, please update your question to include the following: What exactly is your struggle with the `File` class? are you getting an error? an unexpected result? Please include your current applicable code, and perhaps elaborate more on what you are trying save (do you just have a bunch of display objects on the screen who's position you want to remember?) – BadFeelingAboutThis May 08 '15 at 16:56
  • @LDMS there are draggable objects on the screen, it will always vary how many and what objects will be on, etc. As for the code, I've not yet developed any because, as stated, I don't know how to implement the function. – Adam McKenna May 08 '15 at 18:58
  • I updated your question, please refine further if I got something wrong. – BadFeelingAboutThis May 08 '15 at 19:44
  • I used Vesper's solution. – Adam McKenna Dec 06 '15 at 22:50

5 Answers5

2

You are trying to write a DisplayObject into the file directly, this is prevented by Flash engine due to the way Flash handles default serialization of any object. In order to save a DisplayObject into the external resource, you need to employ IExternalizable on that object's class and any class of objects you will plan to store as well. The implementation of writeExternal should save all data required to rebuild the said object from scratch, and readExternal should also employ methods to restore the integrity of said DisplayObject by performing addChild() on nested display objects, or adding them into other internal structures that object might contain.

Note, other answers contain valid points for doing a custom serialization with XML or JSON, and also contain links to requires import, in particular, flash.utils.registerClassAlias and flash.utils.getDefinitionByName are gravely needed to recreate the structure from a serialized data chunk.

An example: Let's say you have a drawing board in a Board class, and a set of rectangles that you can drag by using mouse, that differ by size and color. Rectangles are custom made MovieClips and don't have a class of their own, but each MovieClip is also assigned a color property to simplify their distinction. This means you need to implement IExternalizable on Board class only. Let's also assume Board class has a pieces array that contains all links to nested rectangles, and a method to create a new properly sized rectangle based on width, height and color supplied as parameters. (There might be more requirements to the data structure of Board to meet in your case, so watch closely) So, the process of serializing Board will be to collect all the data from nested MCs and stuff it in order into IDataOutput supplied, and the process of restoring an instance of Board should retrieve stored data, parse it to find what is where, create the nested MCs to be the same like they've been stored, position them properly, addChild() to self and rebuild thepieces` array.

public class Board extends Sprite implements IExternalizable {
    private var pieces:Array;
    public function createRectangle(_width:Number,_height:Number,color:uint):MovieClip {
        var mc:MovieClip=new MovieClip();
        mc.graphics.beginFill(color);
        mc.graphics.drawRect(0,0,_width,_height);
        mc.graphics.endFill();
        mc.color=color;
        pieces.push(mc);
        return mc;
    }

A refinement to data structure is already visible - you need to store the passed _width and _height in the MC somewhere, because the actual width of that MC will differ from what's passed by the default line thickness (1, 0.5 on either side). x and y are properly retrieved from MC's properties, though. So, adding both lines into createRectangle is necessary.

mc._width=_width;
mc._height=_height;

With this, serializing the Board becomes more easy.

public function writeExternal(output:IDataOutput):void {
    var pl:int=pieces.length; // cache
    output.writeInt(pl); // assuming we keep this array in integral state
    for (var i:int=0;i<pl;i++) {
        var _mc:MovieClip=pieces[i];
        output.writeDouble(_mc.x); // this is usually not rounded when dragging, so saving as double
        output.writeDouble(_mc.y);
        output.writeDouble(_mc._width);
        output.writeDouble(_mc._height);
        output.writeInt(_mc._color);
    }
    // if anything is left about the "Board" itself, write it here
    // I'm assuming nothing is required to save
}

To restore, you need to read the data out of IDataInput in the very same order as it was written in writeExternal and then process to rebuilding the display list we've stored.

public function readExternal(input:IDataInput):void {
    // by the time this is called, the constructor has been processed
    // so "pieces" should already be an instantiated variable (empty array)
    var l:int;
    var _x:Number;
    var _y:Number;
    var _width:Number;
    var _height:Number;
    var _color:uint;
    // ^ these are buffers to read data to. We don't yet have objects to read these into
    input.readInt(l); // get pieces length
    for (var i:int=0;i<l;i++) {
        input.readDouble(_x);
        input.readDouble(_y);
        input.readDouble(_width);
        input.readDouble(_height);
        input.readInt(_color);
        // okay we got all the data representing the rectangle, now make one
        var mc:MovieClip=createRectangle(_width,_height,_color);
        mc.x=_x;
        mc.y=_y;
        addChild(mc); // createRectangle does NOT have addchild call
        // probably because there are layers for the parts to be added to
        // I'm assuming there are no layers here, but you might have some!
        // pieces array is populated inside createRectangle, so we leave it alone
    }
    // read all the data you have stored after storing pieces
}

In case your nested MCs have a class that also implements IExternalizable, you can save the entire array in a single instruction, writeObject(pieces), this will make Flash walk through the array, find all data it contains and call writeObject on any nested object, essentially calling that class's writeExternal function for each of the instance in the array. Restoring such an array should include rebuilding the display list by walking the array and calling addChild() on each of the restored instances.

And last but not the least, registerClassAlias() should be called prior to doing any serialization or deserialization of custom objects. Best place to call these is probably your main object's constructor, as this will surely be called before any other code your application contains.

Vesper
  • 18,599
  • 6
  • 39
  • 61
  • "Stuff it in order"... can I instead stuff it in order directly into a bin file using a FileStream? This approach (FileStream) worked fine for setting circle shapes all over the board (save and then load from that same file) but the color value always gets saved as 0. I've tried `stream.writeUnsignedInt` and `writeInt`. I get no errors but the circles end up black and the value that gets saved is 0. – Neal Davis Nov 24 '16 at 17:50
  • 1
    In fact, `writeExternal()` does the stuffing into anything supplied that is an `IDataOutput`, therefore, if you would call `FileStream.writeObject()` this method would be invoked. And about your colors not restoring, it's possible that you needed to redraw the circle shape using correct fill. You seem to not check if you've read a zero out of a filestream, right? – Vesper Nov 26 '16 at 04:39
1

Assuming all your objects to save belong to the same parent, you could dosomething along these lines:

First, create a class file (let's call is SaveData.as and put it in the root of your project directory). This will describe the data you want to save:

package 
{
    import flash.geom.Rectangle;

    public class SaveData 
    {
        public var bounds:Rectangle; //to save where an object is on the stage
        public var classType:Class; //to save what kind of object it is

        //you could add in more proterties, like rotation etc


        public function SaveData() {

        }
    }
}

Next, on your save function, do something like this:

    //this will hold all your data
    //a vector is the same as an array only all members must be of the specified type
    var itemList:Vector.<SaveData> = new Vector.<SaveData>();

    //populate the array/vector with all the children of itemContainer
    var tmpItem:SaveData;
    //loop through all children of item container
    for (var i:int = 0; i < itemContainer.numChildren; i++) {
        tmpItem = new SaveData(); //create a new save record for this object
        tmpItem.bounds = itemContainer.getChildAt(i).getBounds(itemContainer); //save it's bounds
        tmpItem.classType = getDefinitionByName(itemContainer.getChildAt(i)) as Class; //save it's type
        itemList.push(tmpItem);  //add it to the array
    }

    //Now you have an array describing all the item on screen

    //to automatically serialize/unserialize, you need this line (and you need to register every class nested in SaveData that isn't a primitive type - which would just be Rectangle in this case
    registerClassAlias("SaveData", SaveData);
    registerClassAlias("flash.geom.Rectangle", Rectangle);

    //create a new File to work with
    var file:File = File.applicationStorageDirectory; //or whatever directory you want
    file.resolvePath("saveData.data"); //or whatever you want to call it
    var fileStream:FileStream = new FileStream();
    fileStream.open(file, FileMode.WRITE);
    fileStream.writeObject(itemList); //write the array to this file
    fileStream.close();

Now, to load it back in:

    var itemContainer:Sprite = new Sprite(); //however you initialize this
    addChild(itemContainer);

    var file:File = File.applicationStorageDirectory;
    file.resolvePath("saveData.data");
    var fileStream:FileStream = new FileStream();
    fileStream.open(file, FileMode.READ);
    var itemList:Vector.<SaveData> = fileStream.readObject() as Vector.<SaveData>;
    fileStream.close();

    //now that you've read in the array of all items from before, you need to recreate them:
    var tmpItem:DisplayObject;
    var tmpClass:Class;
    //loop through all items in the array, and create a object
    for (var i:int = 0; i < itemList.length; i++) {
        tmpClass = itemList[i].classType; //The type of item
        tmpItem = new tmpClass() as DisplayObject; //create the item

        //now move the item to it's former position and scale
        tmpItem.x = itemList[i].x;
        tmpItem.y = itemList[i].y;
        tmpItem.width = itemList[i].width;
        tmpItem.height = itemList[i].height;

        //add the item back to the parent
        itemContainer.addChild(tmpItem);
    }

If you're not sure of the imports, here they are:

import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.net.registerClassAlias;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
BadFeelingAboutThis
  • 14,445
  • 2
  • 33
  • 40
0
var bytes:ByteStream;
var filename:String = "mySaveFile.sav";    

//[...] //initialize byte stream with your data 

//get a reference to where you want to save the file 
//(in this example, in the application storage directory, 
//which is fine if you don't need to move the save file between computers

var outFile:File = File.applicationStorageDirectory;
outFile = outFile.resolvePath(fileName);

//create a file output stream, which writes the byte stream to the file 
var outStream:FileStream = new FileStream();
outStream.open(outFile, FileMode.WRITE);
outStream.writeBytes(bytes, 0, bytes.length);
outStream.close();


//to load the file:
var inFile:File = File.applicationStorageDirectory;
inFile = inFile.resolvePath(fileName);

bytes = new ByteArray();

var inStream:FileStream = new FileStream();
inStream.open(inFile, FileMode.READ);
inStream.readBytes(bytes);
inStream.close();
user45623
  • 621
  • 4
  • 18
  • Thanks for that submission but I don't quite understand what the above code is achieving – Adam McKenna May 08 '15 at 18:59
  • As LDMS says. I don't know whether this is terribly helpful because you haven't described what type of information you are trying to save or what you don't understand about the File class and its operations. If you can give some more detail, I will gladly expand my answer. – user45623 May 08 '15 at 19:11
  • I can't really provide much. As I said I'm trying to save the scene's in some form so that it can be loaded back later. I've been told using File is appropriate because I am using AIR, I don't understand the file class _at all_ and thus any information revelant to the File class would have to be contextualised and explained – Adam McKenna May 08 '15 at 19:13
  • Adam, please describe at least something about the scene information you are trying to save. Is it an image? Object coordinates? Character names? Give us something. – user45623 May 08 '15 at 19:17
  • Various MovieClips that are dynamically added to the scene, alongside an image, text field and a button. – Adam McKenna May 08 '15 at 20:23
  • Depending on the volume of data you need to save and whether you need to be able to find and transfer the save file, you should be able to use a Shared Object ( http://www.republicofcode.com/tutorials/flash/as3sharedobject/ ), or write a file using the methods LDMS or I have provided. If you still don't have any idea how to save the data at this point, you need to do a lot more research and read some tutorials, then come back with a much more focused question. – user45623 May 08 '15 at 21:08
0

I usually use SharedObject, by saving the number of objects with their locations ,scale ,rotation , .. etc. as an array (usually multidimensional array).

this example is tested :

first make a movie clip giving it "mc" as a name in the ActionScript Linkage add any graphics you like (this MovieClip will be the objects to be saved later ) then add the following script

////////// get random values for each object
var speed:Number ;
var yPosition:Number ;
var size:Number ;

this.width = size;
this.height = size;
this.y = yPosition ;




//// Moving the MovieClip from Left to right 
function moving(e:Event):void
{
    this.x += speed ;
    if(this.x > 550)
        {
    this.removeEventListener(Event.ENTER_FRAME,moving);
    MovieClip(parent).removeChild(this);
        }

}
this.addEventListener(Event.ENTER_FRAME,moving);

in the root stage of the project add :

import flash.events.MouseEvent;
import flash.display.MovieClip;




var num:int = 0 ;
var mmc:MovieClip  ;
var mySharedObj:SharedObject = SharedObject.getLocal("SavingStatus"); //// SharedObject to save info


function init()
{
if (!mySharedObj.data.savedArray)
{
    ///// first run No datat saved
    this.addEventListener(Event.ENTER_FRAME,addingmcs)
}else {
    ///// Laoding previusly saved data
    loading();
}
}


init() ;

/////////////// adding MovieClips to stage /////
function addingmcs(e:Event):void
{
num +=1 ;
if(num > 20){
    num = 0 ;
    mmc = new mc ;
    mmc.speed = 2 + (5 * Math.random()) ;
    mmc.yPosition  = 500 * Math.random() ;
    mmc.size  = 50 + 10 * Math.random() ;
    this.addChild(mmc); 
}
}

///////////////////////////////////////////
///////////////////////////////////////////////



var obj:* ;  //// to hold children MovieClips of the stage
var savingArr:Array = new Array ;  //// the array to be saved , Contains all info of the children



////////////// Save all MovieClips with their parameters  ////////////
function saving(e:MouseEvent):void
{
this.removeEventListener(Event.ENTER_FRAME,addingmcs)
for (var i:int=0;i<this.numChildren;i++)
{
    if (this.getChildAt(i)is MovieClip) { ///// add all MovieClips of the stage to the array with their info (position - size - speed ... etc)
     obj = this.getChildAt(i);
        savingArr.push([obj , obj.x , obj.y , obj.speed , obj.size]); //// add the info in 3 dimentional array 
        obj.speed = 0 ;
    }
}
////////////////saving array externally
mySharedObj.data.savedArray = savingArr ;
mySharedObj.flush ();
}
save_btn.addEventListener(MouseEvent.CLICK,saving)

////////////// Load all saved parameters  ////////////

load_btn.addEventListener(MouseEvent.CLICK,loading)

function loading(e:MouseEvent =null):void
{
 savingArr = mySharedObj.data.savedArray ;
for (var i:int=0;i<savingArr.length ; i++)
{
    mmc = new mc ;
    mmc.x = savingArr[i][1] ; ///// Get saved x 
    mmc.yPosition = savingArr[i][2] ; ///// Get saved y 
    mmc.speed = savingArr[i][3] ; ///// Get saved speed
    mmc.size = savingArr[i][4] ; ///// Get saved size
    addChild(mmc);
}
this.addEventListener(Event.ENTER_FRAME,addingmcs) ;
}
kare
  • 147
  • 1
  • 9
0

You already have some answers here but from your question, maybe you are missing the larger context.

So the File class represents a path to a file on disk and the FileStream class enables reading and writing data to that file. These are easy to use and there are many examples on the web. Here is one tutorial from Adobe: Reading and writing files

But what data to write and what is the format and data type? Those are the more important and more interesting questions.

The simplest approach is to use a text based format like XML or JSON where you read and write whatever properties of Sprites (or other objects) you want. One advantage of this is that the resulting file is a human readable/editable text file. A minor disadvantage is that you need to specify which properties to save and restore and deal with simple data type conversions (string to int, etc).

A more robust approach is to use what is called Serialization where the state of an entire object is saved and restored. This is more complicated and while not hard, is probably overkill for your project needs. There are good examples and discussion here , here and here.

For your current project and skill level, I'd suggest using XML orJSON Here's a tutorial using XML: Loading and Processing External XML Files

ChrisF
  • 134,786
  • 31
  • 255
  • 325
spring
  • 18,009
  • 15
  • 80
  • 160
  • Glanced through those discussions on serialization and found no mentions of IExternalizable. Weird. I have found this one to be the best when working with class-based `DisplayObject`s. For example, I have an object of type `Tower` that only requires three fields to fully rebuild itself, so I'm storing only those three in the `writeExternal` call and do all rebuilding in `readExternal` based on the values read. That restores about 40 parameters not including display list. The parent is responsible for other metadata and contains a smaller but still worthy ratio of data rebuilt to data written. – Vesper May 09 '15 at 05:47