Using The Expression Class


BACK TO README.htm

Want to skip all the explanations and see an example of a simple program made with the Expression class? Scroll all the way to the bottom


About animated parts of the character


Character parts

Just as with the Expansion class, the Expression class assumes you have your character divided into parts, each of which is a MovieClip. Some good parts to always have are: eyes, mouth, arms and legs.

Each of the parts of the character has these properties: A set of animation frames, an X and Y position on the screen and a rotation property, that ranges between 0 and 360 degrees.


Types of parts

The parts of a character can be divided into sets of frames, each set of frames represents a looping animation.

Each part of the character can have one of these two types: Animated or Movable. Animated parts are ones that will jump directly to whichever set of frames corresponds to the animation you want to play. Movable parts are ones that will play a transitioning animation between one looping animation and another.

Animated parts are best used for things that change quickly, like a character's eyes and mouth. The frame loops can represent things like "open", "closed", "happy" and "sad".

Movable parts are best used for a character's arms or legs. A character's arm normally stays in one place, which we can consider a frame loop. Then, when the arm moves to a different place, we would use a transition animation between that frame loop and the next one. Frame loops would represent positions, like "up" or "down". Transition animations would represent motion, like "move up" or "move down".


Extra things to set up


Animated parts

In order for the program to properly animate Animated parts, each animated part is going to have to follow these rules:

As you may have noticed, the code in the end of the loop just makes the MovieClip go back to the beginning of the loop.


Movable parts

In order for the program to properly animate Movable parts, each movable part is going to have to follow these rules:

Rules for Frame Loops

In this image, you can see an example of 3 frame loops, labeled "a", "b" and "c". Loop "a" goes from frame 1 to frame 5. Loop "b" goes from frame 6 to frame 10. "c" goes from frame 10 to frame 11.

Rules for transition animations

Example

In this image, you can see one example of a part with 3 frame loops. They are labeled "a", "b" and "c". For each possible pair of frame loops, there's a transition animation. So we have 6 transitions in total: "a2b", "b2a", "a2c", "c2a", "b2c", "c2b".

At the end of each transition loop is a frame with the the "gotoAndPlay(<name>)" code. Here's a list of the values of "name" for each transition.


Setting up the environment with code


What we start off with

To use as examples, we'll have a character made of 4 MovieClips: eye1, eye2, mouth, arm1. The character has more parts, but they won't be animated, so they don't need to be named.

The following image shows the timeline of each MovieClip. eye1 and eye2 both have 2 frame loops: "closed" and "sad". The mouth has two frame loops: "happy" and "sad". The arm has two frame loops: "up" and "down". The arm also has two transition animations, labeled "up2down" and "down2up". Remember that the transition animations are necessary for movable items.

Having that ready, it's time to start writing code


Initializing the Expression object

The first thing to do it load the library using import expansion.*. Then, create a new Expression object, giving it the stage as the first parameter, just like with the Expansion object.

import expansion.*;
var character:Expression = new Expression(stage);

Now you have to insert the parts of the character into the Expression object. You can do this with the function addPart(). This function has 3 parameters.

The first parameter is the reference to the MovieClip to add.

The second one is a number indicating the type of part it is: Animated or Movable. The Expression class has two global constants you can use: Expression.ANIMATED and Expression.MOVABLE

The third parameter is a Boolean value named "eye". This last parameter determines that the part you're inserting is an eye. It will be explained later.

By default, if you use only the first parameter, it will consider it an ANIMATED part and not an eye. addPart() returns a value which is the ID number of the part you inserted. This number will be used to access the part in other functions.

Let's insert our 4 MovieClips

character.addPart(mouth); //Default parameters work in this case
character.addPart(eye1,Expression.ANIMATED,true);
character.addPart(eye2,Expression.ANIMATED,true);
character.addPart(arm1,Expression.MOVABLE);

You can get the ID number of a part by calling the getPartID() function, giving the MovieClip as a parameter. If you had the ID and not the MovieClip, you can use getPartAt() to get the MovieClip from the ID number.

var mouthID:int = character.getPartID(mouth);
var test:MovieClip = character.getPartAt(mouthID);
trace(test == mouth); //Output: true

You can remove a part from the Expression object by calling the removePart() function giving the MovieClip as a parameter, or you can call removePartAt() and give the ID instead.

var mouthID:int = character.getPartID(mouth);
character.removePart(eye1);
character.removePartAt(mouthID);

Creating states

A state is the list of values all the parts of the character should have at one moment. Here's an example of one state: Her arm should be down, her eyes should be closed and her mouth smiling.

