This website uses cookies to improve user experience. By using our website you consent to all cookies in accordance with our Cookie Policy. X

Understanding the "Graphics" symbol

Anyone who already spent some quality time in ActionScript is no doubt fully aware what MovieClips do or how they work, however Flash also allows for creation of something very similar, the symbols called "Graphics". At first they might seem like a simplified versions of MovieClips, which is of course true but their existence relies on much simpler rule... during the exporting process all "Graphics" symbols are split into pieces, similar to how the "break apart" command (CLTR+B) does it, with only difference being that all properties and tweens are applied to objects within. From the ActionScript's perspective this gives us an interesting ways of optimization by removing excessively nested MovieClips.
For example lets take a scenario where we want two objects to be circling around each other and also at the same time both of them smoothly relocated to a new position:

So the Anim symbol contains the animation of two circling objects, while on the stage both of them are being moved to a new location. Nothing interesting here yet, but if we decide we want access to both of those objects from ActionScript instinctively the first step will be to switch Anim from "Graphics" into "MovieClip" and than followed by giving it an instance name so we can navigate through it to access the two objects we want. Which is exactly the nesting we can avoid by using the Graphics - of course if we know how they work.
I suggest experimenting with this by yourself, starting by creating two MovieClips on the stage with two different key-frames within them, then name them "mc1" and "mc2" (instance names, so you can access them from ActionScript) and finally enclose the whole thing inside a Graphics symbol. What will happen if we call the following script from the Stage's timeline?
mc1.gotoAndStop(1);
mc2.gotoAndStop(2);
Because the Graphics symbol will be broken apart with MovieClips "mc1" and "mc2" being thrown outside, the script will execute correctly.
A ready example for download: GraphExample.zip

Creating a subclass with predefined graphics [basic]

Some time ago I have described how to use Interfaces to create a system of popups - here to be exact. I have mentioned then that similar system could be crafted with the use of subclasses, but in case of Flash this might bring unexpected hurdles to overcome. To understand what those hurdles are exactly, it is best to start from the very end by explaining how to deal with them - there are two solutions:

1. One base class to control all subclasses.
Lets say we have two popups, one with a text and other with an image, but also two shared elements: background and "OK" button. In this case it would be best to create three classes: MyPopup which will serve as a base class containing the logic for the background (named e.g. "mcBackground") and for the button ("mcButton"), followed by MyPopupImage responsible for image popup and MyPopupText responsible for the text popup. Unfortunately Flash will refuse to compile this setup, stating that there is a conflict with background in MyPopup and backgrounds in both MyPopupImage and MyPopupText. Of course you could just use different names, but this kind of defeats the purpose - the correct answer is to … delete the subclasses (their files to be exact), allowing Flash to create them on export.

Quite frankly I have no idea how it works, but one thing is for sure, we now can create as many popups as we want using the variables from the base class for background, button and whatever else we want. The one downfall of the solution is of course the need to create all the logic (for every popup) in one place.

2. Using the GET functions.
Here the situation is exactly of opposite of the one described in the previous point, we will be creating separate classes for each popup even it will require zero lines of actual execution code.
Back to the example of 3 classes: MyPopup, MyPopupImage and MyPopupText - we need to build them in such way that no variable conflicts with ones defined in the base class:

//MyPopup class
public class Popup extends MovieClip {
	public function Popup() {
	}
}
//MyPopupImage
public class MyPopupImage extends MyPopup {
	public var mcBackground:MovieClip;
	public var mcButton:MovieClip;
		
	public function MyPopupImage() {	
	}
}
//MyPopupText
public class MyPopupText extends MyPopup {
	public var mcBackground:MovieClip;
	public var mcButton:MovieClip;
		
