6. Animation

Amulet supplies built-in support for animations and other time-based effects using Animation Constraints. The most common effects, such as moving smoothly, can be achieved with only a few lines of code. More complex effects can be achieved as well.

6.1 Include Files

The animation constraint prototypes and other animation controls are exported from the file anim.h, which is automatically included when you include amulet.h. Good examples of the use of animation constraints are in the files amulet/src/anim/testanimators.cc, amulet/samples/circuit/circuit.cc, amulet/samples/tree.cc, and amulet/samples/space2.cc.

6.2 Introduction

Amulet supports animations and timers. The basic mechanism for animation is called an ``animation constraint,'' which can be attached to one or more slots on an object to smoothly change their values over time. For instance, if an animation constraint is attached to the Am_LEFT and Am_TOP slots of a graphical object, then whenever the object's position is changed from (L1, T1) to (L2, T2), the animation constraint will delay the change and smoothly interpolate Am_LEFT from L1 to L2 and Am_TOP from T1 to T2. Thus the animation happens once for each time the value is set (but see Section 6.10 for how to make the animation continuous).

Animation constraints are instances of the Am_Animator object. To add an animation constraint to an object, make an instance of Am_Animator and use Am_Animate_With to set it into each slot it should control. For instance, here is how a rectangle's position would be animated:

	Am_Object my_animator = Am_Animator.Create (``my_animator'');
	Am_Object moving_rectangle = Am_Rectangle.Create ()
		.Set (Am_LEFT, Am_Animate_With (my_animator))
		.Set (Am_TOP, Am_Animate_With (my_animator))
There are many different kinds of animator objects, and each kind has a number of parameters you can control. Also, you can access the low-level timer mechanism if you want to create a simple timer, or you need a new kind of animation constraint.

More complete information about the Animation constraints in Amulet are provided in a conference paper, which is available from the Amulet WWW area:

Brad A. Myers, Robert C. Miller, Rich McDaniel, and Alan Ferrency, ``Easily Adding Animations to Interfaces Using Constraints.'' ACM Symposium on User Interface Software and Technology, UIST'96, November 6-8, 1996. Seattle, WA. pp. 119-128.

6.3 Am_Time

To support animations portably across all platforms, the Gem level of Amulet exports the Am_Time class (defined in gdefs.h). Most Amulet users will not need to use the methods of Am_Time, and can just create Am_Times using an integer number of milliseconds, and set them into the appropriate slots. The interface to Am_Time is:

class Am_Time {
  Am_Time();  // defaults to 0 delta time
  Am_Time (unsigned long milliseconds); // starting number of msec
  static Am_Time Now(); // create a time object = to the current time
  bool Is_Future () const; // is this time > Now()
  bool Is_Past () const;
  bool operator> (const Am_Time& other) const;
  bool operator< (const Am_Time& other) const;
  bool operator>= (const Am_Time& other) const;
  bool operator<= (const Am_Time& other) const;

  unsigned long Milliseconds() const;  //return this time as msec
  Am_Time operator- (unsigned long milliseconds) const;
  Am_Time operator+ (unsigned long milliseconds) const;
  Am_Time operator- (const Am_Time& other) const;
  Am_Time operator+ (const Am_Time& other) const;
  void operator+= (unsigned long milliseconds);
  void operator-= (unsigned long milliseconds);
  void operator+= (const Am_Time& other);
  void operator-= (const Am_Time& other);

