Thursday, March 10, 2011

How can you make an Actionscript 2 function know about itself? How do you copy all functions from one movieclip to another?

Scoping has got to be one of the most tricky things in programming in Actionscript. A function like:
this.shape_mc.onPress = function(){
trace(this);
}
will trace out the path to this.shape_mc when shape_mc is pressed, unless call or apply is used. If the same function above is invoked using call:
this.shape_mc.onPress.call(this.shape2_mc);
the path to this.shape2_mc will be traced!

Recently, there have been a number of times when I want a function to know about itself, the way shape_mc knows about itself as "this" in the function above. Here is a simple example of how that can be done:
//create a triangle
this.createEmptyMovieClip("triangle_mc", this.getNextHighestDepth());

this.triangle_mc.beginFill(0x00a900, 100);
this.triangle_mc.lineStyle(1, 0xFF0000, 100);
this.triangle_mc.moveTo(50,0);
this.triangle_mc.lineTo(100, 83);
this.triangle_mc.lineTo(0, 83);
this.triangle_mc.lineTo(50,0);
this.triangle_mc.endFill();

this.triangle_mc.onRelease = function(){
this._parent.traceEvent.call(this, arguments.callee.name);
}
this.triangle_mc.onReleaseOutside = function(){
this._parent.traceEvent.call(this, arguments.callee.name);
}

this.triangle_mc.onRelease.name = "onRelease";
this.triangle_mc.onReleaseOutside.name = "onReleaseOutside";

this.traceEvent = function(type:String){
trace("An event of type:"+type+" happened to "+this);
}

From which you get output like:

An event of type:onRelease happened to _level0.triangle_mc
An event of type:onReleaseOutside happened to _level0.triangle_mc

There are three subtle things going on here. One is that arguments.callee is a self-reference to the function that is being executed. The second is that a function can be assigned properties, like name. Lastly, using call with traceEvent means that the "this" is changed from "_level0" to the first parameter in the call statement, in this case, _level0.triangle_mc.

You can find examples on the web where arguments.callee is used to implement recursion, like this one where the names of all functions are set recursively.

You can even set a property on a function to be another function, like

this.triangle_mc.onRelease.tracingFunction = this.traceEvent;

In which case the onRelease function could be:

this.triangle_mc.onRelease = function(){
arguments.callee.tracingFunction.call(this, arguments.callee.name);
}

Getting dizzy yet?

Like call, apply lets you set the scope of a function, but the parameters are supplied in an array. The following example is the definition of a function in a class ubMath called coordsToGlobal which is used in the documented example to draw a bounding rectangle around one movieClip on another one.

/**returns an array of global (stage coordinates)
*@param mc (MovieClip) the movieclip that the coordinates are respect to.
*@param x (Number) the x coordinate in mc's coordinate system
*@param y (Number) the y coordinate in mc's coordinate system
*@param global_mc (MovieClip) optional movieclip to express the coordinates with respect to. If undefined, it will truly be global.
*@returns (Array) [x, y]
*@example

import edu.clips.util.ubMath;

var boundsObj:Object = mc.getBounds();
var container:MovieClip = _root.createEmptyMovieClip("ubDebug_bounding"+(new Date()).getTime()+"_mc", _root.getNextHighestDepth());
container.lineStyle(3, 0xFF0000, 70);
container.moveTo.apply(container, ubMath.coordsToGlobal(mc, boundsObj.xMin, boundsObj.yMin));
container.lineTo.apply(container, ubMath.coordsToGlobal(mc, boundsObj.xMax, boundsObj.yMin));
container.lineTo.apply(container, ubMath.coordsToGlobal(mc, boundsObj.xMax, boundsObj.yMax));
container.lineTo.apply(container, ubMath.coordsToGlobal(mc, boundsObj.xMin, boundsObj.yMax));
container.lineTo.apply(container, ubMath.coordsToGlobal(mc, boundsObj.xMin, boundsObj.yMin));

*/
public static function coordsToGlobal(mc:MovieClip, x:Number, y:Number, global_mc:MovieClip):Array {
var myPoint:Object = {x:x, y:y};
mc.localToGlobal(myPoint);
if (global_mc != undefined) global_mc.globalToLocal(myPoint);
return [myPoint.x, myPoint.y];
}


You can also use arguments to pick up the parameters passed to a function. In the following example a function is created on newMc for every function in mc. The new function calls the old function on mc, scoped with newMc, with all the parameters that got passed to it.

for (var item in mc) {
if (typeof(mc[item]) == "function"){
newMc[item] = function(){
var argArray:Array = new Array();
for (var i=0; i<arguments.length;i++){
argArray.push(arguments[i]);
}
return arguments.callee.functionDuplicatedFrom.apply(this, argArray);
}
newMc[item].functionDuplicatedFrom = mc[item];
}
}


Oh no, I've said too much
I haven't said enough...