	public function MyPopupText() {	
	}
}
Okay, but doesn't it make all pointless? From looks of it now the base class doesn't have the access to both "mcBackground" and "mcButton", meaning we wont be able to write single, shared logic for them in MyPopup. This is where the GET functions come into action! Lets added them to MyPopup in following way:
public class MyPopup extends MovieClip {
	public function MyPopup() {
	}
	protected function get popupBackground():MovieClip {
		return null;
	}
	protected function get popupButton():MovieClip {
		return null;
	}
}
And then rebuild MyPopupImage and MyPopupText as so:
//MyPopupImage
public class MyPopupImage extends MyPopup {
	public var mcBackground:MovieClip;
	public var mcButton:MyButton;
	
	public function MyPopupImage() {	
	}
	
	override protected function get popupBackground():MovieClip {
		return mcBackground;
	}
	override protected function get popupButton():MovieClip {
		return mcButton;
	}
}
//MyPopupText
public class MyPopupText extends MyPopup {
	public var mcBackground:MovieClip;
	public var mcButton:MyButton;
	
	public function MyPopupText() {	
	}
	
	override protected function get popupBackground():MovieClip {
		return mcBackground;
	}
	override protected function get popupButton():MovieClip {
		return mcButton;
	}
}
Finished, now base class MyPopup can access backgrounds and buttons from subclasses:
public function MyPopup() {
	super();
	trace(popupBackground!=null); //true
}

Check it out for yourself: subclass.zip

Sending data from preloader to loaded SWF [basic]

Continuing the topic on interfaces from the previous post, I’ve mentioned that their usefulness is sometimes irreplaceable and one good example of this is when trying to establish a communication channel with loaded SWF file, most frequently seen in preloaders.
Lets start with a basic preloader:
var loadLoader:Loader = new Loader();
loadLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,onLoaded);
loadLoader.load(new URLRequest("MyAnimation.swf"));
		
function onLoaded(e:Event):void {
	addChild(loadLoader.content);
}
In this case the MyAnimation.swf file and its content will be put on the stage when preloader finishes loading it. This is without a doubt the simplest solution, giving us pretty much no control over who and how can open this animation, which is why lets, for example, make it only viewable when preloader passes a correct keyword. To do this, we need to go back the the animation file and make it a document class:

In such way that external no external user will be able to start it neither automatically, nor manually:
public class MyAnimation extends MovieClip {
	public function MyAnimation() {
		super.gotoAndStop(1);
	}
public override function gotoAndStop(frame:Object,scene:String=null):void {
		return;
	}
	public override function gotoAndPlay(frame:Object,scene:String=null):void {
		return;
	}
	public override function play():void {
		return;
	}

}
Now even we won’t be able to start it, which is why we will make a backdoor entrance available only to us, by using an Interface of course. Lets make it look like this:
public interface IMyInterface {
	function playUsingPassword(pass:String):void;
}
And then implemented in our MyAnimation class:
public class MyAnimation extends MovieClip implements IMyInterface {
	public function MyAnimation() {
		super.gotoAndStop(1);
	}
	public function playUsingPassword(pass:String):void {
		if(pass == "MyPassword") super.play();
	}
public override function gotoAndStop(frame:Object,scene:String=null):void {
		return;
	}
	public override function gotoAndPlay(frame:Object,scene:String=null):void {
		return;
	}
	public override function play():void {
		return;
	}

}
(Don’t forget to add the implements!)
By writing super.play() we bypass our overwrites of play() from the MovieClip class. One last thing to do is to test it out:
var loadLoader:Loader = new Loader();
loadLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,onLoaded);
loadLoader.load(new URLRequest("MyAnimation.swf"));
		
function onLoaded(e:Event):void {
	addChild(loadLoader.content);

	var main:IMyInterface = loadLoader.content as IMyInterface;
	if(main != null) main.playUsingPassword("MyPassword");
}
Resulting in something like this:
(Animation file is located here: MyAnimation.swf - anyone who wants a challenge can try loading it themselves)
Source: PreloaderInterface.zip

Interfaces in Flash [basic]

