AS3: GotoAndPlayAndThen

Although I’ve mostly been using this blog as an outlet for game design ideas, I consider myself a programmer/designer, and this seems like the best place to put up some programming notes as well.

Since I first learned Actionscript 3, I’ve been frustrated that when you try to run code like the following inside a class (as opposed to on the stage)…


myMC.gotoAndStop('FINISHED_SCREEN');
myMC.result_txt.text = "You won!";

Then often (but not always!) you will get the familiar error:

TypeError: Error #1009: Cannot access a property or method of a null object reference.

(To clarify: in this context, result_txt is the name of a TextField that exists on the stage, and only exists on the frame with the label “FINISHED_SCREEN”. If you haven’t previously displayed this frame, and you run the code above, then sometimes you will find that the result_txt TextField object hasn’t been properly instantiated by Flash yet when the second line is run – therefore myMC.result_txt will resolve to null, and you’ll get the error above.)

I’ve wrestled with this bug (and I do regard it as a bug in Flash/AS3) for a couple of years now, and have looked into the behavior in some depth. (One of the strange things you’ll find is that after running gotoAndStop(“FINISHED_SCREEN”), myMC.numChildren might be, say, 3… yet when you actually manually iterate over myMC’s children, you will find there to be less than 3. Yikes.)

This bug has been talked about before. Senocular posted about it here on the Kirupa forums. (And while I love every single other post senocular has made on that forums or elsewhere, the fact that he pretends that it is something other than a bug, and basically ends up recommending that people not use frames for the one purpose in which frames are useful – acting as a UI state machine – is head-asplodingly incomprehensible to me.) In that post senocular described a possible workaround; later, Richard Leggett blogged about this issue here, and ended with an ominous note that Event.ADDED_TO_STAGE and Event.RENDER both have bugs in certain contexts. Then, in the comments to that Richard Legget blog, Bryce Branson provided a link to his implementation of Senocular’s method above: Utils.as.

Which is very nice and practical and has only one downside, which is that [b]it doesn’t work.[/b] Take it from me, I used that Utils.as method in two different projects, and though it reduced the occurrence of this issue, it did not eliminate it by any means.

Well, I haven’t fully cleaned-up this solution yet, and I haven’t implemented it for gotoAndPlay (should be a simple search-and-replace of “stop” for “play”)… but here’s a function that I’ve begun using to take care of this, and which provides a watertight implementation. First, here’s a code example of the function in action:


	Utility.gotoAndStopAndThen( myMC, "FINISHED_SCREEN", ["result_txt"],
			function(...args)
			{
				myMC.result_txt.text = "You won!";

				// ...Any additional code that should run afterwards goes here.

			} );

As you can see it’s as simple as providing a movieclip and a frame ID, and then a callback function to be run.

And here’s the function (actually two functions are included here):


/**
* Method for reliably moving to a new frame and then accessing elements that should be on the stage on that frame.
*
* @param mc		MovieClip which we wish to navigate between frames in.
* @param frameID	 Name or number of the frame we're moving to
* @param elementNames Names of elements on the timeline that we wish to interact with. This will also be used to build a cooresponding list of parameters for the "callbackFunc" function.
* @param callbackFunc Function to call once we've entered the new frame and the elements specified in elementNames definitely exist. This function will be passed parameters, a series of DisplayObjects - the elements specified by elementNames.
* @param callbackParams (Optional) Additional parameters to pass to the callbackFunc function. These will be passed after the elements specified by elementNames.
*
*/
public static function gotoAndStopAndThen(mc:MovieClip,frameID:*,elementNames:Array,callbackFunc:Function,callbackParams:Array=null,callbackThis:Object=null)
{
	mc.gotoAndStop(frameID);
	mc.addEventListener(Event.ENTER_FRAME,
			function(evt:Event)
			{
				// Confirm that the elements requested exist, and use them if so.
				var elems:Array = new Array();

				for each( var elemName:String in elementNames )
				{
					var elem:DisplayObject = mc.getChildByName(elemName);

					if ( elem != null )
					{
						elems.push(elem);
					}

				}

				if ( elems.length == elementNames.length )
				{
					// Remove this function as an enter-frame listener so it never goes off again.
					evt.currentTarget.removeEventListener(Event.ENTER_FRAME, arguments.callee);

					// All of the elements were successfully found and put into the elems array. Call the callback function, with the elements and
					// any other parameters we were given.
					doCallbackFunc( callbackFunc, elems, callbackParams, callbackThis );

				}

			} );
}

