AS3 - Clone an object - actionscript-3

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.

Related

Using SharedObject with complicated classes

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);

converting element by using AS operator

So im creating something that now is finished and i want not to create every time elements, but to Pool them (ObjectPooling)
The problem comes that my object from the pool doesnt have the variable from the class it mimics, or at least i understand it that way, cause it doesnt do what it should.
Can someone tell me does this
var myNewBox:Box = Pool_myBox.getSprite() as Box;
mean that all the proparties and parameters that the class Box() has will be given and can be used by the new variable myNewBox or it`s a little more tricky that this?
or in other words is var myNewBox:Box = new Box();
the same as
var myNewBox:Box = Pool_myBox.getSprite() as Box;
------------EDIT-----------
so i do private var Pool_myBox:SpritePool; in the Main Class .
and set Pool_myBox = new SpritePool(Bullet,20); so in the 1st moment it has generated 20 objects.
The whole SpritePool class is this
package {
import flash.display.DisplayObject;
public class SpritePool {
private var pool:Array;
private var counter:int;
private var classRef:Class;
public function SpritePool(type:Class, len:int) {
pool = new Array();
counter = len;
classRef = type;
var i:int = len;
while (--i > -1) {
pool[i] = new type();
}
}
public function getSprite():DisplayObject {
if (counter > 0) {
return pool[--counter];
} else {
increasePool(10);
return getSprite();
}
}
public function increasePool(amount:int):void {
counter += amount;
while( --amount > -1 )
pool.unshift ( new classRef() );
}
public function returnSprite(s:DisplayObject):void {
pool[counter++] = s;
//trace(pool.length)
}
}
}
Absolutely not. If your getSprite() method does not return a Box instance (or some descendant of it), it will not 'inherit' the properties of Box. as is not performing any kind of internal magic - it is simply casting and telling the compiler that you know what you are doing and that the object indeed is a XXX instance (fill in). You should use casting only when going from a more general type to a more specific type, let's assume this:
var child:Sprite = parent.getChildAt(0); //what does this return? A display object instance => compiler will throw an error as Sprite is not DisplayObject
/*
Implicit coercion of a value with static type flash.display:DisplayObject to a possibly unrelated type flash.display:Sprite.
*/
//so you cast it:
var child:Sprite = parent.getChildAt(0) as Sprite; //this won't throw anything cos you casted it correctly
Also note that:
myObj as MyObj
is the same as:
MyObj(myObj)
if Pool_myBox.getSprite returns only Box objects, then you don't need to cast. The getSprite function should be look something like:
public function getSprite():Box {
var recycled_box:Box = ... // get box from pool
return recycled_box;
}
var myNewBox = Pool_myBox.getSprite();
Then, myNewBox will look and act like a Box. Note that any initialization or processing that happened on previous Box instances must be undone when it's returned to the pool if you need a "fresh" instance of Box.
OK, given the pool class, it looks like it should work with casting. Note that your text says you're passing in "Bullet" as the Class, while your code seems to want Box's (I assume this is either a typo, or Bullet is a superclass of Box?). If it works on the first 20, but not after you start recycling, then check what may need to be undone (as above).
What behavior are you seeing that makes you think it's not returning the right Class?

Restoring custom class object array from SharedObject

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

Shared objects not working correclty in flex -- Mobile

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.

Is it possible to get the type of uninitialized variable in Action Script 3?

The task was meant to be quite simple: I needed to initialize variable with new keyword dynamically, depending on it's type. For example:
public var object:Sprite;
...
object = new Sprite();
In this case type is Sprite, but it could be anything and a method which actually instantiates it with new, doesn't know with what type it was declared. Of course I could store type (or class name) in string variable and instantiate object with it. But I just wonder if I could get that type info from the object itself, 'cause it's declared in a class and logically thinking it's type info might be stored somewhere and be retrievable.
Yes, you can, but the variable must be public (or have accessor methods), and you need its name as a String:
Use describeType() to get an XML Description of your class, then get accessors and variables as XMLList, iterate until you find your variable's name, and get the class by calling getDefinitionByName(). Here's an example:
var className : String = "";
var type:XML = describeType (this);
var variables:XMLList = type..variable;
for each (var variable:XML in variables) {
if (variable.#name == myVariableName) {
className = variable.#type;
break;
}
}
if (className == "") {
var accessors:XMLList = type..accessor;
for each (var accessor:XML in accessors) {
if (accessor.#name == myVariableName) {
className = accessor.#type;
break;
}
}
}
if (className=="") {
trace ("no such variable");
return;
}
var ClassReference : Class = getDefinitionByName( className.replace ("::", ".") ) as Class;
myVariable = new ClassReference( );
I can't figure out how to "reply" to an answer, otherwise I would add this to the current top answer.
If you have a list of known types that the object could be, you could test against those types using typeof.
switch(typeof unknownVar)
{
case typeof Function:
unknownVar = new Function();
break;
case typeof String:
unknownVar = "Bruce Lee";
break;
case typeof Number:
unknownVar = 3.14;
break;
default:
trace(typeof unknownVar); // This is not normally helpful...
}
In short no, you can't get the type of an uninitialised variable.
Sounds like this is kind of a factory pattern implementation. Your best bet is to pass a reference of the Class to the method
method:
public function create(class:Class) : void
{
return new class();
}
calling code:
public var object:Sprite;
...
object = createObject(Sprite)