In programming, by ‘Interface’ we understand an abstract definition of methods that by themselves don’t do anything, except for forcing a certain syntax in a class that implements them. In other words we define a function and its arguments in one place and then implement its mechanics in another (target class). Of course by no means its the best way to create a function in Flash, but interfaces have a special trait that makes them irreplaceable in certain scenarios. This trait is ability to cast an object to the interface they implement, which in turn allows us to communicate with it, without actually know what the object is.
I think it is best to demonstrate it on a example, starting from the very beginning: let’s say we want to create a game that displays some information through pop-ups in such way, that each of them contains an “OK” button:
public class MyPopup extends MovieClip {
	private var myButton:OKButton;
	public function MyPopup() {
		myButton = new OKButton(this);
		addChild(myButton);
	}

	public function close():void {
		this.visible = false;
	}
}
public class OKButton extends MovieClip {
	private var myPopup:MyPopup;
	public function OKButton(popup:MyPopup) {
		myPopup = popup;
		this.addEventListener(MouseEvent.CLICK,onMouse);
	}
	private function onMouse(e:MouseEvent):void {
		myPopup.close();
	}
}
At first everything might like fine, but as soon as we start adding more pop-ups the limitation of the above code become apparent - what if we want to reuse the same button for MyPopup2 ? Is adding another argument ( OKButton(popup:MyPopup, popup2:MyPopup2) {} ) enough? Should we repeat this for each new pop-up we want to add? One solution for sure is to create a base class for every pop-up, so we cast it down to use shared methods. However such root classes might bring some troubles in Flash, that I won’t be discussing here. At this point it is probably quite obvious what I am trying to say - the best solution is to use Interfaces. So, let’s create a new AS file:
public interface IPopup {
	function close():void
}
And then modify existing classes:
public class MyPopup extends MovieClip implements IPopup{
	private var myButton:OKButton;
	public function MyPopup() {
		myButton = new OKButton(this);
		addChild(myButton);
	}

	public function close():void {
		this.visible = false;
	}
}
public class MyButton extends MovieClip {
	private var myPopup:IPopup;
	public function MyButton(popup:IPopup) {
		myPopup = popup;
		this.addEventListener(MouseEvent.CLICK,onMouse);
	}
	private function onMouse(e:MouseEvent):void {
		myPopup.close();
	}
}
The difference is quite small, but possibilities definitely much broader. From now on MyButton can communicate with every pop-up without actually knowing what class it is, as long as they implement the proper interface.

Cookies in Flash [basic]

Anyone who regularly browses the Internet is surly aware of so called "cookies", or at least heard about them. Cookies are small pieces of information stored on the user's computer, such as passwords or site's settings (look and behavior). Flash isn't an exception here and does support a similar functionality that is called Shared Objects which allows us to store up to 100Kb of data (more requires user's permission).
Each cookie can be created (or restored) with just one simple line of code:
var soData:SharedObject = SharedObject.getLocal("MyCookie","/");
"MyCookie" is the name of the cookie - if there is already one with that name it will be loaded back into the memory.
Now that the Shared Object is created we can write to it whatever we what, all through the "data" field:
soData.data.message = "Hello world!"
soData.data.myVar = 1337;
Similarly, if we want to read from the Shared Object:
trace(soData.data.myVar); //1337
To make things even simpler all that data is being stored on user's computer automatically, but if needed there is also an option to save it manually by calling the flush() function, which also allows to set the upper limit of cookie's size, making it possible to go over the 100Kb limit by prompting the user for more storage.
Example usage:
(Refreshing the page will save the message and then restore it to the upper text field)
Source: Share.zip

Debugging in local enviroment [basic]

In previous post I've described some very basic debugging methods for already released products - while we are on topic of debugging, lets have a look at some popular and easy methods for fixing errors in local environment. Of course everything comes down to what we want to do with our application, but there will be always room for those universal tricks:

1. Execution speed time.
Skipping past the fact you should build your application also considering slower computers, speed measuring is a great way to test algorithms, especially if we know how much time they need beforehand. For more prominent results it's usually a good idea to add a loop:

public static function measureProcessTime(fun:Function, repeat:int = 10):Number {
	var time:Number = getTimer();
	for(var i:int = 0; i < repeat; i++) {
		fun();
	}
	return getTimer() - time;
}
Note that local functions are especially useful here as Flash allows their creation pretty much everywhere, allowing us to test any part of the code by simply enclosing it in the following way:
var someVar:Number = 0;
someVar = 1;
function debug() {
	var otherVar:Number = 10;
	someVar += otherVar;
}
trace(measureProcessTime(debug));
}

2. Loop traversing.
This is another neat neat way to test algorithms. Basically the idea here is to display one single point in a loop, but with ability to move that point around. Example implementation:

private static var DEBUG_COUNT:int = 0;
private static var DEBUG_STEP:int = 0;
public static function nextStep():void {
	DEBUG_STEP++;
}
public static function prevStep():void {
	DEBUG_STEP--;
	if(DEBUG_STEP<0) DEBUG_STEP=0;
}
public static function resetDebug():void {
	DEBUG_COUNT = 0;
}
public static function countDebug():Boolean {
	DEBUG_COUNT++;
	return  DEBUG_COUNT == DEBUG_STEP;
}
Functions nextStep and prevStep are meant to be connect to the arrow keys, allowing us to traverse the loop. The resetDebug function should be called before the loop we want to test. Finally the countDebug function which will return true when our loop reaches the selected step. Although it doesn't seem like much, loop traversing can be incomparable debugging tool. Good example of this is a line drawing algorithm:
(Use the up and down arrow keys to move around the loop)

3. Graphics overlay.
I think no one will disagree it's easier to see data in graphic representation rather than list of variables, which is why every application should always have some kind of way to draw (using Graphics class) information above every other object. This can be easily done by holding the main application in a "container" MovieClip, while having a single Sprite one step above it, which will be empty but accessible from anywhere in the application:
public static debug:Sprite;
public function DocumentClass() {
	var main:MyApplication = new MyApplication();
	addChild(main);

	var debug:Sprite = new Sprite();
	addChild(debug);
}
Now we just need to write DocumentClass.debug.graphics to start drawing over everything else. Personally I like to use it as a way to check hit-boxes of objects on the screen:

Basics of tile-based engine part 1: isometric view [basic]

In this three-part article I will try to explain some of basic problems one might stumble upon when working with tile-based engines. Since they are quite old one might think that the topic was already exhausted, however many people still have problems with it, often complicating trivial issues.
One of those issues is isometric view (sometimes might be called "diagonal view") which a specific type of 3D transformation without the perspective and was often used in older strategy games. Although the definition mentions 3D transformation it is actually not required at all and can be replaced with something much simpler, which unfortunately not many people try to do, resulting in many articles like this one focusing on basics of 3D - unnecessarily.
Basically you could sum everything to this simple function:
function getTile(nTargetX:Number,nTargetY:Number,nWidth:Number,nHeight:Number):Point {
	var nScale:Number = nWidth/nHeight;
	var nTransY:Number = (nScale*nTargetY-nTargetX)/2;
	var nTransX:Number = nTargetX+nTransY;
	var nTmpY:Number = Math.round( nTransY/(nHeight*nScale) );
	var nTmpX:Number = Math.round( nTransX/nWidth );	
	var nTileX:Number = (nTmpX-nTmpY)*nWidth;
	var nTileY:Number = (nTmpX+nTmpY)*nHeight;
	return new Point(nTileX,nTileY);
}
Which results in this:
(Green circle is the Point returned by the getTile function)
Try it yourself: tile_example.zip

That is pretty much it, now the only thing left to do is explain how getTile works step by step:
1.
var nScale:Number = nWidth/nHeight;
Scale will be used during the transformation to realign the difference between height and width (as the algorithm works only in 1:1 scale).
2.
var nTransY:Number = (nScale*nTargetY-nTargetX)/2;
var nTransX:Number = nTargetX+nTransY;