  bool Zero() const;  // is the delta time 0?  (uninitialized time test)

ostream& operator<< (ostream& os, const Am_Time& time);

//returns the current time and date as a string, like
//  "Fri Jan 17 16:03:55 EST 1997\n".
extern Am_String Am_Get_Time_And_Date(); 

6.4 General Interface

Animation constraints internally are subclasses of Am_Constraint, but the interface to the programmer is more like graphical objects or interactors. Each Animator is represented by an Amulet Object, so you control the animators by setting slots. Once the animator object is configured, you attach it to a slot of an object using the function:

Am_Constraint* Am_Animate_With (const Am_Object& animator);
which returns a constraint that you set into one or more slots. The animator object can be retrieved from a slot using:

Am_Object Am_Get_Animator (Am_Object obj, Am_Slot_Key key);
Am_Object Am_Get_Animator (Am_Constraint* constraint);
The first routine gets the animator object from a slot of an object, and the second gets it from an Am_Constraint object, which is not likely to be needed much, since the constraints used for the animators are generally not accessed by programmers.

The animation is triggered when the slot is Set in the normal way. Thus, given the code above that sets an animator called my_animator on the Am_LEFT and Am_TOP slots of moving_rectangle, setting either of those slots will trigger the animation. Any time a new value is put in one of these slots, the animation is triggered, so a ``regular'' (formula or web) constraint on Am_LEFT or Am_TOP can also trigger the animation. For example, all of the following will cause moving_rectangle to animate:

moving_rectangle.Set(Am_LEFT, 300).Set(Am_TOP, 500);
moving_rectangle.Set(Am_LEFT, left_formula);
The result of the animation being triggered is that instead of the slot value jumping to the new value that is set into the slot, the slot instead takes on a series of values interpolated from the old to the new values. In the case of the constraint, this will cause the moving_rectangle to ``lag behind'' the constrained value, and travel at a fixed maximum rate (which is controllable by the programmer, see Section 6.7).

Because the animation constraints work by setting the slot to a sequence of values, constraints that depend on the slot will be continuously re-evaluated as well. For example, in the amulet/samples/tree.cc sample program, animation constraints are put on the position slots of nodes. The end-points of the lines are constrained to the nodes, so the lines move smoothly as well.

6.5 Starting and Stopping Animations

There are two routines available to explicitly stop an animation. Am_Stop_Animator causes the animation to stop and the slot value to immediately jump to its final value. Am_Abort_Animator on the other hand, causes the animation to stop and the value in the slot to stay at its current value, no matter what it is. Thus using Am_Abort_Animator can leave the slot with a value different from that the slot was originally set to.

void Am_Abort_Animator (Am_Object interp);
void Am_Stop_Animator (Am_Object interp);
Normally the animators are automatically aborted when a new value is set. Thus, if the animation is busy setting the slot to a sequence of values, and a new value is set into the slot, the old animation will abort and a new animation will immediately be started towards the newly set value. This behavior can be changed using the Am_INTERRUPTIBLE slot (see Section 6.9).

To explicitly start an animation, use Am_Start_Animator. value1 can be supplied to specify the original value for the animation to start at, and value2 can be supplied to specify the target or final value for the animation.

void Am_Start_Animator (Am_Object interp,Am_Value value1 = Am_No_Value,
Am_Value value2 = Am_No_Value);

6.6 Turning On and Off Animations

Animators are normally on and operating whenever attached to a slot. There are a number of mechanisms for controlling when the animations operate more precisely.

Animators, like widgets and Interactors, respond to the Am_ACTIVE slot of the animator object. If Am_ACTIVE is false, the animator will not be started, and the slot Set will operate normally (jump to the new value). If Am_ACTIVE is true (the default), then the animation will cause the slot value to change smoothly. Setting the Am_ACTIVE slot while the animation is running will not abort the animator. Use Am_Abort_Animator (see below) for that.

my_animator.Set(Am_ACTIVE, false);  //this animator now won't run
Sometimes it is useful to Set a slot which contains an animation, but not have the animation operate. In this case, you can use the special Set flag Am_NO_ANIMATION. If the slot has an attached animation, that animation is disabled for this one Set, and the slot value will jump to the specified value. Subsequent Set's will not be affected (unless they specify Am_NO_ANIMATION as well):

moving_rectangle.Set(Am_LEFT, 100, Am_NO_ANIMATION); //no animation 
If a slot does not have an animator attached, then the Am_NO_ANIMATION does nothing, so it is safe to use this flag whereever it might be important.

Conversely, sometimes you want to make an animation run that is normally disabled. In this case the Am_WITH_ANIMATION set flag can be used. If there is an animator on the slot, then the animation will be run for this set ignoring the value of the Am_ACTIVE slot of the animator. If the slot does not have an animator attached, then the Am_WITH_ANIMATION flag is ignored.

my_animator.Set(Am_ACTIVE, false);  //this animator now will NOT normally run
Am_Object moving_rectangle = Am_Rectangle.Create ()
		.Set (Am_LEFT, Am_Animate_With (my_animator))
		.Set (Am_TOP, Am_Animate_With (my_animator));
moving_rectangle.Set(Am_LEFT, 300); //NO animation
moving_rectangle.Set(Am_LEFT, 122, Am_WITH_ANIMATION); //animates

6.7 Parameters of all Animators

Animation constraints take a number of parameters, which are slots in the instance of Am_Animator you create. Some of the parameters are global to all animators, and others are specific to one type of animator. This section lists the slots common to all animators:

NOTE: You pick one of Am_VELOCITY or Am_DURATION to set and the other is calculated from the one you set. The default is a fixed velocity of 200 pixels per second, with the duration depending on how far the values are apart.

6.8 Types of Animations

So far, all the animation discussed have been on the position slots of graphical objects. However, animations can be attached to any type of slot. We have built-in animations that can animate any numeric values, colors, line-styles, visibilty, and the point-list of polygons. You can define your own animators for new types of slots, if desired (see Section 6.14). Examples of using most of these animators may be found in the animations test program, src/anim/testanimators.cc.

The current animators supplied in the library are as follows. These are organized approximately in order of usefulness (some of the later ones are mainly interesting for demos.)

6.8.1 Am_Animator

Am_Animator does a linear interpolation of any number of numeric slots (integers or floats). This is normally used for positions. Set the same animator into multiple slots to insure that all the values are animated at the same rate. For example, the dog is first constrained with regular formula constraints to be behind the horse, and then an animation constraint is added to the position slots.

