Advertisement
  1. Code
  2. ActionScript
Code

Thinking in Commands: Part 1 of 2

by
Difficulty:IntermediateLength:ShortLanguages:

Twice a month, we revisit some of our readers’ favorite posts from throughout the history of Activetuts+. This tutorial was first published in March, 2010, and is the first part of a series.

Simple, maintainable code is beautiful. However, when we have a sequences of actions that need to trigger each other, our code can get messy, making it impossible to change later. The Command Pattern keeps things clean.

In this tutorial, I'll show you how to create a minimalist AS3 Command framework, capable of performing actions in sequence, in parallel, or with a delay. You'll lean how to use this framework to create a complex effect with simple and clean code.


Command Encapsulation

Encapsulating instructions into "commands" is a popular programming approach to simplify things - the Command Pattern is actually one of the most commonly used design patterns in object-oriented programming. Basically, the command concept is implemented by creating command classes, each class representing one type of command. In the rest of the tutorial, when I refer to "a command", I mean "a command object".

You can think of a command as a button on a remote control. Each button does something different, but they're all used in the same way: you press it, then the magic happens. Be it turning the TV on, changing the channels, or adjusting the volume, these functions can all be done by simply pressing a button.

Image courtesy of freedigitalphotos.net

The concept of commands is the same. A command's underlying instruction is just like a remote control button's function. You may encapsulate different instructions into commands, such as tracing out a simple message, moving one object from one place to another, or toggling the visibility of a display object. Once the encapsulation is done, these can be performed simply by telling the program to "press the remote control buttons", or in other words, to "execute the commands".

If you want the program to perform differently, you can just change the code inside the Command class: the program still executes the same commands it did previously, but the underlying code inside the commands is different. Your overall list of the actions that you want the program to do is separate from your detailed list of instructions for how each action should be done.


Why Bother with Commands?

"Big deal," you might say, "I could do that using functions. Why bother using commands?" Okay, let's look at two sets of code that create the same effect, one using functions, and the other using the command framework we'll create in this tutorial. The advantage of commands will become clear.

Let's say we want to create a circle, add it to the stage, tween it from invisible to visible over half a second, wait for two seconds, tween back to invisible over another half second, and then remove it from the stage. To do all this we'll use Greensock's TweenNano class.

If you're only using functions, the code will look like this:

Do you see how our list of actions is all tangled up with our instructions for performing each action? To figure out what's going to happen, you have to follow all the onCompletes and see where they lead.

Here's the same code, using a command framework:

Here, AddChild(), TweenNanoFrom, TweenNanoTo, and RemoveChild are all Command classes that we define elsewhere in the code, and SerialCommand is another Command class we can use to create sequences of commands on the fly.

Result: no more function "jumps". It's clear what this sequence is going to do, and in what order. It's also easy to change the order of the actions, or insert a new action between existing ones, without having to hunt around the code for each action and change its onComplete property.

Commands also let us queue up different actions so that they happen at the same time - but we'll get to that later!


The Command Class

A quick working example is worth more than a thousand words, so let's look at the essential element of our command framework: the Command class.

The "emptiest" method is the execute() method; however, this method is the most important part of the command. In order to create various command objects, you have to extend this Command class and override the execute() method, filling in the instructions you want your program to perform.

To make a Command object work, you call its start() method; it counts down the delay time using a Timer object, and calls the execute() method when the timer finishes the count-down. A zero delay time simply means your Command object's execute() method will be called right after you call its start() method.

(Note that when your command is complete, you have to call the complete() method manually, causing it to dispatch a COMPLETE event. The purpose of this method would become clear later in the tutorial.)

By the way, setting up the event parameter for the start() and complete() methods with a null default value is just my personal habit. In this way, the methods can be called like you would do to any other zero-parameter methods, or can be used directly as event listeners.


Example: Simple Tracing

Now that we have our Command class, let's start playing with it with some simple tracing.


Step 1: Create a Flash Document

First off, we have to open the Flash IDE and create a new Flash document. Call it SimpleTracing.fla.


Step 2: Create the Document Class