Here is where a point on the screen gets transformed into a point in the isometric view.
Usually when we are talking about an isometric view, we are thinking about a space that is rotated by 45 degrees in such way that movement to the right is not a simple +X, but rather some value of X plus partly also some Y. The example will probably make more sense:

As we can see the movement of the mouse cursor to the right is transformed into a point that is only partially moved to the right, because partially it is also moved upward. Armed with this knowledge we can try and write a general equation - since movement to the right (+X) gives a bit of upward movement (-Y) we can say that Y gets smaller along with X getting bigger, or in other words: Y = -nTargetX. However we have to remember it is not a straightforward transformation x=>y (otherwise it would result in 90 degrees turn), which is why have to divide the equation by 2 (as 90 degrees divided by 2 gives us 45... in a great simplification). Now that we know Y, getting X is quite trivial as we only need to subtract the Y value from target X (so the distance remains the same as the one before the transformation) and since Y is already negative we need to use the addition Y = nTargetX+nTransY;. By the way, you can switch plus and minus signs between each other to achieve a -45 degrees rotation.
3.
var nTmpY:Number = Math.round( nTransY/(nHeight*nScale) );
var nTmpX:Number = Math.round( nTransX/nWidth );
var nTileX:Number = (nTmpX-nTmpY)*nWidth;
var nTileY:Number = (nTmpX+nTmpY)*nHeight;

Here we are translating given point into a tile, by "shrinking" the whole space using width and height values, then discarding any numbers after the comma and finally bringing everything back to "normal size". Because I love examples here is another one: if tile #0 is 20 pixels width, then every pixel between 0 and 20 will belong to it. So now the question is: how to check which tile owns the seventh pixel? Lets take that 7 and divide it by 20, resulting in 0.35. Now we discard everything after the comma and get our tile with id #0. Lets re-try that for pixel 27; 27/20 = 1.35; drop the comma stuff and we get #1, which is correct because as we already know 27 doesn't belong to #0.

Phew, that is it for today. In two weeks I will write about a very simple path finding algorithm you could implement in a tile-based engine.

Preloaders in Flash [basic]

"Preloading" is simple idea that dates back to the beginnings of the Internet - perhaps even further than that. It is a process of moving all of the applications data or multimedia into a computer's memory, so it can be accessed smoothly without breaks for additional reading. Same goes for Flash, with preloading we will be certain our animations and whatnot won't turn into a slide-show.
It sound simple enough, but implementation of a preloader into Flash can sometimes be quite tricky. Generally speaking, it all comes down to 3 solutions depending on what we need:

1. For animations.
Movies done in Flash that use almost no ActionScript are the easiest target to include a preloader (since there will be nothing to interfere with it), just copy and paste a ready example.
One of the simplest requires a single MovieClip in the very first frame of the main timeline with instance name set to "preloader" (in properties tab) along with this code pasted in to the same frame:

import flash.events.Event;
import flash.events.ProgressEvent;

stop();
loaderInfo.addEventListener(ProgressEvent.PROGRESS,onProgress);
loaderInfo.addEventListener(Event.COMPLETE,onLoaded);

function onProgress(e:ProgressEvent):void {
	var percent:Number = loaderInfo.bytesLoaded/loaderInfo.bytesTotal;
	preloader.gotoAndStop( int(percent*preloader.totalFrames)+1);
}
function onLoaded(e:Event):void {
	loaderInfo.removeEventListener(ProgressEvent.PROGRESS,onProgress);
	loaderInfo.removeEventListener(Event.COMPLETE,onLoaded);
	//moves to a designated frame in the animation after the loading process
	gotoAndPlay(2);
}
The "preloader" MovieClip will have its animation stop on frame number that is equal to percent amount of content loaded, so if the "preloader" has total of 10 frames and the move itself is loaded to, for example 20%, the frame displayed will be 2 (10*20% = 2).
Ready to use example: preloaderSimple.zip

