chadb
chadb

Reputation: 1168

How to get the width of a MovieClip for a different frame instantly?

Is there a way to get the width of a MovieClip (that does have a name) on a different frame? I have tried to using .width and .getBounds(null).width, however, both of them will give me only the width of the current frame. I have tried to do gotoAndStop(frameiwant), but the information doesn't seem to be correct until at least the next frame

I would like to get the width of the frame instantly so I don't have to wait until the next frame for the width.

Upvotes: 4

Views: 740

Answers (4)

marbel82
marbel82

Reputation: 948

If you know the maximum size of the MovieClip, you may try this:

// Create movie clip
var movie :MovieClip = new MovieClipWith3Frames();

// Move to second frame
movie.gotoAndStop(2);

// Create bitmap witch magenta background
var bd :BitmapData = new BitmapData(200, 200, false, 0xFF00FF);

// Draw second frame
bd.draw(movie);

// Found the bounds of shape
var movieBounds:Rectangle = bd.getColorBoundsRect(0xFFFFFF, 0xFF00FF, false);

trace(movieBounds); // (x=42, y=15, w=32, h=33)

Upvotes: 0

Marty
Marty

Reputation: 39458

The only way I could think of doing this was to have an initial phase in your project which will:

  1. Run through all of the frames in your timeline. Create an object which will hold information about the children in that frame. It can be called Frame.
  2. Iterate over all the children that are added to the stage in that frame and add a definition object that describes that child. The description can be as basic or vast as you need. We can call this class an ObjectDefintion.

The downside of this process is that you need to wait for the FRAME_CONSTRUCTED event like @Larusso pointed out in his answer. This means that the frame actually has to finish rendering before you are able to get information about its children, which of course means you have to go through and render every single frame in your timeline during this phase. All you can really do to mitigate this problem is set the frameRate to something high and then set it back when you're done assessing all the frames.

I have set this up and it works well - I'll paste each class and try explain what they do.

So for your document class (or whichever MovieClip holds the frames you want to look at), I have this:

public class Main extends MovieClip
{

    private var _userFrameRate:int;
    private var _frames:Vector.<Frame> = new <Frame>[];


    public function Main()
    {
        _userFrameRate = stage.frameRate;
        stage.frameRate = 120;

        addEventListener(Event.FRAME_CONSTRUCTED, _assess);
    }


    public function getFrame(index:int):Frame
    {
        return _frames[index - 1];
    }


    private function _assess(e:Event):void
    {
        var frame:Frame = new Frame(this);
        _frames.push(frame);

        if(currentFrame === totalFrames)
        {
            removeEventListener(Event.FRAME_CONSTRUCTED, _assess);
            gotoAndStop(1);
            stage.frameRate = _userFrameRate;

            ready();
        }

        else play();
    }


    public function ready():void
    {
        // Start here.

        // There is a MovieClip on frame 10 with the instance name 'test'.
        // We can get the width of it like this.
        trace( getFrame(10).define("test").property("width") );
    }

}

This basically initializes the phase in which we will run over each frame in the MovieClip and assess its children. The ready() method is used as the entry point for your code post-assessment.

Next we have the Frame class, which serves to hold information about children related to a frame:

public class Frame
{

    private var _main:Main;
    private var _content:Object = {};


    public function Frame(main:Main)
    {
        _main = main;
        update();
    }


    public function update():void
    {
        _content = {};
        for(var i:int = 0; i < _main.numChildren; i++)
        {
            var target:DisplayObject = _main.getChildAt(i);

            // This will be explained below.
            var definition:ObjectDefinition = new ObjectDefinition(target, "x", "y", "width", "height");

            _content[target.name] = definition;
        }
    }


    public function define(name:String):ObjectDefinition
    {
        return _content[name];
    }

}

It's pretty straightforward - you give it a reference to Main so that it can check children that are existent within it each frame.

The ObjectDefinition class is also pretty straightforward, acting purely as a repository for data that you want to keep track of on each child of the frame:

public class ObjectDefinition
{

    private var _definition:Object = {};


    public function ObjectDefinition(target:DisplayObject, ...properties)
    {
        for each(var i:String in properties)
        {
            _definition[i] = target[i];
        }
    }


    public function property(property:String):*
    {
        return _definition[property];
    }

}

You'll notice that the constructor accepts the target DisplayObject that will be defined, as well as any amount of properties you want to keep track of as strings (see above within Frame for implementation).

Once complete, you can chain the methods Main.getFrame(), Frame.define() and ObjectDefinition.property() to get properties of children that will exist throughout the timeline. For example, if you have a MovieClip with the instance name square on frame 15 and you want to get its width and height, you can do this within .ready() like so:

var square:ObjectDefinition = getFrame(15).define("square");
trace(square.property("width"), square.property("height"));

Of course this process is not ideal - but unfortunately it is the only way I can see that what you want to achieve is possible.

Upvotes: 4

Discipol
Discipol

Reputation: 3157

You could add an event listener for 1 millisecond and test if the previousWidth you had stored is different. If it is, there you go. If not, its probably listening to the same frame.

A 1 millisecond timer is not such a big deal, stop it if you don't need it, resume it if you do, else, keep it running constantly. When it changes, dispatch an event or whatever needs to happen.

Upvotes: 0

Larusso
Larusso

Reputation: 1151

You have to listen to a specific event before you can ask for the information.

clip.addEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
clip.gotoAndStop(frame);

function frameReadyHandler(event:Event):void
{
    clip.removeEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
    var width = clip.width;
}

The Frame constructed event is the first of several events that gets dispatched. It gets dispatches right before the frame script gets executed. You could also wait for the on enter frame event.

Upvotes: 0

Related Questions