Next, create a document class for this Flash document. Read this Quick Tip for an introduction to document classes.

Save it as SimpleTracing.as.


Step 3: Create the Base Command Class

Create a new AS file and copy the Command class (from above) into it.

Create a new folder in your classpath called "commands" and save this new AS file as Command.as inside that folder.


Step 4: The Trace Command

We'd like to start by encapsulating a tracing function into commands, so let's extend the Command class to create a TraceCommand class for this purpose. This class will contain a message string to be traced out when the execute() method is called, and will call the complete() method after the tracing.

Save this as TraceCommand.as, also in the "commands" folder. See how we've overridden the execute() function to make this command actually do something?


Step 5: Trace

Complete the document class with TraceCommand objects. Add listeners for the COMPLETE event of these commands.

Telling the program to execute the commands is as simple as calling the Command objects' start() methods. Test the movie and you'll see the following output, printed out line-by-line with a time gap of one second. Also, you can see the interlacing messages printed out by the commands' complete event listener. The same variable is used to hold references to different Command objects, but the program does the same thing to the variable: call the start() method and listen for a COMPLETE event.


Composite Commands

There are times when you'd like to execute multiple commands with complex timing. Here I'll introduce two common types of commands which can accomplish advanced command timing: parallel and serial commands. Both of these are composite commands, which means they contain multiple subcommands. Let's check them out one by one.


Parallel Command

A parallel command executes all its subcommands at the same time - or, in other words, in parallel. The command is complete only when all its subcommands are complete. The following figure gives a visual concept of a parallel command. The black arrowheads denote the "flow" of command execution


The ParallelCommand Class

Now it's time to create our class for parallel commands.

Below is the complete code for the ParallelCommand class. Save it as ParallelCommand.as in your "commands" folder.

The subcommands are passed to the constructor as the ...(rest) parameter. This lets us pass as many commands as we like to the constructor; they'll automatically be put into an array called commands. We'll see the beauty of this special type of parameter very shortly.

This class overrides the execute() method; the new execute() method now calls the start() method of all subcommands, and listens for their COMPLETE events. The COMPLETE event listener for the subcommands counts how many subcommands have completed; once all the subcommands are done, the ParallelCommand's complete() method is called, and dispatches a COMPLETE event of its own.


Example: Parallel Tracing

Let's try out the ParallelCommand class. Create a new Flash document, copy the "commands" folder to its classpath, and write a new document class as below:

The benefit of using the "...(rest)" parameter for the constructor parameter now becomes apparent. You can format the subcommands with proper code indentation to write visually self-explanatory codes.

Test the movie and you'll see the three messages traced out at the same time, then a final message denoting the completion of the parallel command:

  • 1st of 3
  • 2nd of 3
  • 3rd of 3
  • all commands are complete

What about setting up delays within a parallel command? Simple. Change your document class's constructor function like so:

Test the movie and you'll see the following three waves of messages printed out, with a one-second time gap between each wave:

  • first wave, 1st of 2
  • first wave, 2nd of 2


  • second wave, 1st of 3
  • second wave, 2nd of 3
  • second wave, 3rd of 3


  • last wave, 1st of 2
  • last wave, 2nd of 2

To get a better idea of what's going on, check out this illustration:


Serial Command

The second type of composite command is the serial command. A serial command executes its subcommands one after the other - or, in other words, in series. For instance, the second command is executed after the completion of the first one and the third one is executed after the completion of the second one. The following figure gives a visual concept of a serial command:


The SerialCommand Class

Here's the source code for the SerialCommand Class. The overridden execute() method calls the start() method of the first subcommand and listens for its COMPLETE event. Then, the event listener starts the next subcommand and listens for its COMPLETE event, and so on, until all subcommands are completed. At that point, the COMPLETE event for the whole SerialCommand is dispatched.


Example: Serial Tracing

Let's use the SerialCommand class to do some serial tracing. As before, create a new Flash document, copy the "commands" folder over, and write a new document class:

Test the movie and the following messages are traced out one-by-one, with a one-second time gap, followed by "all commands are complete".

  • first command


  • second command


  • third command

