0

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?

Neal Davis
  • 2,010
  • 2
  • 12
  • 25
  • "*into a `ByteArray` and save that data to a text file*" what would be the point of writing binary data into a text file? – null Nov 22 '16 at 21:13
  • where is your `byteArrayToBrickArray` function? – payam_sbr Nov 22 '16 at 21:17
  • @Neal Davis in a binary file, obviously. The binary data will not result in any useful text. – null Nov 22 '16 at 22:17
  • @null, oh... LOL. How intriguing. I will try that and see if anything improves in my results. – Neal Davis Nov 22 '16 at 22:19
  • @null it appears that either a text file or a bin file has the same results. Since I don't really care if it is a text file or a bin file, I'll use a bin file since that seems neater, but doesn't seem to make an advantage. – Neal Davis Nov 22 '16 at 23:35
  • See my answer here: http://stackoverflow.com/a/33455756/1457439 – BadFeelingAboutThis Nov 23 '16 at 00:43
  • Mine, and other answers to this question may also be useful to you. http://stackoverflow.com/questions/30125221/using-file-to-save-scene-object-locations-to-rebuild-later-in-as3/30136465 – BadFeelingAboutThis Nov 23 '16 at 01:14
  • @Neal Davis the advantage is that it makes sense. If it is generally advantageous for your case to have the data in binary and not text is still questionable, though. – null Nov 23 '16 at 08:48
  • also take a look at this http://www.adobe.com/devnet/actionscript/learning/as3-fundamentals/vectors-and-bytearrays.html – payam_sbr Nov 23 '16 at 17:14

2 Answers2

2

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.

Vesper
  • 18,599
  • 6
  • 39
  • 61
  • Thanks. I'm attempting this approach now. Will I run into any trouble trying to add properties like Point objects and SoundTransforms? where you say "write all the blocks[i]'s properties in order to output"? – Neal Davis Nov 23 '16 at 09:09
  • I'm not quite grasping how to use this method from the main doc class. Can you demonstrate that syntax? I'm confused because even the output function returns void...? I'm sorry my brain is bleeding from reading all these different examples that i can't make sense of all over the internet. – Neal Davis Nov 23 '16 at 09:18
  • And reading and writing this to an external file would be done with just a byteArray? `fileStream.writeBytes(Level)`? or something like that? – Neal Davis Nov 23 '16 at 09:21
  • Writing a file stream is done from a ByteArray, yes. But writing an object is done with `writeObject()`. About syntax, an example here http://stackoverflow.com/questions/30125221/using-file-to-save-scene-object-locations-to-rebuild-later-in-as3/30136465 should be sufficient. Writing a `Point` should be better done by properties IMHO, but since it's a type with all properties being public, and a constructor that accepts zero parameters, you can use builtin serialization and call `writeObject(point)`. – Vesper Nov 24 '16 at 09:59
  • Indeed, this `IExternalizable` is a serious jump in difficulty, because you're getting pulled off objects down to a byte stream level, where no inherent structure yet exist, and you are to create it first, then recreate an object from a seqquence of bytes. – Vesper Nov 24 '16 at 10:00
  • So I have a simple working solution roughly based on your approach but instead of using a byte array I just save the variables (int, Boolean, and uint values) straight via the FileStream. No byte array involved. It's like `stream.writeBoolean(brick._value);` and the `brick._value = stream.readBoolean();`. Any reason I shouldn't go this route? I'm publishing to AIR which works on my desktop but am aiming for mobile eventually. Thoughts? If it works go for it, right? Just wondering if I'm missing something that will be important later by circumventing the byte array? – Neal Davis Nov 26 '16 at 22:07
  • 1
    well, `IExternalizable` is a tool to allow `stream.writeObject(level)` right away, once serialization and deserialization methods are written. And anyway the stream has a byte array under the hood. There shouldn't be any stones underwater if you're just writing/reading simple types in the correct order. – Vesper Nov 28 '16 at 18:24
1

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

}
payam_sbr
  • 1,428
  • 1
  • 15
  • 24
  • I see a problem with this. You are suggesting (if I understand correctly) that I make a Vector of my bricks (cast as Objects) but Object class doesn't have the same properties as my LineTestBlock class. I see that will cause an error saying that Object class doesn't have property "foo". – Neal Davis Nov 22 '16 at 21:57
  • @NealDavis i test it by a custom `LineTestBlock` Class that contains some public properties and save it and load it to `bytearray` then returning Object from `readObject()` also consists all of properties with the same **key names** – payam_sbr Nov 22 '16 at 22:15
  • Thanks, I'll give it a go! – Neal Davis Nov 22 '16 at 22:47
  • `var blocks:Vector. = bytes.readObject() as Vector.;` throws all those errors I showed. – Neal Davis Nov 22 '16 at 23:54
  • can you post the whole example you say you tested so I can run it on my system and see it work? thanks! – Neal Davis Nov 22 '16 at 23:55
  • @NealDavis yes but i should try a vector instead a single object, i'll post it soon – payam_sbr Nov 22 '16 at 23:57
  • @NealDavis edited and works fine with my own LineTestBlock class, implement it with yours. – payam_sbr Nov 23 '16 at 00:24
  • So I have a simple working solution roughly based on your approach but instead of using a byte array I just save the variables (int, Boolean, and uint values) straight via the FileStream. No byte array involved. It's like `stream.writeBoolean(brick._value);` and the `brick._value = stream.readBoolean();`. Any reason I shouldn't go this route? I'm publishing to AIR which works on my desktop but am aiming for mobile eventually. Thoughts? If it works go for it, right? Just wondering if I'm missing something that will be important later by circumventing the byte array? – Neal Davis Nov 26 '16 at 22:06