The Expression class uses an Array to represent each state. You can create each state entirely in your own, but the Expression class comes with two functions that can help you build your state.

The first function is addStateAttributes(). If you give it an empty Array, it will fill the Array with the values you gave it in the parameters. This function has 4 parameters.

The first parameter is the reference to the state you want to edit. The second parameter is "canBlink", and it tells you if the character will be able to blink in this state. In normal states, this should be true, but if she has her eyes closed, you wouldn't want that.

The third and fourth parameters are "frequency" and "offset". These two values are numbers that control her breathing rhythm. Her breathing follows a sine wave pattern. The "frequency" parameter indicates how many radians of the sine wave are completed in one frame. The "offset" number of a value between 0 and 1, which indicate how heavily she's breathing.

To make a new state and give it the global values, you can do this

var state:Array = new Array();
//The character can blink. She breathes slowly and deeply
character.addStateAttributes(state,true,0.1,1);
//The state now contains the canBlink, frequency and offset values.

If you use NaN as values for "frequency" and "offset", this state will not change the way the character is breathes

The second function to edit states is addPartToState(). This function will let you set all the properties of one of the parts for this state. It has seven parameters.

The first parameter is the reference to the state you want to edit. The second parameter is the ID number of the part want to control.

The third parameter is the name of the frame loop you want the part to go to. The fourth parameter is the rotation the MovieClip should have when this state is activated. The fifth parameter is the rotation speed: how fast the part will spin to get to that rotation. The last two parameters are the X and Y position relative to where the MovieClip was when it was added to the Expression object.

Here's an example of how to use both of these function together to create a state for the character

var state1:Array = new Array();
character.addStateAttributes(state1,false,0.1,1);
character.addPartToState(state1,character.getPartID(eye1),"closed",0,1,0,0);
//Actually, "0,1,0,0" are the default parameters.
//We don't have to put them in this function.
character.addPartToState(state1,character.getPartID(eye2),"closed");
character.addPartToState(state1,character.getPartID(mouth),"happy");
character.addPartToState(state1,character.getPartID(arm1),"down");

If you use NaN as values for the rotation, rotation speed, x position and y position, those values will not be affected when the character uses this state.


Managing states

Once you have created a state, you can insert it into the Expression object. You can do that with the addState() function. This function has two parameters: The name of the state and the state Array. You can add as many states as you want.

//Continuing from the previous code
var state2:Array = new Array();
character.addStateAttributes(state2,true,0.3,1);
character.addPartToState(state2,character.getPartID(eye1),"sad");
character.addPartToState(state2,character.getPartID(eye2),"sad");
character.addPartToState(state2,character.getPartID(mouth),"sad");
character.addPartToState(state2,character.getPartID(arm1),"up");
//Add states
character.addState("expanding",state1);
character.addState("reverting",state2);

You can remove a state by calling removeState() and giving the state's name as a parameter:

character.removeState("reverting");

You can get a state from the Expansion object by calling getStateByName() and giving the name of the state. You can edit the returned state without having to insert it again with addState().

var eye1ID:int = character.getPartID(eye1);
var state:Array = character.getStateByName("expanding");
state[eye1ID][Expression.FRAME] = "sad";
//No need to re-insert it into the Expression object
//The change is already there

As seen in the code before, you can access the state of every single part by using its ID in the [] operator. What's more, you can then access each property of the part using a second [] operator. Each property has its own ID number. There are 5 in total:

Expansion.FRAME The name of the frame loop the part should be in.
Expansion.ROTATION The rotation of the part.
Expansion.ROTATION_SPEED The rotation speed of the part.
Expansion.X_POS The horizontal position of the part compared to its starting point.
Expansion.Y_POS The vertical position of the part compared to its starting point.

You can also access the "canBlink", "frequency" and "offset" properties from the state Array like this:

state.frequency = 0.2;
state.offset = 0.5;
state.canBlink = true;

Just in case that was a bit confusing, in this diagram you can see the structure of each state Array:


Controlling the character

At this point you have inserted all of the parts into the Expression object. Then you created the states and inserted them into the Expression object. You're ready to start controlling the states.

First call activate() to "start time". Just like with the Expansion object, you can start and stop time with this function anytime. Unlike the Expansion object, animated objects will continue to animate even when time is stopped. The flow of time only affects things like breathing, position and rotation.

Use setState() to make a character go to a specific state. Give the name of the state as a parameter.

character.setState("expanding");

And that's it. When you call this function, all of the character's parts will go to the positions given by the state. The animated objects will go to the frame loops, the movable objects will transition towards their frame loops. Any of the parts that were not specified in the state will not change.