Here's a concept figure of this example that helps you have a better understanding of what's going on.


Nested Composite Commands

So far, we've only explored the most basic usage of parallel and serial commands, and there doesn't seem to be any point to use them instead of separate commands. However there are times when you need much more complex command executions, and you may combine multiple composite commands to create nested commands to fit your needs. The next example demonstrates how to use the ParallelCommand class and SerialCommand class to create such nested commands.


Example: Nested Commands

As before, create a new Flash document, copy the "commands" folder over, and write a new document class:

Test the movie and the program will print out the following message chunks one-by-one, with a one-second time gap. As in the previous examples, a final complete message will be printed out when all the subcommands are complete.

  • parallel command #1, part 1 of 2
  • parallel command #1, part 2 of 2
  • --------------------------------


  • parallel command #2, part 1 of 3
  • parallel command #2, part 2 of 3
  • parallel command #2, part 3 of 3
  • --------------------------------


  • last command
  • --------------------------------

Here's the concept figure of this example.


Example: Light Circuit

Finally, let's look at a more practical example. We're going to use the command framework we've built to create a light circuit demo, with advanced timing. Before we start, (you guessed it) create a new Flash document, copy the "commands" folder over, and create a new document class.


Step 1: The Light Symbol

Create a movie clip symbol, with a timeline animation where a circle changes its color from gray to yellow.

In the timeline, at the last keyframe, add the following code. This causes the movie clip to stop animating and dispatch a COMPLETE event:

If you want to avoid coding on the timeline, you can create a class for your light movieclip, with a function:

...and then in the constructor for that class, write the following:


Step 2: The Circuit

Arrange light instances on the stage and name them as the following figure shows:


Step 3: Interaction

Drag a Button component from the Components Panel onto the stage and name it "start_btn". We want to execute our commands when this button is pressed.


Step 4: Completion Indicator

Create a text field on the stage and type in your completion message. Next, convert it to a movie clip symbol, and name the instance "completeMessage_mc".


Step 5: The Document Class

Now it's time to edit the document class. Declare a private variable "circuitCommand", which will be used to hold a reference to a Command object:

At the beginning of the program, all lights shall be turned off, i.e. stopped at the first frame, and the completion message should be hidden. So we call the reset() method in the constructor.

Then, create our nested commands that play the light movie clips' animations, lighting them up with proper timing. We use a PlayCommand class here, which simply calls a movie clip's play() method. We'll write the class later.

Next, listen for the COMPLETE event of the command and the CLICK event of the start button:


Step 6: Add Event Handlers

Show the completion message when the command is complete:

Reset the circuit and start the command when the start button is clicked.


Step 7: The Reset Method

The last part for the document class is the reset() method. Nothing to do with commands here.


Step 8: The PlayCommand Class

The last part of this example is the PlayCommand class. As mentioned before, what it does is as simple as calling a movie clip's play() method. As soon the play() method is called in the command's overridden execute() method, the complete() method is also called.

Save this as PlayCommand.as in your "commands" folder.


Step 9: Test the Movie

All right, we're done! Now test the movie and you'll see the lights being lit up from left to right after the start button is clicked. The completion message is shown when all the lights are lit up.

Here's the visual representation of what's going on in this Example:

Compare it to the actual code, and see how easy it is to understand:

Again, with proper code indentation, a complex nested command can be expressed as simple and clean code.


Summary

In this tutorial you've learned the concept of commands. Instructions can be encapsulated into commands which have identical interfaces, just like each button on a remote control has a different action, but the method to invoke each action is the same: press the button.

Also, this tutorial introduced you to two types of composite commands: parallel and serial. These can be used to create nested commands allowing for advanced command execution timing while keeping code clean.


Conclusion

The concept of commands is very convenient and powerful. Code encapsulation is the main approach to simplify things out while programming, and one of the most commonly used methods is the use of command objects. I hope this tutorial helps you understand better how to use commands in practical applications.

In the next part of this tutorial, I'll show you how to integrate TweenLite with the command framework we created in this tutorial, then handle scene transitions with simple and clean code. Thank you very much for reading.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.