使用“文件”保存场景对象位置以便稍后在 AS3 中重建

Posted

技术标签:

【中文标题】使用“文件”保存场景对象位置以便稍后在 AS3 中重建【英文标题】:Using 'File' to save scene object locations to rebuild later in AS3 【发布时间】:2015-07-19 10:50:35 【问题描述】:

我正在尝试使用 ActionScript 3 中的“文件”功能来保存以下信息:

我在场景中有不同的可拖动显示对象,数量和类型可能会有所不同。我想保存金额和它们的位置,然后在以后的会话中加载它们。

我正在努力使用 File 来保存任何内容,我已经搜索了 Adob​​e 文档,但无法弄清楚如何使用它。

我还没有使用它开发任何代码。

任何帮助将不胜感激。

谢谢。

【问题讨论】:

File 类用于 AIR,使用 FileReference 将某些内容保存到磁盘,您最好使用 SharedObjects 来保存特定于 Flash 的数据。 我正在为我的应用程序使用 AIR @Vesper,即使我的应用程序使用 AIR,我也可以使用 SharedObjects 吗? 您可以将 SharedObject 与 AIR 一起使用。但首先你想以什么格式保存那个场景?二进制? XML 还是以后重建场景? @BotMaster 我希望以后能够重建场景,如果可能的话 【参考方案1】:
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();

【讨论】:

感谢您的提交,但我不太明白上面的代码实现了什么 正如 LDMS 所说。我不知道这是否非常有用,因为您没有描述您要保存的信息类型或您对 File 类及其操作不了解的内容。如果您可以提供更多详细信息,我将很乐意扩展我的答案。 我不能提供太多。正如我所说,我正在尝试以某种形式保存场景,以便以后可以将其加载回来。有人告诉我使用 File 是合适的,因为我使用的是 AIR,我完全不了解文件类,因此必须对与 File 类相关的任何信息进行上下文化和解释跨度> Adam,请至少描述一下您要保存的场景信息。是图像吗?物体坐标?人物名字?给我们一些东西。 动态添加到场景中的各种影片剪辑,以及图像、文本字段和按钮。【参考方案2】:

我通常使用 SharedObject,通过将对象的数量及其位置、比例、旋转、.. 等保存为一个数组(通常是多维数组)。

这个例子已经过测试:

首先制作一个影片剪辑,在 ActionScript Linkage 中将其命名为“mc” 添加您喜欢的任何图形 (此 MovieClip 将是稍后要保存的对象) 然后添加以下脚本

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

在项目的根阶段添加:

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

【讨论】:

这是一个非常低质量的答案。考虑通过展示一个示例使其更有用。 我忙了一段时间对不起 我现在加了一个例子【参考方案3】:

假设您要保存的所有对象都属于同一个父对象,您可以按照以下方式进行操作:

首先,创建一个类文件(我们称它为SaveData.as 并将其放在项目目录的根目录下)。这将描述您要保存的数据:

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

        
    

接下来,在您的保存功能上,执行以下操作:

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

现在,重新加载它:

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

如果您不确定导入,这里是:

import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.net.registerClassAlias;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;

【讨论】:

【参考方案4】:

您在这里已经有了一些答案,但从您的问题来看,您可能错过了更大的背景。

所以File 类表示磁盘上文件的路径,FileStream 类可以读取和写入该文件的数据。这些很容易使用,网上有很多例子。这是来自 Adob​​e 的一个教程:Reading and writing files

但是要写入什么数据,格式和数据类型是什么?这些是更重要和更有趣的问题。

最简单的方法是使用基于text 的格式,例如XMLJSON,您可以在其中读取和写入您想要的Sprites(或其他对象)的任何属性。这样做的一个优点是生成的文件是人类可读/可编辑的文本文件。一个小缺点是您需要指定要保存和恢复哪些属性以及处理简单的数据类型转换(字符串到 int 等)。

一种更强大的方法是使用所谓的Serialization 来保存和恢复整个对象的状态。这更复杂,虽然不难,但对于您的项目需求来说可能是多余的。有很好的例子和讨论here,here和here。

对于您当前的项目和技能水平,我建议使用XMLJSON 这是使用 XML 的教程:Loading and Processing External XML Files

【讨论】:

浏览了那些关于序列化的讨论,发现没有提到 IExternalizable。诡异的。在使用基于类的DisplayObjects 时,我发现这个是最好的。例如,我有一个 Tower 类型的对象,它只需要三个字段即可完全重建自身,因此我仅将这三个字段存储在 writeExternal 调用中,并根据读取的值在 readExternal 中进行所有重建。这将恢复大约 40 个参数,不包括显示列表。父级负责其他元数据,并且包含较小但仍然值得重建的数据与写入的数据的比率。【参考方案5】:

您正在尝试将DisplayObject 直接写入文件,由于 Flash 处理任何对象的默认序列化的方式,Flash 引擎阻止了这种情况。为了将DisplayObject 保存到外部资源中,您需要在该对象的类以及您计划存储的任何对象类上使用IExternalizablewriteExternal 的实现应该保存从头开始重建所述对象所需的所有数据,并且readExternal 还应该通过对嵌套显示对象执行addChild() 或将它们添加到对象可能包含的其他内部结构。

注意,其他答案包含使用 XML 或 JSON 进行自定义序列化的有效点,还包含需要导入的链接,特别是,flash.utils.registerClassAliasflash.utils.getDefinitionByName 非常需要从序列化数据块重新创建结构.

举个例子:假设您有一个Board 类中的绘图板,以及一组可以使用鼠标拖动的矩形,它们的大小和颜色不同。矩形是定制的MovieClips 并且没有自己的类,但每个MovieClip 还分配了一个color 属性以简化它们的区别。这意味着您只需要在Board 类上实现IExternalizable。我们还假设Board 类有一个pieces 数组,其中包含指向嵌套矩形的所有链接,以及一个根据作为参数提供的宽度、高度和颜色创建一个新的适当大小的矩形的方法。 (您的情况可能对Board 的数据结构有更多要求,因此请密切关注)因此,序列化Board 的过程将收集嵌套MC 中的所有数据并将其按顺序填充到@提供了 987654341@,并且恢复 Board 实例的过程应该检索存储的数据,解析它以找到在哪里,创建嵌套的 MC,使其与存储的相同,正确定位它们,addChild() to self and rebuild the件`数组。

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;
    

对数据结构的改进已经可见 - 您需要将传递的 _width_height 存储在 MC 中的某处,因为该 MC 的实际 width 将不同于默认线粗( 1,两边各 0.5)。不过,xy 可以从 MC 的属性中正确检索。因此,将这两行都添加到createRectangle 中是必要的。

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

这样,序列化Board 变得更加容易。

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

要恢复,您需要从IDataInput 中读取数据按照与writeExternal 中写入的顺序完全相同,然后处理重建我们存储的显示列表。

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

如果您的嵌套 MC 有一个也实现 IExternalizable 的类,您可以将整个数组保存在一条指令中,writeObject(pieces),这将使 Flash 遍历数组,找到它包含的所有数据并调用 @ 987654359@ 在任何嵌套对象上,本质上为数组中的每个实例调用该类的 writeExternal 函数。恢复这样的数组应该包括通过遍历数组并在每个恢复的实例上调用addChild() 来重建显示列表。

最后但并非最不重要的一点是,应在对自定义对象进行任何序列化或反序列化之前调用registerClassAlias()。调用它们的最佳位置可能是您的主对象的构造函数,因为它肯定会在您的应用程序包含的任何其他代码之前被调用。

【讨论】:

“按顺序填充”... 我可以使用 FileStream 将其按顺序直接填充到 bin 文件中吗?这种方法(FileStream)适用于在整个板上设置圆形(保存然后从同一个文件加载)但颜色值总是保存为0。我试过stream.writeUnsignedIntwriteInt。我没有收到任何错误,但圆圈最终变成黑色,保存的值为 0。 事实上,writeExternal() 将填充到任何提供的 IDataOutput 中,因此,如果您调用 FileStream.writeObject(),则会调用此方法。关于您的颜色无法恢复,您可能需要使用正确的填充重新绘制圆形。您似乎没有检查是否从文件流中读取了零,对吧?

以上是关于使用“文件”保存场景对象位置以便稍后在 AS3 中重建的主要内容,如果未能解决你的问题,请参考以下文章

如何将文档文件保存到 iOS 中的 SQLite 数据库,以便稍后在程序中查看它们?

AS3:强制保存 Xml 文件

如何永久保存数据,以便稍后在 Android 中访问?

从向量中保存的类对象输出类函数

如何缩放一个对象,以便当它等于 0 时它会转到下一帧? AS3

如何在处理中保存设置数据,然后稍后加载以供使用