2. For applications created in Flash Professional.
Here it gets a bit tougher. When creating project that heavily use ActionScript people don't usually think about preloaders first and foremost, which actually isn't a bad thing but might bring some complications, especially when exporting to the first frame.
Speaking of exporting to the first frame, lets take a moment to look at this function. When exporting graphics to ActionScript we have an option to "export in 1 frame", which does exactly what it says and puts all of the graphics in the first frame of main timeline - the trouble is, this leaves us with no room to later insert a preloader. In Flash Professional this can be dealt in two ways:
- by changing a designated frame in ActionScript's properties

- or by simply putting the graphics manually on the stage in proper frame. Lets, for example, take a class that makes use of a exported graphic on frame 3 in the main timeline, allowing us to put them on frame 2 (even outside of the screen if needed) while having the "export to 1 frame" opetion turned off.

3. For applications created in FlashDevelop.
… or any other development environment that loads SWC files. I'm referring here to external ActionScript libraries that may or may not come packed with graphics or other multimedia. Those multimedia elements are loaded as soon as there is something referencing the classes they are linked with, which basically means any kind of import from SWC will instantly be blocked till the graphics gets loaded into the memory. On the other hands omitting those imports simply means no SWC class will be compiled into the application. This begs a question: how can something be included along the application without actually importing it? There is only one answer and it was found by Keith Peters at: www.bit-101.com. Long story short, this brilliant trick can be summed down to a single line: [Frame(factoryClass="Preloader")] ("Preloader" is the name of the class that will server as a preloader), which has to be put right before documents class definition (but after all the imports) - called, for example: "Main". Main will get loaded but not initialized because this role will be taken over by the Preloader.
Unfortunately there is still a matter of instancing Main after the loading process, since we still can use imports - we import Main, Main imports something else, which in turn imports something else and sooner or later there will come along a class that imports straight from the SWC file, blocking our application the graphics gets loaded. Fear not however as flash.utils.getDefinitionByName comes to our aid being used in the following way:

var mainClass:Class = getDefinitionByName("Main") as Class;
if(mainClass) {
	addChild(new mainClass() as DisplayObject); 
}
WARNING! The getDefinitionByName() function may from time to time return the NULL value, meaning ENTER_FRAME Event might be good idea to allow for further retries in later times.
As per usual, here is an example code: preloader.zip
And in action it can be viewed here: Preloader.html

Communication between AS2 and AS3[basic]

As soon as ActionScript3 saw the light of the day Adobe instantly pointed out that the virtual machine behind it is completely different than the one used in AS2, which in turn means there is no direct way of communicating between those two languages. Right now someone might think that if that is the case, then there is no point in trying, topic closed. But wait a second, no direct connection doesn't equal to no possible connection at all. Let me put it this way: lets take MySQL for example, Flash doesn't come packed with any means to connect to it directly, yet it doesn't stop us anyway, thanks to PHP (or other methods). Which brings us to the heart of the matter: indirect connection. In case of AS3 and AS2 it is even simpler than using PHP, because both of them include a class for communication between local SWF files - LocalConnection is its name.
Main purpose of LocalConnection is to communicate between two Flash applications running on the same computer, however since its implementation did not change at all in AS3, we can also use it to establish a connection between AS2 and AS3.
Creating a LocalConnection is very easy and almost identical for both languages. For ActionScript2 it will be:
var connectionAS2:LocalConnection = new LocalConnection();
connectionAS2.connect("LocalConnectionTestAS2");
And for ActionScript3:
var connectionAS3:LocalConnection = new LocalConnection();
connectionAS3.client = this;
connectionAS3.connect("LocalConnectionTestAS3");
connectionAS3.addEventListener(StatusEvent.STATUS,onStatus);
function onStatus(e:Event):void { }
AS2 version is quite trivial, however AS3 one requires some explanation. First of all, the line that says connectionAS3.client = this; is very important as it points to object that will handle the incoming function calls, without it the application will simply not work. StatusEvent is added to the LocalConnection only to stop Flash from throwing any errors before the connection is established.
Oh and texts put into the connect() functions are different to allow for bidirectional communication.
Okay, now that we have AS2 and AS3 ready to exchange commands, it is time to create function that will send them. Also, to get a better feedback on the whole process we will include TextFields to display the information. For ActionScript3 it will be:
var tf:TextField = new TextField();
tf.text = "No Messages";
addChild(tf);