  doganimation = Am_Animator.Create (``dog_position'');
  dog.Set(Am_TOP, same_as_horse_bottom)
    .Set(Am_LEFT, behind_horse)
    .Set (Am_LEFT, Am_Animate_With (doganimation))
    .Set (Am_TOP, Am_Animate_With (doganimation))

6.8.2 Am_Style_Animator

Am_Style_Animator linear interpolation of one Am_Style slot, and looks at changes in the color and line thickness parts of the style (other parts of the styles are ignored by the animator). The color can be animated in two ways: either through the Red-Green-Blue (RGB) color space or through the Hue-Saturation-Value (HSV) color space. The choice is controlled by the boolean slot Am_RGB_COLOR. Animations through colors spaces basically draw a line through the three dimensional color cube from the initial value to the final value, and animate along that line. Animations in the RGB color space often go through grey in the middle, which isn't as attactive as HSV animations in some cases. On the other hand, going from a color to black or white (fading the color in or out) looks better in RGB. The default for Am_RGB_COLOR is true.

The following example makes the fill and line style of the rectangle depend with a regular constraint on the values from palettes (fetch_color and fetch_line). These are declared to be Multi_Constraint so the constraints to not disappear if the slot is explicitly set. An Am_Style_Animator is attached to the each slot so the colors and line-thickness will change smootly over 2000 milliseconds (2 seconds). The fill style will animate through the HSV color space (Am_RGB_COLOR is false) and the line animator will go through RGB color space (Am_RGB_COLOR is the default = true).

Am_Object Color_Changer = Am_Rectangle.Create ()
        .Set (Am_LEFT, 175)
        .Set (Am_TOP, 125)
        .Set (Am_WIDTH, 50)
        .Set (Am_HEIGHT, 50)
        .Set (Am_FILL_STYLE, fetch_color.Multi_Constraint())
        .Set (Am_FILL_STYLE,
Am_Animate_With (Am_Style_Animator.Create (``fill_style_anim'')
.Set (Am_RGB_COLOR, false)
	 				 .Set (Am_DURATION, Am_Time (2000))))
        .Set (Am_LINE_STYLE, Am_Black)
        .Set (Am_LINE_STYLE, fetch_line.Multi_Constraint())
        .Set (Am_LINE_STYLE,
	     	    Am_Animate_With (Am_Style_Animator.Create (``line_style_anim'')
	  				 .Set (Am_DURATION, Am_Time (2000))))

6.8.3 Am_Visible_Animator

Am_Visible_Animator reacts to changes in Am_VISIBLE by flying an object onscreen or offscreen, or by shrinking and growing the object. Unlike other animators, the Am_Visible_Animator modifies multiple slots, not just the one it is attached to. For example, even though the animator goes in the Am_VISIBLE slot, it can also modifiy the position and size slots of the object. (Amulet constraints can perform arbitrary side effects.) See src/anim/testanimators.cc for examples.

The various ways to animate an object becoming invisible and visible are: fading out, flying away, and shrinking. Each of these operates in reverse depending on whether the Am_VISIBLE slot changes to be true or false. Because all of these effects are controlled by different parameters of the Am_Visible_Animator, you can even use more than one effect at the same time, just by setting more than one of the controlling variables. See also the Am_Fly_Apart_Animator (Section 6.8.4) which is also attached to the Am_VISIBLE slot. Fading out

To have objects fade out they must be part of a Am_Fade_Group (see Section 4.8.4 in the Opal manual). The Am_Visible_Animator then sets the Am_VALUE of the fade group with a series of values to make the group fade out or fade back in. To get the Am_Visible_Animator to use fading, set the Am_USE_FADING slot of the animator to true. For example:

  fade_group = Am_Fade_Group.Create (``Fade_Group'')
    .Set (Am_WIDTH, Am_From_Owner(Am_WIDTH))
    .Set (Am_HEIGHT, Am_From_Owner(Am_HEIGHT))
    .Set (Am_VALUE, 0) //fully visible to start
    .Set (Am_VISIBLE, Am_Animate_With (Am_Visible_Animator.Create(``fade'')
				       .Set (Am_USE_FADING, true))); Fly Offscreen

Another option for changing visibility is for objects to fly offscreen. This is controlled by setting the animator's Am_LEFT and/or Am_TOP slots. If these slots contain integer values, then the object will be moved to those values when it becomes invisible, and moved from those values to the object's real position when it become visible. (The default values for these slots of the animator are Am_No_Value.) For example, to have the object fly offscreen to the upper right, you might use:

obj.Set (Am_VISIBLE, Am_Animate_With(Am_Visible_Animator.Create()
						   .Set (Am_LEFT, 200)
						   .Set (Am_TOP, -200)));
To have the object just fly vertically, just set the Am_TOP and leave the Am_LEFT as Am_No_Value:

obj.Set (Am_VISIBLE, Am_Animate_With(Am_Visible_Animator.Create()
						   .Set (Am_TOP, -200)));
The position to move to (just like any other slot) can be computed by a constraint. For example, in the samples/checkers.cc sample program, the black pieces go down and the white pieces go up when they become invisible. Note that whether the piece is visible or not is also computed by a formula in this example. The animation is triggered no matter how the Am_VISIBLE slot changes.

Am_Define_Formula(int, black_at_bottom) {
  if ((bool)self.Get_Object(Am_OPERATES_ON).Get(BLACK_PLAYER))
		return 550;  //at the bottom
  else return -1000; //at the top
  Am_Object visible_animator = Am_Visible_Animator.Create()
    .Set (Am_LEFT, -1000)
    .Set (Am_TOP, black_at_bottom)
    .Set (Am_ACTIVE, visible_animator_active)
    .Set (Am_DURATION, Am_Time(500))
Am_Object Piece = Am_Group.Create (``piece'')
 .Set (Am_VISIBLE, piece_visible)
 .Set (Am_VISIBLE, Am_Animate_With (visible_animator));
Another use for this effect might be to have the object move out from some source, for example a duplicated object could move from the original. Shrinking

Another option for changing visibility is for objects shrink and grow. This is controlled by setting the animator's Am_WIDTH and/or Am_HEIGHT slots. If these slots contain integer values, then the object will be shrink to those values when it becomes invisible, and grow from those values to the object's real size when it become visible. (The default values for these slots of the animator are Am_No_Value.) For example, to have the object shrink to zero height, you might use:

obj.Set (Am_VISIBLE, Am_Animate_With(Am_Visible_Animator.Create()
						   .Set (Am_HEIGHT, 0)));

6.8.4 Am_Fly_Apart_Animator

The Am_Fly_Apart_Animator reacts to changes in Am_VISIBLE by simulating breaking the object up into parts and flying the parts offscreen (or onscreen, depending on whether the object is going invisible or visible). This is more special-purpose than the Am_Visible_Animator. The effect was inspired by the way objects become invisible in the Alice system (see http://alice.virginia.edu)

The Am_Fly_Apart_Animator does not take any extra parameters, and it only is effective on groups.

group3.Set(Am_VISIBLE, Am_Animate_With(Am_Fly_Apart_Animator.Create()));

6.8.5 Am_Blink_Animator

The Am_Blink_Animator alternates a slot between two values (of any type). Specify the two values in the Am_VALUE_1 and Am_VALUE_2 slots of the animator (which default to true and false). Unlike other animators, the blink animator by default runs forever, once it starts. To stop it, call Am_Abort_Animator(). A typical use is to blink an object, by putting it into the Am_VISIBLE slot:

obj.Set (Am_VISIBLE, Am_Animate_With (Am_Blink_Animator.Create()));
Another example is to change the color of an object:

obj.Set (Am_FILL_STYLE, Am_Blue)
   .Set (Am_FILL_STYLE, Am_Animate_With (Am_Blink_Animator.Create()
					  .Set(Am_VALUE_1, Am_Blue)
					  .Set(Am_VALUE_2, Am_Green)));

6.8.6 Am_Through_List_Animator

Am_Through_List_Animator iterates a slot through a list of values. This is especially useful on the Am_IMAGE slot of an Am_Bitmap object, to animate through a sequence of images, but it can be used for any slot. For example, you might want an object to take on a specific set of colors.

Put an Am_Value_List of values into the Am_LIST_OF_VALUES slot. For example, the walking eye in src/anim/testanimators uses a formula in the Am_LIST_OF_VALUES to pick a different list of images depending on whether the eye is walking left or right. The WALKER is a different animator that is moving the eye's position. This also uses the command Am_Animation_Wrap_Command to make the animator wrap around when it reaches its final value instead of stopping (see Section 6.10).

Am_Value_List eye_walking_right, eye_walking_left;

static void init_eye () {
  int i;
  for (i = 0; i < 6; i++)
    eye_walking_right.Add (read_pixmap(pixmapfilename[i]));
  for (; i < 12; i++)
    eye_walking_left.Add (read_pixmap(pixmapfilename[i]));
Am_Define_Value_List_Formula(which_way) {
  int x_offset = self.Get_Object(Am_OPERATES_ON)
    .Get_Object (WALKER).Get_Object(Am_COMMAND).Get(Am_X_OFFSET);
  if (x_offset > 0) return eye_walking_right;
  else return eye_walking_left;
obj.Set (Am_IMAGE, Am_Animate_With (Am_Through_List_Animator.Create()
	 	 .Set(Am_LIST_OF_VALUES, which_way)
	 	 .Set_Part (Am_COMMAND, Am_Animation_Wrap_Command.Create ())))

6.8.7 Am_Point_List_Animator

The Am_Point_List_Animator animates changes to the Am_POINT_LIST slot of a polygon. It checks to see whether the old point list and the new point list differ by a single point, and if so, it animates the point list so that extra point grows out of the center of the line between the two adjacent points. If the new or old point list is empty, then the animation is from the center of the object. If neither of these cases holds, then the extra points are all added at the end of the point list. There are no extra parameters to the Am_Point_List_Animator.

polygon = Am_Polygon.Create (``Polygon'')
  .Set(Am_FILL_STYLE, Am_Red)
  .Set(Am_POINT_LIST, triangle_point_list)
  .Set(Am_POINT_LIST, Am_Animate_With (Am_Point_List_Animator.Create()));

6.8.8 Am_Stepping_Animator

Am_Stepping_Animator interpolates numeric slots in quantized steps. The steps are set into the Am_SMALL_INCREMENT slot. Currently, the Am_Stepping_Animator can only handle one slot, unlike the Am_Animator.

  hopper = Am_Arc.Create (``Hopper'')
    .Set (Am_LEFT, Am_From_Sibling (MOVER, Am_LEFT))
    .Set (Am_LEFT, Am_Animate_With (
	   			   Am_Stepping_Animator.Create (``Hopper_Interp'')
	  			    .Set (Am_REPEAT_DELAY, Am_Time(1000))
				    .Set (Am_SMALL_INCREMENT, 20)))
    .Set (Am_TOP, 20)
    .Set (Am_FILL_STYLE, Am_Green)
  window.Add_Part (hopper);

6.8.9 Am_Exaggerated_Animator

Am_Exaggerated_Animator does a linear interpolation of numeric slots, with anticipation (``windup'') at start of animation, and feedback (``wiggling'') at end. Thus, the Am_Exaggerated_Animator has three phases. Currently, the Am_Exaggerated_Animator can only handle a single slot, unlike the Am_Animator. The parameters to the Am_Exaggerated_Animator are:

As an example:

  wiggler = Am_Arc.Create (``Wiggler'')
    .Set(Am_WIDTH, 20)
    .Set(Am_HEIGHT, 20)
    .Set (Am_TOP, Am_From_Sibling (MOVER, Am_TOP))
    .Set (Am_TOP, Am_Animate_With (Am_Exaggerated_Animator.Create ()
	    			  .Set(Am_WIGGLE_AMOUNT, 4)))
    .Set (Am_LEFT, 60);

6.9 Interruptible Animations

Normally, whenever a new value is set into a slot that is being animated, the animations is interrupted (aborted) and a new animation is started going from the slot's current value (which may be an intermediate value between the original and target values) and going towards the new value. However, the animators also have a feature that allow you to specify that the original animation should continue to completion, and only then should the new animation start. This is implemented internally by keeping a list of the values set into the slot, and popping off the next one when the previous animation is finished. See the Figure.

This is controlled using the slot Am_INTERRUPTIBLE. If Am_INTERRUPTIBLE is true (the default) then animations are interrupted when new values arrive. If false, then the new values are queued.

6.10 Commands on Completion of Animations

When any animation is finished, it executes the Am_DO_METHOD of the command that is its Am_COMMAND part. Thus, you can add commands to animation objects just as you would for widgets or interactors. This can be used to achieve various affects. In the samples/circuit/circuit.cc sample program, the command in the animation triggers the value of the next circuit element, so the values do not propagate until the animation finishes and the previous value arrives. There are two methods important in the command in animations:

There are also two built in commands that allow the animations to be continuous, and not to stop when the value reaches the destination value. Instead the animation starts over or continues.

6.11 The Timing Function

Normally, you want objects to change smoothly during animations. However, for movements on the screen, sometimes a ``slow-in-and-slow-out'' animation looks more natural. This is controlled through the Timing Function associated with an animator. The Timing function maps the current elapsed time into a floating point percentage (between 0.0 and 1.0) where 1.0 is the end of the animation. For example, the default Linear interpolator is

Am_Define_Method (Am_Timing_Function, float, Am_Linear_Timing, 
		  (Am_Object interp, Am_Time t))
  Am_Time total_time = interp.Get (Am_CURRENT_DURATION);
  unsigned long t_ms = t.Milliseconds();
  unsigned long total_ms = total_time.Milliseconds();
  if (t_ms >= total_ms)
    return 1.0;
    return (float)((double)t_ms / (double)total_ms);
The built in timing functions are:

6.12 Creating a Simple Timer

Am_Animator objects may be used as simple timers. To implement this, create an instance of Am_Animator whose Am_REPEAT_DELAY is the desired time interval and whose Am_ANIMATION_METHOD is the code to run at every timer tick, and then start it running with Am_Start_Animator.

For example:

Am_Define_Method (Am_Timer_Method, void, my_timer_tick,
  (Am_Object my_timer, const Am_Time& elapsed_time)) {
   // ... code to execute once every timer tick

Am_Object my_timer = Am_Animator.Create (``my_timer'')
    .Set (Am_REPEAT_DELAY, Am_Time (100))    // every 100 milliseconds,
    .Set (Am_ANIMATION_METHOD, my_timer_tick)// call this method
Am_Start_Animator (my_timer);   // start the timer running
To stop the timer, use Am_Stop_Animator().

6.13 Debugging Animations

Like interactors, the Animators are annotated with tracing output which can be turned to produce a trace of the actions of the animators. The regular functions for enabling and disabling trace output for interactor objects can also be used on animator objects (see Section 5.7). The Inspector also has a menu for pausing, resuming and single-stepping all animations.

6.14 Creating a New Animator

If you want to animate a new type of value, and if the available parameterizations are not sufficient, then you can create your own type of Am_Animator object. The simplest way to customize an animator is to specify a new Path Function.

Am_Define_Method (Am_Path_Function, Am_Value, my_path_function,
		  (Am_Object anim, Am_Value value1, Am_Value value2, float tau))
The path function takes the return value from the timing function called tau (from 0.0 to 1.0) and calculates a value between value1 and value2 and returns it. The path function is set into the slot Am_PATH_FUNCTION:

my_animator = Am_Animator.Create()
    .Set (Am_PATH_FUNCTION, my_path_function)
If you need more explicit control over the initialization and finalization of the animator, you can specify the following methods

Two other methods are used to handle interruptible and uninterruptible animations. It is rarely necessary to override these methods.

Last Modified: 01:44pm EDT, August 13, 1997