/**
* Reusable method for calling a function pointer, with two optional arrays of parameters that can be automatically merged.
*	This is useful for various patterns of asynchronous-but-readable programming.
*
* @param func	 (Optional) The callback function that is being called. If none is provided, this function will do nothing.
* @param firstParams	(Optional) An array of parameters for the function call.
* @param secondParams (Optional) An additional array of parameters for the function call. Will be concatenated onto the end of firstParams to generate the array of parameters.
* @param thisObj	 (Optional) An Object to treat as the "this" object in the function call context.
*
*/
public static function doCallbackFunc(func:Function = null, firstParams:Array = null, secondParams:Array = null, thisObj:*=null)
{
	if ( func != null )
	{
		var params:Array = new Array();
		if ( firstParams != null )
		{
			params = params.concat( firstParams );
		}

		if ( secondParams != null )
		{
			params = params.concat( secondParams );
		}

		func.apply(thisObj, params);

	}
}

Here’s one more use case of the code


	Utility.gotoAndStopAndThen( myMC, "FINISHED_SCREEN", ["result_txt","icon_container_mc"],
			function(resultTextField:TextField,iconContainer:MovieClip,resultTextToDisplay:String,iconAlpha:Number)
			{
				myMC.result_txt.text = resultTextToDisplay;
				iconContainer.alpha = iconAlpha;
				// ...Any additional code that should run afterwards goes here.

			},
			["You won!!!", .5] );

The thing to notice in this example is that there are two names passed in for the third array parameter (“result_txt”,”icon_container_mc”), and that these align to the FIRST TWO parameters that the callback function accepts: the names specify a TextField and a MovieClip on the stage, and those objects are what the function will be passed, in that order.

But the callback function can also be passed additional “custom” parameters – in this case, a display string for the textfield, and an alpha value for the MovieClip. These are provided in the fourth parameter to the function, another array (“You won!!!”, .5).

In other words, the parameters to the function are first going to be a series of DisplayObjects from the stage corresponding to the elements named in the elementNames paramater; and THEN the contents of the callbackParams parameter.

And if you don’t want to worry about parameters at all, just do what I do in the first example and have the callback function accept (…params) for it parameters; you don’t need to worry about what params the function will be called with. The params functionality is only provided if you want to use it; if you don’t care about it or understand it, just follow the first example.

This entry was posted in Uncategorized. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

3 Comments

  1. Posted June 2, 2010 at 10:57 pm | Permalink

    Neat solution! It might be convenient to add this function to MovieClip.prototype.

  2. IQpierce
    Posted June 3, 2010 at 11:53 am | Permalink

    You know, I’ve never looked into the AS3 prototype object – never really understood it I suppose. I’m looking into it now (in, of course, senocular’s epic AS3 Tip of the Day thread) and realizing how useful it can be – reminds me of the weird stuff I’m learning in Objective C of ways to “append” methods to existing classes…

    Will have to look into it more, but so far it does sound like it might be an even cleaner way to do this. Thanks for the tip!

  3. IQpierce
    Posted June 3, 2010 at 1:06 pm | Permalink

    By the way, without doubt the biggest downside of using this is the fact that, if the element names you specify are never found (i.e. you specify one or more names that are not the names of elements on the stage at all), then this onEnterFrame event will continue to run forever, and never execute the callback function provided.

    Hopefully this should be an immediately obvious problem, since the presumably-important callback code simply won’t be executed. But the fact that this can happen so easily (from a simple typo), and results in a hit to performance every frame, as well as a small hit to memory… well it’s not ideal.

    I suppose the best solution would be to provide an additional parameters, of how many frames to keep waiting for, which would default to something like 10 and which could be set to -1 to wait forever. Maybe someday I’ll make this addition, and work it into MovieClip.prototype as Krilnon suggested, and post the code (and working examples, a gotoAndPlayAndThen copy of it… etc). When I have time. :)

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*
  • Here there be advertisements. Hopefully.