function messageFromAS2(msg:String,nx:Number,ny:Number):void {
	tf.text = msg+" "+nx+" "+ny;
	tf.x = nx;
	tf.y = ny;
}
While for ActionScript2 it will be:
this.createTextField("textMsg",this.getNextHighestDepth(),0,0,200,25);
var tf:TextField = this.textMsg;
tf.text = "No Messages";

connectionAS2.messageFromAS3 = function(msg:String,nx:Number,ny:Number):Void {
	tf.text = msg+" "+nx+" "+ny;
	tf._x = nx;
	tf._y = ny;
}
One last thing remaining now is to fire up those functions - Mouse Events will do the trick. For ActionScript3:
stage.addEventListener(MouseEvent.MOUSE_MOVE,onMove);
function onMove(e:MouseEvent):void {
	try {
		connectionAS3.send("LocalConnectionTestAS2","messageFromAS3","AS3 MouseEvent",e.stageX,e.stageY);
	} catch(e:Error) {
		return;
	}
}
For ActionScript2:
this.onMouseMove = function():Void {
	try {
		connectionAS2.send("LocalConnectionTestAS3","messageFromAS2","AS2 Mouse Event",_root._xmouse,_root._ymouse);
	} catch(e) {
		return;
	}
}
In both cases try-catch is used to stop Flash from throwing errors before the connection is established.
In action, the whole thing will look like this:
Source code: localcon.zip

By the way, if you ever find yourself in a situation where you need to access an AS2 file that does not support LocalConnections and can't be modified, I suggest creating a separate SWF file in ActionScript2, then loading (loadMovie() function) the target file to read all the necessary information and send them further down the line wherever you like using LocalConnection.

Seek Bar in Flash [basic]

Happy New Year 2012!

Often on the Internet I see people asking how to make a seek bar in a Flash animation (similar to how other video players do it, such as the one used on YouTube) however in the end not many people go through with it - why is that? The answer lays in how Flash handles MovieClips, which is why before I go any further I will take a moment to write few words about them.
It might be hard to visualize it, but each MovieClip is a self contained movie with its own time-line (and settings) and is controlled completely independently of any other objects, which can be seen below (pick any moment, right click and uncheck "play"):

