I know that if I want to store a custom class with SharedObject, I have to use registerClassAlias.
registerClassAlias("MyClass", MyClass);
sharedObject.data.myObject = new MyClass();
But in my case, I have a custom class whose fields are themselves instances of custom classes. How can I store it in such a way as to recover the types when I load the data?
Specifically, the class in question is a Graph class which contains an array of Objects. This isn't the actual code, just an overview:
class Graph {
public var vertices : Array;
}
I have an instance of this Graph class, and I'm filling its vertices field with instances of another class, called Node. I need to store this Graph instance in such a way that I can:
Recover it as a Graph instance.
Access the vertices field of this recovered instance, and then access the elements of that array as Node types.
I've tried throwing some registerClassAlias("Node", Node)'s in appropriate-seeming places, but it's not having any effect. Is there a way to do this?
With more complex data such as this, your best bet is to define load() and save() methods manually on objects that you want to store in a SharedObject. Those methods will define what data should be saved, simplified as much as possible, and collate it into a format of your choice such as JSON.
In this example, your Graph could have a method save() which looks like this:
public function save(name:String, sharedObject:SharedObject):void
{
var list:Array = [];
for each(var node:Node in vertices)
{
// Add a simple object defining the important Node properties
// to the array we will save as JSON.
list.push({ x: node.x, y: node.y });
}
sharedObject.data[name] = JSON.encode(list);
}
And then a load() function like so:
public function load(name:String, sharedObject:SharedObject):void
{
// Empty current list of vertices.
vertices = [];
var list:Array = JSON.decode(sharedObject.data[name]);
for each(var def:Object in list)
{
// Create real Node from simpler definition.
var node:Node = new Node();
node.x = def.x;
node.y = def.y;
vertices.push(node);
}
}
Which would be utilized like:
existingGraph.save('myGraph', sharedObject);
var newGraph:Graph = new Graph();
newGraph.load('myGraph', sharedObject);
Related
I'm working making my first foray into the exciting world of byteArrays!
End Goal
I want to save the positions and other properties of each game element (in this case, blocks in a breakout clone) as part of a level design feature for my app, and also to more easily design levels for the game.
Current Approach
Convert data from a Vector of custom class instances (the bricks) into a ByteArray and save that data to a text file. It seems like this is working fine up to this point (can't be sure until I successfully extract that data back into a Vector object, because the text file that gets saved is pure gobbledygook).
I load a level design by reading the text file into a byteArray and then doing writeObject() into a Vector with the intention of now having a vector that contains all the bricks (this is not working).
The Problem
When I try to run my load function, the file loads, and the byteArray gets "filled" with data, but when I try to do writeObject, I get all these errors(one copy of the following errors for each brick in the vector).
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92cdcb9 to flash.geom.Point.
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92cde09 to flash.geom.Point.
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92df041 to flash.geom.ColorTransform.
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92df161 to flash.geom.Point.
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92df281 to flash.geom.Point.
TypeError: Error #1034: Type Coercion failed: cannot convert Object#92df431 to flash.media.SoundTransform.
TypeError: Error #2004: One of the parameters is invalid.
My custom brick class is an extension of the Sprite class. But it additionally has properties that depend on Point and ColorTransform objects. Oddly, nowhere in my custom class do I have any reference to or use of SoundTransform... so that error seems glaringly odd. I'll post my custom class if anyone wants to look at it.
My Save and Load Methods
private function saveLevelDesign(brVec:Vector.<LineTestBlock>):void{
trace("save");
var file:File = File.documentsDirectory;
file = file.resolvePath("AnimationFiles/brickLevels/lev_001.txt");
fileStream.open(file,FileMode.WRITE);
var bytes:ByteArray = new ByteArray();
bytes = brickArrayToByteArray(brVec);
//fileStream.close();
}
private function loadLevelDesign():void{
trace("loadLevelDesign");
var file:File = File.documentsDirectory;
file = file.resolvePath("AnimationFiles/brickLevels/lev_001.txt");
fileStream.open(file,FileMode.READ);
file.addEventListener(IOErrorEvent.IO_ERROR,ioError);
file.addEventListener(Event.COMPLETE, loaded);
file.load();
//fileStream.open(file,FileMode.READ);
}
private function ioError(ioE:IOErrorEvent):void{
trace("oops",ioE);
}
private function loaded(e:Event):void{
trace("loaded");
var bytes:ByteArray = new ByteArray();
fileStream.readBytes(bytes);
trace(bytes.length,"bytes length"); // 0 bytes length
var vec:Vector.<LineTestBlock> = new Vector.<LineTestBlock>;
for (var i:int = 4; i < _playerTurn._brickArray.length; i++){
vec.push(_playerTurn._brickArray[i]);
}
bytes.writeObject(vec);
trace(bytes.length,"bytes length"); // 53516 bytes length
destroyBricks(_playerTurn); // just removes all bricks on the stage
vec = byteArrayToBrickArray(bytes); // calling this function throws all those errors
trace("vector length:",vec.length); // vector length 208 (this is the correct number of bricks, so that's good)
}
My Byte Conversion Methods
private function byteArrayToBrickArray(bytes:ByteArray):Vector.<LineTestBlock>{
bytes.position = 0;
var blocks:Vector.<LineTestBlock> = bytes.readObject() as Vector.<LineTestBlock>;
trace(bytes.position);
return blocks;
}
private function brickArrayToByteArray(brVec:Vector.<LineTestBlock>):ByteArray{
var bytes:ByteArray = new ByteArray();
/*for (var i:int = 0; i < brVec.length; i++){
if (brVec[i]._break == true){
bytes.writeObject(brVec[i]);
}
}*/
bytes.writeObject(brVec);
return bytes;
}
Anyone see if I doing something wrong, or not understanding something?
Any object that implements IExternalizable or is not a DisplayObject can be saved in a ByteArray and restored from one, if you write both readExternal and writeExternal methods correctly. If an object does not implement IExternalizable, Flash will attempt to write it using public components visible to the code, and read it by assigning values read to public properties in the same order. Normally you should use the interface with anything that's more complex than a Vector.<int>. Therefore, you need to implement IExternalizable in your LineTestBlock class, writing and reading only those properties that are required. Also, you can only use this method with objects that have an empty constructor, because in IDataInput.readObject the object is first constructed, then values are assigned.
The manual on IExternalizable. For some reason you can't access it from the normal class tree, but it is there and the interface is working.
I'd change your approach by encapsulating all the vectors, SoundTransforms etc into a single class, say Level, then implement IExternalizable in it, which will then write all the simple data types in order (remember to write vector's lengths before the data!) when asked to, then read itself from a byte array and reconstruct all the internal data structure in the meantime. An example:
import flash.utils.*;
public class Level implements flash.utils.IExternalizable
{
private var blocks:Vector.<LineTestBlock>;
// something extra
public function writeExternal(output:IDataOutput):void {
var l:int=blocks.length;
output.writeInt(l);
for (var i:int=0;i<l;i++) {
//write all the blocks[i]'s properties in order to output
}
// same approach to write all the extra properties
}
public function readExternal(input:IDataInput):void {
var l:int=input.readInt();
blocks=new Vector.<LineTestBlock>();
for (var i:int=0;i<l;i++) {
// first read all the properties into any local variables in the VERY SAME order they were written
// then create an instance of LineTestBlock
var block:LineTestBlock=new LineTestBlock(...);
// required parameters for the constructor should be read before creating object
// then fill remaining properties to the created instance
blocks.push(block); // and reconstruct the array
}
// same approach to every property that was saved
// reconstruct everything else that's depending on the data read
}
}
And finally, you would likely need to perform a flash.net.registerClassAlias() call somewhere in your app's initialization to have your Level be recognized as a serializable class.
bytes.readObject() return an Object.
so problem is about convertin Object to Vector.<LineTestBlock> so you have to convert it your self
private function byteArrayToBrickArray(bytes:ByteArray):Vector.<LineTestBlock>{
bytes.position = 0;
// Edit : readObject() only presents an Object
var blocks:Object = bytes.readObject();
trace(bytes.position);
/* you have to convert all step by step
at first we have to assume blocks as a vector
best way to searching its items is using _for key in_
*/
var converted:Vector.<LineTestBlock> = new Vector.<LineTestBlock>(blocks.length);
for (var key:String in blocks) {
converted.push(objectToLineTestBlock(blocks[key]));
}
return converted;
}
as i dont know structure of your LineTestBlock class, i cant provide "objectToLineTestBlock" function exactly
Here is an Example that simulates your LineTestBlock Class
my own LineTestBlock Class
public class LineTestBlock
{
public var w:int;
public var loc:Point;
public var stf:SoundTransform;
public function LineTestBlock(_w:int, _loc:Point, _stf:SoundTransform)
{
w = _w;
loc = _loc;
stf = _stf;
}
}
main class that testing the solution.
what i do is just converting all Objects to what really they are
bytearray.readObject() convert all classes to pure Objects
public class ByteTest extends Sprite
{
public function ByteTest()
{
var save_vector:Vector.<LineTestBlock> = new Vector.<LineTestBlock>();
var block_item1:LineTestBlock = new LineTestBlock(200, new Point(-1, 1), new SoundTransform(0.5));
var block_item2:LineTestBlock = new LineTestBlock(400, new Point(-2, 2), new SoundTransform(0.25));
save_vector.push(block_item1);
save_vector.push(block_item2);
var load_vector:Vector.<LineTestBlock>;
var bytes:ByteArray = new ByteArray();
bytes.writeObject(save_vector);
// trace(bytes.position);
load_vector = objectToLineTestVector(bytes);
// now test to check if everything is OK
trace(load_vector[1].stf.volume); // must print 0.25
}
public function objectToLineTestVector(bytes:ByteArray):Vector.<LineTestBlock> {
bytes.position = 0;
var loadedObject:Object = bytes.readObject();
var blocks:Vector.<LineTestBlock> = new Vector.<LineTestBlock>();
for (var key:String in loadedObject) {
blocks.push(objectToLineTestBlock(loadedObject[key])); // loadedObject[key] is a block_item1 and could be converted
}
return blocks;
}
public function objectToLineTestBlock(obj:Object):LineTestBlock {
return new LineTestBlock(obj.w, objectToPoint(obj.loc), objectToSoundTransform(obj.stf));
}
public function objectToPoint(obj:Object):Point {
return new Point(obj.x, obj.y);
}
public function objectToSoundTransform(obj:Object):SoundTransform {
return new SoundTransform(obj.volume);
}
}
Well basically as the title implies, I can't save my array to the shared object.
I have an array which contains different "soldiers" with different characteristics(Health,Armor,Weapon,Position,Exp,Level) etc etc and was wondering how I would go about saving it. When I reload the swf I get this trace (",,,") but before I reload it I get a correct array reading.
This is my code if it helps:
//Saving game
function saveGame(E:MouseEvent){
var so:SharedObject = SharedObject.getLocal("saveFile"); //Instantiating the shared object
so.data.savedUnitArray = towerDefenceMain.unitArray;// is the array that stores the Soldiers
trace(so.data.savedUnitArray); //returns correct trace
so.flush();//Saving the operation
}
//Loading the data back
var so:SharedObject = SharedObject.getLocal("saveFile");
if(so.data.savedUnitArray != undefined){
unitArray = so.data.savedUnitArray;
trace(unitArray); //returns (",,,,")
}
In order to save a custom object, you either have to make all its properties public and accessible, AND have no references to DisplayObjects, or implement IExternalizable and define writeExternal() and readExternal() methods. Note that if your object is read from elsewhere, it first is initialized via zero parameter call to its constructor, and then the instance's readExternal() is invoked.
The manual on IExternalizable
An example:
public class Tower2 extends Obstacle implements gameRunnable,IExternalizable {
// HUGE set of statistics skipped, all methods skipped
public function writeExternal(output:IDataOutput):void {
output.writeInt(gemType);
output.writeInt(gemGrade);
output.writeBoolean(affectsFlying); // as some gems might be elongated, saving this
output.writeInt(_targetMode); // placeholder for targetting
output.writeInt(kills);
// hehe, what else to write in here? Everything else is derivable
}
public function readExternal(input:IDataInput):void {
var gt:int = input.readInt();
var gg:int = input.readInt();
MakeGem(gt, gg); // this is the function that initializes everything that's the tower
raised = true; // will place manually if ever
affectsFlying = input.readBoolean();
gt = input.readInt();
SetTargetting(gt);
kills = input.readInt(); // kills
updateDamage(); // this updates damage respective to kills counter
}
So, for your Soldiers you only need to save essential data, and re-create everything else once you load your set of soldiers from the shared object.
I have an array of Widgets (a class I created) called "widgetArray" that I save into a shared object.
savedGame1.data.widgetArray = widgetArray;
When I go to load this data out and use the widgets I run into problems. The array is stored fine, but each widget is saved as an object. So I can't simply do:
widetArray = savedGame1.data.widgetArray;
because then if I try to use the widgets:
widgetArray[0].someWidgetFunction();
it does not work because flash thinks they are objects. I have tried typecasting the widgets when I load them, but it only produces a null object.
widgetArray[i] = widgetArray[i] as Widget;//widgetArray[i] becomes null
tempWidget:Widget = widgetArray[i] as Widget;//null as well
This question is asked in one of the forums from a friend, since there were no responses, i thought this is the right place for it...
How does anyone else deal with this?
Make two methods that can save and load a widget, the save method should take the data from the widget class and save it into the shared object the load class will take the data from the shared object and set the properties of the object.
Something like this :
public function save():Object
{
return {
"x":this.x,
"y":this.y,
"otherStuff":otherStuff
};
}
public function load(data:Object):void
{
this.x = data.x;
this.y = data.y;
this.otherStuff = data.otherStuff;
}
You can call the save method and store the results in an array and then store it in the shared object. You only need to save the data that is required to rebuild the widget class, not all the properties of the class.
Edit : Updated based on BlueRaja's comment.
As BlueRaja pointed out IExternalizable is meant to be used for this.
If you have a class like this :
public class MyClass implements IExternalizable
{
private var one:int = 1;
private var two:int = 2;
public function MyClass()
{
}
public function writeExternal(output:IDataOutput):void
{
output.writeInt(one);
output.writeInt(two);
}
public function readExternal(input:IDataInput):void
{
one = input.readInt();
two = input.readInt();
}
public function print():void
{
trace(one);
trace(two);
}
}
Then you can save it like this:
registerClassAlias("MyClass", MyClass);
var sharedObject:SharedObject = SharedObject.getLocal("so");
var myClass:MyClass = new MyClass();
sharedObject.data['storedObject'] = myClass;
sharedObject.flush();
Then to load it :
registerClassAlias("MyClass", MyClass);
var sharedObject:SharedObject = SharedObject.getLocal("so");
var loadedClass:MyClass = sharedObject.data['storedObject'] as MyClass;
loadedClass.print();
Hope that helps.
http://tush.wordpress.com/2007/07/08/actionscript-3-serializing-classes-using-registerclassalias/ ... This is also a genuine way to store the custom class objects in shared objects
I have a very simple test going with shared objects in flex with mobile I have a person class.
package
{
import flash.display.MovieClip;
public class Person extends MovieClip
{
var personsname:String="";
public function Person(name:String)
{
personsname = name;
}
}
}
And then some simplish code in a view.
var person1:Person;
var person2:Person;
var person3:Person;
var person4:Person;
var thePeople:Array=[];
var so:SharedObject;
function init():void{
person1 = new Person("james");
person2 = new Person("mike");
person3 = new Person("Amanda");
person4 = new Person("Shelly");
thePeople.push(person1,person2,person3,person4);
//so = SharedObject.getLocal("savedData"); //clear it again
///so.clear(); // clear it again
savePeople();
getPeople();
}
private function savePeople():void{
so = SharedObject.getLocal("savedData");
if(so.data.thePeopleArray == null){
so.data.thePeopleArray = thePeople;
so.flush();
}
}
private function getPeople():void{
so = SharedObject.getLocal("savedData");
var thePeeps:Array = so.data.thePeopleArray;
trace(thePeeps);
}
The first time I run this it traces out
[object Person] 4 times
I close the emulator and rebuild and run it traces out
,,,
If I clear out the so it show the [object Person] again, but comment out get the ,,,
Can shared objects even store an array of objects properly. It is the same with the persistanceManager I believe.
The root of the problem here is that you are trying to save an instance MovieClip into the SharedObject. Since the MovieClip is an intrinsic object (native to flash) it cannot be converted into a form which can be stored. This causes flash to convert the data into a generic Object which is stored to disk. I can only guess at exactly what is going into the SharedObject at this point.
It seems to work the first time because flash does not actually load the shared object in the getPeople() call, it just uses the object which is already in memory. The second time the app runs it reads the generic object from disk and creates a generic object.
There is another problem which is that the flash player does not know to pass data to the constructor when it reads the object.
There are a few possible workarounds, some are:
Store the data as text
Store the data as a ByteArray
Store the data in a "Data Object"
Each of these requires some conversion during the read and write process, but this can be simplified using an interface. This also adds flexibility in case your object changes you will still be able to read the data in the SharedObject.
1: Text
As an example, you might add two methods to the Person object, call them serialise() and deserialise(). The serialise() method will return text which can be stored in the shared object. The deserialise() will parse text and populate the values of the object.
Here's a sample to illustrate this:
class Person {
private var name:String;
private var age:int;
public function serialise():String {
return [name, age].join("\t");
}
public function deserialise(input:String):void {
var tokens:Array = input.split("\t");
name = tokens[0];
age = parseInt(tokens[1]);
}
public static function create(name:String, age:int):Person
{
var output:Person = new Person();
output.name = name;
output.age = age;
return output;
}
}
For ease of use we can create a class for managing a collection of people:
class People {
private var people:Vector.<Person> = new Vector.<Person>();
public function clear():void {
people.splice(0, people.length);
}
public function add(person:Person):void {
people.push(person);
}
public function serialise():String {
var output:Array = [];
for each (var person:Person in people)
output.push(person.serialise());
return output.join("\n");
}
public function deserialise(input:String):void {
var tokens:Array = input.split("\n");
for each (var token:String in tokens) {
var person:Person = new Person();
person.deserialise(token);
add(person);
}
}
public function save():void {
var so:SharedObject = SharedObject.getLocal("cookie");
so.data.people = serialise();
so.flush();
}
public function load():void
{
var so:SharedObject = SharedObject.getLocal("cookie");
if (so.data.people != null)
deserialise(so.data.people);
}
}
Usage:
var people:People = new People();
people.load();
trace(people.serialise());
people.clear();
people.add(Person.create("Candy", 21));
people.add(Person.create("Sandy", 23));
people.add(Person.create("Randy", 27));
people.save();
trace(people.serialise());
An obvious flaw in this example is that the \n and \t characters cannot be used as part of the data (ie for the name of a person). This is a common short-coming with text data.
** Update: Look into the built-in JSON methods for a consistent approach to serialising objects to and from text.
2: ByteArray
Very similar to the text method described above, except the serialise/deserialise methods would accept an additional parameter of a ByteArray, which the object would write to. The ByteArray would then be saved and loaded from the shared object. The advantages of this method is that resulting data is usually is compact and versatile than the text method.
Flash also defines the IDataInput and IDataOutput interface which can be used here.
3: Data Objects
If you still prefer the storing objects directly, then you could create a proxy object which serves the sole purpose of carrying data. A data object (aka DO) is a an object which only has variables, and not methods. Eg:
class PersonDO {
public var name:String;
}
It would be used something like this:
var person2:Person;
var person3:Person;
var person4:Person;
var thePeople:Array=[];
var so:SharedObject;
function init():void{
person1 = new Person("james");
person2 = new Person("mike");
// store the people data into data objects
person1DO = new PersonDO();
person1DO.name = person1.name;
person2DO = new PersonDO();
person2DO.name = person2.name;
thePeople.push(person1DO,person2DO);
savePeople();
// load the people into data objects
getPeople();
person1 = new Person(thePeople[0].name);
person2 = new Person(thePeople[1].name);
private function savePeople():void{
so = SharedObject.getLocal("savedData");
if(so.data.thePeopleArray == null){
so.data.thePeopleArray = thePeople;
so.flush();
}
}
private function getPeople():void{
so = SharedObject.getLocal("savedData");
var thePeeps:Array = so.data.thePeopleArray;
trace(thePeeps);
}
Even though this may appear simpler than the alternatives there are downsides to storing objects directly:
- Stored data is very fragile - if you change the object then your data will become unusable unless you have several versions of each object.
- You need to ensure that a reference to the data object is compiled into the application.
- A common usage scenario for Shared Objects is to save data objects from one SWF, and load them in another. You need ensure that both SWFs use identical version of the class being saved and loaded.
Hope that helps.
I have a game with a variety of ship types. My Ship class has a static array holding one of each of the types in it. Whenever I make a new Ship (other than when initializing this array), I want to make it a clone of one of the existing Ship objects in my prototype array.
1 - How can I run through all the properties in one Ship object and assign them to a second Ship object?
2 - How can I see if a property is an object or a basic type like String or int? Some of the objects in my Ship class need to be cloned, and some are just references that need to stay the same.
One option, arguably the most agile, would be to define clone methods for each class that you need to clone, such as:
class Ship
{
public var prop1:Number;
public var otherClassInstance:OtherClass;
public function clone():Ship
{
var result:Ship = new Ship();
result.prop1 = this.prop1;
result.otherClassInstance = this.otherClassInstance.clone()
}
}
class OtherClass
{
public var prop1:Number;
public function clone():OtherClass
{
var result:OtherClass = new OtherClass();
result.prop1 = this.prop1;
}
}
Another option is to clone an object by using the ByteArray class like this example from the Adobe documentation:
function clone( source:Object ):*
{
var myBA:ByteArray = new ByteArray();
myBA.writeObject( source );
myBA.position = 0;
return( myBA.readObject() );
}
I've seen instances where this approach does not work for cloning instances of custom classes, specifically view classes like Sprites.
Another approach is to use describeType from the flash.utils package. With describeType you can iterate through the properties of an object.
Here is an example of using describeType to inspect the properties of an object that is a part of a utils lib I wrote.
As for checking the type of the property, you can use describeType or you can also use the is operator like this:
if( myObj is SomeClass )
{
}
if( myObj is OtherClass )
{
}
To run through all the properties of one ship object and assign them to a second:
shipobj1:Ship = new Ship();
//set values for all shipobj1 properties
shipobj2:Ship = new Ship();
for (item in shipobj2)
item = shipobj1[item];
Checking if a property value is an object you could use typeof. The limitation of this is that there are only 6 possible types returned: boolean, function, number, object, string, and xml. So for example if you need to know if a property is an array you can't really do that with typeof since that would actually return "object" since "array" isn't one of the 6 options, but if you're just concerned with identifying simple types like numbers and strings versus other stuff it should do the trick:
if(typeof item == "object")
// do whatever with object
else if(typeof item == "string")
// do whatever with string
//etc, etc.
EDIT: Replaced variable "var" with "item" since var is a reserved word.