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); clos->current_angle += CFX_ONE; if (clos->current_angle == clos->final_angle) return FALSE; return TRUE; } ... RotationClosure clos = { NULL, } clos.actor = an_actor; clos.final_angle = CLUTTER_FLOAT_TO_FIXED (360.0); clos.current_angle = 0; g_timeout_add (1000 / 360, /* fps to interval in milliseconds */ rotate_actor, &clos); 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 Clutter Timelines abstract a set period of time with a set rate at which to call a provided call back function. They essentially extend g_timeout like functionality further by; Having a set duration (in milliseconds) and a set 'frame rate'. Essentially the rate at which the callback is called. Passing current position information to the callback. Handling 'dropped frames' in guarenteeing the set duration and skipping over frames if Clutter cannot keep up with set rates. Query the number of milliseconds elapsed between current and previous callback. Allowing the timeline to be modified on the fly as well as being stoped, started, looped, rewound, reversed. Using the ClutterTimeoutPool to more efficiently schedule multiple timeout istances. A Timeline is created with; clutter_timeline_new (guint n_frames, guint fps); Taking a number of frames and a frames per second, or by; clutter_timeline_new_for_duration (guint 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 settings it "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 () , #clutter_timeline_skip () calls. By attaching a handler to the timelines "new-frame" signal a timeline can then be used to drive an animation by altering actors visual properties in this callback. The callback looks like; void on_new_frame (ClutterTimeline *timeline, gint frame_num, gpointer user_data) The new-frame signals 'frame_num' parameter is set to the timelines current frame number this is between 0 and the "num-frames" property. This value can be used to compute the state of a particular animation that is dependant on the current timeline position. The function #clutter_timeline_get_progress () can also be used to get a normalised value of the timelines current position. Timelines can also be played in reverse #clutter_timeline_set_direction() and a one-time delay set before they begin playing #clutter_timeline_set_delay (). When using a timeline to control a physical simulation using #clutter_timeline_get_delta() allows retrieving the number of frames and milliseconds since the previous callback to ensure the physics simulation 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, (gdouble)frame_num, clutter_actor_get_width (actor)/2, clutter_actor_get_height (actor)/2); } 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 (360, 60); /* num frames, fps */ g_object_set(timeline, "loop", TRUE, NULL); /* have it loop */ 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 unweldy 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 pixel alpha!). It is created by referencing a source timeline and a function which produces a value between 0 and %CLUTTER_ALPHA_MAX dependant on the timeline position. Various prebuilt alpha functions are included with Clutter these include %CLUTTER_ALPHA_RAMP_INC %CLUTTER_ALPHA_RAMP_DEC %CLUTTER_ALPHA_RAMP %CLUTTER_ALPHA_SINE %CLUTTER_ALPHA_SINE_INC %CLUTTER_ALPHA_SINE_DEC %CLUTTER_ALPHA_SINE_HALF %CLUTTER_ALPHA_SQUARE %CLUTTER_ALPHA_SMOOTHSTEP_INC %CLUTTER_ALPHA_SMOOTHSTEP_DEC %CLUTTER_ALPHA_EXP_INC %CLUTTER_ALPHA_EXP_DEC A Behaviour is created with a #ClutterAlpha and a set of limits for whatever the behaviour modifys actor wise. 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 #ClutterAlphaFunc - a #CLUTTER_ALPHA_RAMP_INC making it to move at constant speed along the path, a #CLUTTER_ALPHA_SINE making it alternate from one end of the path to the other with non constant speed. 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 function set to %CLUTTER_ALPHA_RAMP_INC the actor will follow the path at a constant velocity, but when changing to %CLUTTER_ALPHA_SINE_INC the actor initially accelerates before quickly decelerating.
The behaviours included with clutter are #ClutterBehaviourBspline #ClutterBehaviourDepth #ClutterBehaviourEllipse #ClutterBehaviourOpacity #ClutterBehaviourPath #ClutterBehaviourRotate #ClutterBehaviourScale 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 (100, 26); /* num frames, fps */ g_object_set(timeline, "loop", TRUE, NULL); /* have it loop */ /* Set an alpha func to power behaviour */ alpha = clutter_alpha_new_full (timeline, CLUTTER_ALPHA_SINE, NULL, NULL); behave = clutter_behaviour_ellipse_new (alpha, 200, /* center x */ 200, /* center y */ 400, /* width */ 300, /* height */ CLUTTER_ROTATE_CW, /* direction */ 0.0, /* angle begin */ 360.0); /* angle end */ clutter_behaviour_apply (behave, actor); clutter_actor_show_all (stage); clutter_timeline_start (timeline); clutter_main(); 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 togeather combining behaviours that effect the same actor properties (i.e two seperate paths) will cause unexpected results. The values will not be merged in any way with essentially a the last applied behaviour taking precedence. FIXME: actually move subclassing behaviours here?
Effects ClutterEffect's provide a simplified abstraction for firing simple transitions from code. ClutterEffects are created from ClutterEffectTemplate s which are an abstraction of a timeline and an alpha. An effect template can be created with: ClutterEffectTemplate *etemplate; etemplate = clutter_effect_template_new_for_duration ( 2000, CLUTTER_ALPHA_RAMP_INC); This will create an effect template lasting 2000 milliseconds (2 seconds) and use an alpha function of CLUTTER_ALPHA_RAMP_INC, there are other more advanced forms for creating effect templates from existing timelines, as well as attaching a callback to be called with user_data when the effecttemplate is destroyed. When we have an effect-template we can create a temporary behaviour animating an actor simply by issuing: clutter_actor_move (etemplate, actor, 23, 42, NULL, NULL); and the actor will move to the coordintes 23, 42 in 2 seconds, if we at the same time issued: clutter_actor_fade (etemplate, actor, 0x0, NULL, NULL); The actor would fade out at the same time. Clutter effects return a timeline, you can stop an effect from immediatly happening by calling clutter_timeline_stop () on the returned timeline. This returned timeline can also be used to then use effects in the ClutterScore etc.
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 be created outside of Clutter Utilities, they are 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.