You can use the state property to get the name of the state the character is currently in. You can use the curState property to get an Array representing the state the character is in.

trace(character.state); var state:Array = character.curState;

Other than states, there are a few other values you can get from the Expression object:

state:String[read-only] Represents the character's current state.
curState:Object[read-only] A copy of the character's current state Array.
defaultState:String[read-only] Returns the name of the default state of the character.
offset:Number[read-write] Represents how heavily the character is breathing. Recommended values are between 0 and 1.
frequency:Number[read-write] Represents how fast the character is breathing
angle:Number[read-only] The current angle of the sine wave that represents the character's breathing.
position:Number[read-only] The sine of the angle mentioned above.
canBlink:Boolean[read-write] Indicates whether the character is allowed to blink.
blinking:Boolean[read-only] Indicates that the character is blinking.
timer:int[read-only] Tells how many frames are left before a character stops reacting.
reacting:Boolean[read-only] Indicates whether a character is reacting or not.
interruptible:Boolean[read-only] Indicates whether a character can override reacting or not.

Eyes and blinking

So you've seen the "eye" parameter in the addPart function, and the "canBlink" property in the state object. You may already be guess what that means.

When you add a part to the Expression object with addPart() function, you have the option of declaring it as an "eye" by setting the last parameter to "true". Yes, a character can have anywhere between 0 and countless eyes. There's no limit.

character.addPart(eye1,Expression.ANIMATED,true);

Every time a frame passes, there is a 1/100 chance that the character decides to blink. When this happens, all of the character's eyes will blink at once. And they will all stop blinking when the first eye you inserted reaches the end of its blinking animation.

This means you have to set up the frames in each of your eyes according to a few rules:

If all of your eyes are set up like that, congratulations. The character can blink any time! No more work needed.


Breathing

You may have noticed that there are a lot of variables controlling the breathing of the character, but none of these variables are affecting any of the parts of the character. You may be wondering: What's it for?

Really, it's up to you what you want to do with the breathing. The Expression class gives you the raw data of a breathing character. Here's an example of one thing you can do with it:

Create a MovieClip named "chest" and animate it so that it's smallest on frame 1 and biggest on frame 30. This would be the character's chest. You can create an "enterFrame" function that runs this code:

var character:Expression; //We have this thing already set up
var chest:MovieClip; //The animated clip of the character's chest
stage.addEventListener("enterFrame",frame);

function frame(E:Event){
   //character.position is a number between -1 and 1
   //Divide it by 2 and shift it upwards by 1/2
   var position:Number = character.position*0.5+0.5;

   //Multiply it by offset to control how heavily the character breathes
   //Multiply it by 30 so that it can get up to 30 frames big
   var toScale:Number = position*character.offset*30;

   //Get its integer value
   var chestFrame:Number = 1+Math.floor(toScale);

   //Animate a breathing chest
   chest.gotoAndStop(chestFrame);

}

Think of other ways to use it. Maybe you can make an animated mouth just like with the chest. Or you can make the breasts of a female character move up and down by changing their X and Y positions based on the "position" and "offset" values


Reactions

So far you have a character made of parts. And your character can have different states that control the parts. You could work with that already, but this library lets you do even more! Your character can react to different events without you having to manually program them.

The first thing you can do is call the react() function to make the character react to something. This function saves the character's current state, then it gives the character a new state for a short while. When the time is up, the character will take the old state it had before.

character.react("surprised!",30);

The first parameter of this function is the name of the state you want to switch to. The second parameter is the "timer" value, the number of frames you want to wait before returning to the old state. If you set the timer to -1, the character will stay with this reaction forever. You can stop the reaction at any time by calling the endReact() function.

character.react("surprised!",-1);
//Maybe you want to stop the reaction only after
//a special event has ocurred
belly.addEventListener("mouseDown",stopReacting);
function stopReacting(E:Event){
   //This character only stops being surprised
   //when you click her belly
   character.endReact();
}

There are a few side effects of using reactions. While a character is reacting, setState() will not have any effect and the character will never blink. Both of these side effects were made to prevent conflicts between the current state and the stored state.


Reacting to events

Besides being able to make the character react at any time, you can also make the character react to any event at all, by calling the addReaction() function.

This function takes any EventDispatcher at all as the first parameter, and the name of any event as the second parameter. The third parameter is the name of the state to go to when the event happens to the EventDispatcher object. The last parameter is how long the character should spend reacting after the event happened.

Here's how to make the character react to her belly being poked:

var belly:MovieClip; //This is the character's belly
//The character gets surprised for 30 frames
//when the user clicks her belly
character.addReaction(belly,"mouseDown","surprised",30);

You can even use it to monitor events completely unrelated to the character, like if a key was pressed on the keyboard

//The character gets happy when you press the keys
character.addReaction(stage,"keyDown","happy",30);

If you want to find out whether you have a certain reaction for a specific event, you can call getReaction(). The first parameter is the EventDispatcher object you want to check on, and the second parameter is the name of the event you want to get.

var reaction:Object = character.getReaction(stage,"keyDown");

This function returns an object containing these properties:

hit:EventDispatcher The EventDispatcher object waiting for an event.
type:String The name of the event the EventDispatcher is going to trigger.
state:String The name of the state the character will go to when the event is triggered.
timer:String The time the character will spend reacting after the event has been triggered.

When you want to stop a character from responding to a certain event, you can call the removeReaction() function, giving it a reference to the EventDispatcher and the name of the event.

character.removeReaction(stage,"keyDown");

Reacting to expansion

This is probably the most interesting part. Do you remember the setCheckPoint() function from the Expansion class? This is going to be the biggest time saver of them all. A single function can allow the character to react to her expansion all by herself.

The reactTo() function takes an Expansion object as the first parameter. The character will react to all of the "checkpoint" events sent by the Expansion object. Since the events already come with their "reaction" and "timer" attributes, your work is already done for you.

Without further delay, here's a code that works:

//Imagine the belly has 80 frames
var expander:Expansion = new Expansion(stage);
var bellyID:int = expander.addExpandable(belly,80);
expander.setGrowth(bellyID,1);
expander.setCheckPoint(bellyID, 20, 1, "wondering", 10);
expander.setCheckPoint(bellyID, 40, 1, "surprised", 10);
expander.setCheckPoint(bellyID, 60, 1, "worried", 10);
expander.setCheckPoint(bellyID, 80, 1, "finallyover!", 10);
expander.setCheckPoint(bellyID, 40,-1, "disappointed", 10);
expander.setCheckPoint(bellyID, 1,-1, "relieved", 30);
var character:Expression = new Expression(stage);
/*
...
Setting up all the character's parts and states.
Too long to put in this example.
See the end of the page for a complete program.
...
*/
character.reactTo(expander);
expander.activate();
character.activate();

To summarize, this code does the following. The character starts expanding. At frame 20, she starts wondering why she feels weird. At frame 40 she's surprised that her belly grew. At frame 60 she's worried about how big she's getting. At frame 80, she's happy it's over. While she's reverting back to her normal size, she gets disappointed at frame 40, and then is relieved it's gone at frame 1.

reactTo() has a second parameter named "permanent". By default this value is set to false. If this value is false, the character will react to the checkpoint and then go back to her old state. If the value is true, the character will switch to the new state and stay, without reacting.

Finally, if you set the first parameter to null, the character will stop reacting to checkpoint events from any Expansion object


Destroying the object

Here's an important note!

If you even plan to stop using the Expression object before the program ends, call the destroy() function. Here's why:

When you use the addReaction() and reactTo() functions, Flash stores event handlers for each object in separate lists. When the Expression object is deleted, Flash cannot know which of the event handlers were associated with it, so it never deletes them.

Deleting the Expression object without calling destroy() can result in a memory leak, which will eventually cause the program to slow down or throw an error. Always remember to destroy it when you're done using it.

/* ... */
// Maybe I decided to stop using my old character
character.destroy();
// And replace it with a new one
character = new Expression(stage);
/* ... */

Example program

Here's an example of a program that uses both the Expansion and the Expression classes. The program will use the 4 MovieClips from the beginning of this page and a belly MovieClip. In total there would be 5 MovieClips: belly, eye1, eye2, mouth and arm1. Let's assume the belly has 200 frames.

This program will have the belly expanding constantly. When a key is pressed, the expansion will revert. And if a key is pressed again, the belly will expand again. It's just a belly expansion toggle button.

The character will react to the expansion and she will also react to being clicked on the belly. Her reaction when being clicked will depend on what size she has.

import expansion.*;
//Initialize expansion
var expander:Expansion = new Expansion(stage);
var bellyID:int = expander.addExpandable(belly,200);
expander.setGrowth(bellyID,1);

//Initialize character parts
var character:Expression = new Expression(stage);
character.addPart(eye1,Expression.ANIMATED,true);
character.addPart(eye2,Expression.ANIMATED,true);
character.addPart(mouth,Expression.ANIMATED,false);
character.addPart(arm1,Expression.MOVABLE,false);

