Matthew Allum
mallum@openedhand.com
Creating Animations with Clutter With Clutter using hardware accelration for graphics rendering, complex and fast animations are possible. This chapter describes basic techniques and the utilities Clutter provides in aiding animation creation.
Basic Animations The most basic way to create animations with Clutter is via the use of g_timeout_add(). This enables a callback function to be called at a defined interval. The callback function can then modify actors visual properties as to produce an animation. Simple Rotation... struct RotationClosure { ClutterActor *actor; ClutterFixed final_angle; ClutterFixed current_angle; }; static gboolean rotate_actor (gpointer data) { RotationClosure *clos = data; clutter_actor_set_rotationx (clos->actor, clos->current_angle, 0, 0, 0); /* add one degree */ clos->current_angle += COGL_FIXED_ONE; if (clos->current_angle == clos->final_angle) return FALSE; return TRUE; } static void rotate_actor_cleanup (gpointer data) { RotationClosure *clos = data; g_object_unref (clos->actor); g_free (clos); } ... RotationClosure *clos = NULL; clos = g_new (RotationClosure, 1); clos->actor = g_object_ref (an_actor); clos->final_angle = CLUTTER_FLOAT_TO_FIXED (360.0); clos->current_angle = 0; g_timeout_add_full (1000 / 360, /* fps to interval in milliseconds */ rotate_actor, clos, rotate_actor_cleanup); Priorities %G_PRIORITY_DEFAULT should always be used as the timeouts priority (in case of g_timeout_add_full()) as not to intefere with Clutter's scheduling of repaints and input event handling.
Timelines #ClutterTimelines abstract a set period of time with a set frame rate at which to call a provided callback. #ClutterTimelines also extend the timeout sources functionality further by: Having a set duration (in milliseconds) and a set 'frame rate' - that is, the rate at which the callback is called Passing current progress information to the callback Handling 'dropped frames' and guarenteeing the set duration by skipping over frames if the callback cannot keep up with the set frame rate Querying the number of milliseconds elapsed between the current and previous callback. Allowing the timeline to be modified on the fly as well as being stopped, started, looped, rewound and reversed Using a #ClutterTimeoutPool to more efficiently schedule multiple timeout sources without incurring in potential starvation of the main loop slices A Timeline is created with; clutter_timeline_new (n_frames, frames_per_seconds); Taking a number of frames and a frames per second, or by; clutter_timeline_new_for_duration (msecs); Which takes the duration of the timeline in milliseconds with a default frame rate (See clutter_get_default_frame_rate()). The speed, duration and number of frames of the timeline then be modifed via the objects properties and API calls. The timeline can be made to loop by setting its "loop" property to %TRUE. The timelines is started via clutter_timeline_start() and its playback further manipulated by the clutter_timeline_pause(), clutter_timeline_stop(), clutter_timeline_rewind() and clutter_timeline_skip() calls. By attaching a handler to the timeline's #ClutterTimeline::new-frame signal a timeline can then be used to drive an animation by altering an actor's visual properties in this callback. The callback looks like: void on_new_frame (ClutterTimeline *timeline, gint frame_num, gpointer user_data) { } The frame_num parameter is set to the timeline's current frame number (which is between 1 and the "num-frames" property). This value can be used to compute the state of a particular animation that is dependant on the frame numer. The clutter_timeline_get_progress() function can also be used to get a normalised value of the timeline's current position between 0 and 1. Timelines can also be played in reverse by setting the direction using clutter_timeline_set_direction(), and can also have a one-time delay set before they begin playing by using clutter_timeline_set_delay(). Timelines can also control a pyshical simulation; the clutter_timeline_get_delta() function allows retrieving the number of frames and milliseconds elapsed since the previous callback to ensure the physics engine to be able to take the actual time elapsed between iterations into account. The following example demonstrates rotating an actor with a timeline. #include <clutter/clutter.h> void on_new_frame (ClutterTimeline *timeline, gint frame_num, gpointer data) { ClutterActor *actor = CLUTTER_ACTOR(data); clutter_actor_set_rotation (actor, CLUTTER_Z_AXIS, (gdouble) frame_num, clutter_actor_get_width (actor) / 2, clutter_actor_get_height (actor) / 2, 0); } int main (int argc, char *argv[]) { ClutterTimeline *timeline; ClutterActor *stage, *actor; GdkPixbuf *pixbuf; clutter_init (&argc, &argv); stage = clutter_stage_get_default (); pixbuf = gdk_pixbuf_new_from_file ("an-image.png", NULL); actor = clutter_texture_new_from_pixbuf (pixbuf); clutter_container_add_actor (CLUTTER_CONTAINER (stage), actor); clutter_actor_set_position (actor, 100, 100); timeline = clutter_timeline_new_for (360, 60); /* one degree per frame */ clutter_timeline_set_loop (timeline, TRUE); g_signal_connect (timeline, "new-frame", G_CALLBACK (on_new_frame), actor); clutter_actor_show_all (stage); clutter_timeline_start (timeline); clutter_main(); return 0; } Multiple timelines can be sequenced in order by means of the #ClutterScore. See the #ClutterScore documentation for more details on using this.
Behaviours With a large application containing many animations, the use of just timelines can become unwieldy and difficult to manage with much code duplication in the new-frame handlers that can require over complex code changes for minor animation modifications. To ease these problems the #ClutterAlpha and #ClutterBehaviour classes were created. #ClutterAlpha and #ClutterBehaviour attempt to generalise the new-frame function by defining common actions or behaviours that can be quickly modified, applied to multiple actors or mixed on a single actor. A ClutterAlpha is simply a 'function of time' (not a pixel alpha channel!). It is created by referencing a source timeline and an "easing mode" which produces a value between -1 and 2 depending on the progress of the timeline. Clutter provides various easing modes, as described by the #ClutterAnimationMode enumeration. It is also possible to register a new animation mode using clutter_alpha_register_func() or to provide a custom #ClutterAlphaFunc for a specific #ClutterAlpha instance. A Behaviour is created with a #ClutterAlpha and a set of limits for whatever the behaviour modifies in an actor. The current #ClutterAlpha value is then mapped to a value between these limits and this value set on any applied actors. With the #ClutterAlpha's underlying timeline playing the produced value will change and the behaviour will animate the actor. A #ClutterBehaviour is effectively 'driven' by a supplied #ClutterAlpha and when then applied to an actor it will modify a visual property or feature of the actor dependant on the Alpha's value. For example a path based behaviour applied to an actor will alter its position along the path dependant on the current alpha value over time. The actual motion will depend on the chosen "easing mode". Multiple behaviours can of course be applied to an actor as well as a single behaviour being applied to multiple actors. The separation of timelines, alphas and behaviours allows for a single timeline to drive many behaviours each potentially using different alpha functions. Behaviour parameters can also be changed on the fly.
Effects of alpha functions on a path
The actors position between the path's end points directly correlates to the #ClutterAlpha's current alpha value driving the behaviour. With the #ClutterAlpha's animation mode set to %CLUTTER_LINEAR the actor will follow the path at a constant velocity, but when changing to %CLUTTER_EASE_SINE_IN_OUT the actor initially accelerates before quickly decelerating.
The behaviours included in Clutter are #ClutterBehaviourDepth Changes the depth of actors #ClutterBehaviourEllipse Moves actors along an ellipsis #ClutterBehaviourOpacity Changes the opacity of actors #ClutterBehaviourPath Moves actors along a path #ClutterBehaviourRotate Rotates actors along an axis #ClutterBehaviourScale Changes the scaling factors of actors The following example demonstrates an ellipse behaviour in action. #include <clutter/clutter.h> int main (int argc, char *argv[]) { ClutterTimeline *timeline; ClutterBehaviour *behave; ClutterAlpha *alpha; ClutterActor *stage, *actor; GdkPixbuf *pixbuf; clutter_init (&argc, &argv); stage = clutter_stage_get_default (); pixbuf = gdk_pixbuf_new_from_file ("ohpowers.png", NULL); actor = clutter_texture_new_from_pixbuf (pixbuf); clutter_container_add_actor (CLUTTER_CONTAINER (stage), actor); timeline = clutter_timeline_new_for_duration (4000); /* milliseconds */ clutter_timeline_set_loop (timeline, TRUE); /* Set an alpha func to power the behaviour */ alpha = clutter_alpha_new_full (timeline, CLUTTER_EASE_SINE_IN_OUT); behave = clutter_behaviour_ellipse_new (alpha, 200, /* center x */ 200, /* center y */ 400, /* width */ 300, /* height */ CLUTTER_ROTATE_CW, /* direction */ 0.0, /* initial angle */ 360.0); /* final angle */ clutter_behaviour_apply (behave, actor); clutter_actor_show_all (stage); clutter_timeline_start (timeline); clutter_main(); /* clean up */ g_object_unref (behave); g_object_unref (timeline); return 0; } Behaviour parameters can be changed whilst a animation is running There can be many #ClutterAlpha's attached to a single timeline. There can be many behaviours for a #ClutterAlpha. There can be many behaviours applied to an actor. A #ClutterScore can be used to chain many behaviour together. Combining behaviours that effect the same actor properties (i.e two separate paths) will cause unexpected results. The values will not be merged in any way with only the last applied behaviour taking precedence. Tips for implementing a new behaviour can be found here.
Implicit Animations Using behaviours for simple animations of a single actor may be too complicated, in terms of memory management and bookkeeping of the object instances. For this reason, Clutter also provides a simple animation API for implicit animations using properties of an actor: clutter_actor_animate(). The clutter_actor_animate() family of functions will create and use an implicit #ClutterAnimation instance, which will then handle the animation of one or more #ClutterActor properties between a range of values. The following example demonstrates how to use the clutter_actor_animate() method to tween an actor between the current position and a new set of coordinates. The animation takes 200 milliseconds to complete and uses a linear speed. clutter_actor_animate (actor, CLUTTER_LINEAR, 200 "x", 200, "y", 200, NULL); The clutter_actor_animate() method returns a #ClutterAnimation instance that can be used to start, stop and modify the animation while it's running. The #ClutterAnimation::completed signal will be emitted when the animation has been completed. When the animation is complete it will be automatically unreferenced, and disposed if nothing else is holding a reference on it. Calling clutter_actor_animate() multiple times on an actor which is being animated will cause the animation to be updated with the new values. The following example demonstrates how to animate an actor inside the signal handler for a button press event. If the user presses the button on a new position while the animation is running, the animation will be restarted with the new final values updated. static gboolean on_button_press (ClutterActor *actor, ClutterButtonEvent *event, gpointer user_data) { clutter_actor_animate (actor, CLUTTER_EASE_SINE_OUT, 500, "x", event->x, "y", event->y, NULL); return TRUE; }
Conclusion Clutter provides a number of utility classes to aid animations and complex animations can be produced by combining the various features provided. Of course animations can becreated outside of the Clutter animation framework, as the framework is not expected to cover every kind of possible animation scenario. The animation functionality in Clutter is primarily suited to building animations with a set or finite running time - i.e transitions and the like. For animations involving variable input (such as touchscreen handling) physical simulations may be more suited.