As one might expect the whole animation will be instantly stopped... except for the ball which is a MovieClip. No point in explaining why is that - it just is and will never change. Only thing left to do is to look for alternative solutions, and because stopping all the MovieClips will directly help us with creation of a seek bar it is best to look at two possible workarounds:
1. Remove all the graphics in your animation.
2. Manually stop all the MovieClips with ActionScript.
First solution is the best way to go, as long as you are just starting the creation of your movie, because it is always easier to prepare it beforehand than later convert all the MovieClips into Graphic objects. Afterwards moving around the animation will only require a single call to the gotoAndPlay function, which is exactly what we need for our seek bar. Second solution is only viable in animations with small amount of MovieClips, because seeking and stopping the MovieClips might be a bit too stressful for the processor, especially in the case of a seek bar since it requires a scan through the whole movie. Oh well, sometimes you just don't have a choice.
Code that stops all MovieClip is actually quite simple:
stopMovies(root as MovieClip);
function stopMovies(mc:MovieClip):void {
	for(var i:int = 0; i<mc.numChildren; i++) {
		var d:DisplayObject = mc.getChildAt(i);
		if(d is MovieClip) {
			(d as MovieClip).stop();
			stopMovies(d as MovieClip);
		}
	}
}
Now one might think that the only thing missing here is a gotoAndPlay function and we are set. Unfortunately it is not that simple, as each MovieClip has its own time-line, in will never react to changes in the main time-line (contrary to Graphic objects). Furthermore, replacing every stop function with gotoAndPlay is also pointless as having the main animation on 10th frame does not necessarily mean that each MovieClip is also on the 10th frame - it all comes down to the fact that MovieClip appearing on the, for example, 6th frame, on 10th one will be in 4th frame of its own time-line. It is best to see this in action - animation below has a MovieClip (with 20 frames) appearing on the 10th frame (all of them numbered):
While scrolling, the MovieClip appears to have a mind of his own.
Okay, but why not take the appearing position of the MovieClip and subtract it from the current position? Lets say we want to go to the frame #15 with the MovieClip appearing on the 10th one, so we just take 10 and subtract it from 15 resulting in gotoAndPlay(5) - main time-line stays on 15 while our MovieClip is re-winded to the 5th frame. This is a great idea and perfect solution, but unfortunately Flash has no way of telling us what is the MovieClip's appearing frame. Here is where the challenge for the processor I was talking about earlier comes in, as the next step would be to write down all the starting positions of each MovieClip; which isn't as easy as it sounds, because an animation that takes, for example, 1 minute to play out and is displayed at 20 frames per second, will have 1200 frames worth of scanning! Plus, lets say, 10 MovieClips per frame... Least to say, it is not a very useful solution, which is why for the rest of this article I will assume you are using only Graphic objects.
That is it for theory, time to create our seek bar - taking the bare minimum, we will need MovieClips: a bar representing the length of our whole animation and a marker showing the current position. To make this easier put both of them in a time-line of separate MovieClip (the whole seek bar will be in one place making it easier to, for example, move it between projects) and name them mcBar and mcMarker (instance name). Finally put this code into the time-line of the enclosing MovieClip (one holding the bar and the marker):
import flash.events.MouseEvent;
import flash.events.Event;
import flash.display.MovieClip;

var bSeek:Boolean = false;
var nLength:Number = mcBar.width-mcMarker.width;
mcMarker.mouseEnabled = false;
mcMarker.mouseChildren = false;

if( !mcBar.hasEventListener(MouseEvent.MOUSE_DOWN) ) {
	//to prevent unnecessary duplications on each loop
	mcBar.addEventListener(MouseEvent.MOUSE_DOWN,onSeekStart);
	mcBar.stage.addEventListener(MouseEvent.MOUSE_MOVE,onSeekDrag);
	mcBar.stage.addEventListener(MouseEvent.MOUSE_UP,onSeekStop);
	mcBar.stage.addEventListener(Event.ENTER_FRAME,onRender);
}

function onRender(e:Event):void {
	if(bSeek) return;
	mcMarker.x = ((root as MovieClip).currentFrame/(root as MovieClip).totalFrames)*nLength;
}
function onSeekStart(e:MouseEvent):void {
	bSeek = true;
	onSeekDrag(e);
}
function onSeekDrag(e:MouseEvent):void {
	if(!bSeek) return;
	
	mcMarker.x = mcBar.mouseX;
	if(mcMarker.x > nLength) {
		mcMarker.x = nLength;
	} else if(mcMarker.x<0) {
		mcMarker.x = 0;
	}
	
	var frame:int = int((mcMarker.x/nLength)*(root as MovieClip).totalFrames);
	
	(root as MovieClip).gotoAndStop(frame);
	mcMarker.x = (frame/(root as MovieClip).totalFrames)*nLength;
}
function onSeekStop(e:MouseEvent):void {
	bSeek = false;
	(root as MovieClip).play();
}
Nothing particularly complicated here - clicking on the seek bar moves the marker there and translates its X coordinate into the animation's position.
The whole code + an example how to use it can be download from here: seekbar.zip
And for anyone who wants to try the MovieClip scanning solution I prepared a special class and an example to go with it: seekbarEX.zip. In this case, to avoide conflicts with the animation itself, MovieClip which holds both the marker and the bar is exported for ActionScript with ForusSeekBar class.


Older posts>>>