/* Initialize character states */
var temp:Array;
//State 1: normal. Sleeping character
temp = [];
character.addStateAttributes(temp,true,0.1,1.0);
character.addPartToState(temp,character.getPartID(eye1),"closed");
character.addPartToState(temp,character.getPartID(eye2),"closed");
character.addPartToState(temp,character.getPartID(mouth),"sad");
character.addPartToState(temp,character.getPartID(arm1),"down");
character.addState("normal",temp);

//State 2: expanding. Enjoying the expansion.
temp = [];
character.addStateAttributes(temp,true,0.2,0.7);
character.addPartToState(temp,character.getPartID(eye1),"closed");
character.addPartToState(temp,character.getPartID(eye2),"closed");
character.addPartToState(temp,character.getPartID(mouth),"happy");
character.addPartToState(temp,character.getPartID(arm1),"down");
character.addState("expanding",temp);

//State 3: nervous. Bigger than she's comfortable with.
temp = [];
character.addStateAttributes(temp,true,0.2,0.7);
character.addPartToState(temp,character.getPartID(eye1),"sad");
character.addPartToState(temp,character.getPartID(eye2),"sad");
character.addPartToState(temp,character.getPartID(mouth),"happy");
character.addPartToState(temp,character.getPartID(arm1),"down");
character.addState("nervous",temp);

//State 4: sad. She doesn't want to revert.
temp = [];
character.addStateAttributes(temp,true,0.2,0.7);
character.addPartToState(temp,character.getPartID(eye1),"sad");
character.addPartToState(temp,character.getPartID(eye2),"sad");
character.addPartToState(temp,character.getPartID(mouth),"sad"); character.addPartToState(temp,character.getPartID(arm1),"down");
character.addState("sad",temp);

//Reaction 1: unhappy. Belly poked when she's small.
temp = [];
character.addStateAttributes(temp,true,0.2,0.7);
character.addPartToState(temp,character.getPartID(eye1),"sad");
character.addPartToState(temp,character.getPartID(eye2),"closed");
character.addPartToState(temp,character.getPartID(mouth),"sad");
character.addPartToState(temp,character.getPartID(arm1),"up");
character.addState("unhappy",temp);

//Reaction 2: laugh. Belly poked when she's big.
temp = [];
character.addStateAttributes(temp,true,0.2,0.7);
character.addPartToState(temp,character.getPartID(eye1),"closed");
character.addPartToState(temp,character.getPartID(eye2),"sad");
character.addPartToState(temp,character.getPartID(mouth),"happy");
character.addPartToState(temp,character.getPartID(arm1),"up");
character.addState("laugh",temp);

//Set the default state
character.setState("normal");

/* Set checkpoints */
//Expanding checkpoints
expander.setCheckPoint(bellyID,50,1,"expanding",30);
expander.setCheckPoint(bellyID,200,1,"nervous",30);

//Reverting checkpoints
expander.setCheckPoint(bellyID,100,-1,"sad",30);
expander.setCheckPoint(bellyID,1,-1,"normal",30);

//These checkpoints are not detected by the character
expander.setCheckPoint(bellyID,100, 1,null,0);
expander.setCheckPoint(bellyID,100,-1,null,0);

/* reactTo! */
//Permanent=true.
//We want the belly's expansion to define her
//default state, not just a temporary one.
character.reactTo(expander,true);

/* Reactions */
//The character doesn't like being poked when small.
character.addReaction(belly,"unhappy");

//Switch the reaction when the belly size
//crosses the 100 mark.
character.addEventListener("checkpoint",switchBellyReaction);
function switchBellyReaction(E:ExpansionEvent){
   if(E.direction < 0){
      //Belly is getting smaller
      character.removeReaction(belly,"laugh");
      character.addReaction(belly,"unhappy");
   } else {
      //Belly is getting bigger
      character.removeReaction(belly,"unhappy");
      character.addReaction(belly,"laugh");
   }
}

/* Keyboard event to toggle expansion */
stage.addEventListener("keyDown",switchExpansion);
function switchExpansion(E:Event){
   expander.growth = -expander.getGrowth(bellyID);
}

Don't let the length of the code make you nervous. It's not complicated, it's just a bit tedious defining all of the states for the character. If you want to shorten the code a bit, you can create the states manually instead of using the state creation functions.

I hope this tutorial was helpful, and I strongly recommend reading the Expression reference manual to get a more structured view of the Expression class. Also, you will find out what kind of events the Expression object can trigger.


Using the Speech class
BACK TO README.htm

© 2014 Doom the wolf. http://doom-the-wolf.deviantart.com