cookbook: Added a recipe for reusing a complex animation
This recipe explains how to "reuse" the same animation definition for different actors, by creating a new instance of a "rig" (empty container) and animation for the rig each time the animation is required. An actor is then re-parented to the rig and animated using it, rather than being animated directly. JSON is used to define the rig + animator, to make creating new instances of them simpler. The recipe also discusses various caveats around using this approach, rather than directly animating an actor.
This commit is contained in:
parent
6548bee56c
commit
e8e360eaa7
@ -58,6 +58,7 @@ VIDEO_FILES = \
|
|||||||
videos/events-mouse-scroll.ogv \
|
videos/events-mouse-scroll.ogv \
|
||||||
videos/textures-crossfade-two-textures.ogv \
|
videos/textures-crossfade-two-textures.ogv \
|
||||||
videos/animations-complex.ogv \
|
videos/animations-complex.ogv \
|
||||||
|
videos/animations-reuse.ogv \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
EXTRA_DIST = \
|
EXTRA_DIST = \
|
||||||
|
@ -1438,4 +1438,382 @@ clutter_actor_set_z_rotation_from_gravity (actor,
|
|||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section id="animations-reuse">
|
||||||
|
<title>Reusing a complex animation on different actors</title>
|
||||||
|
|
||||||
|
<section id="animations-reuse-problem">
|
||||||
|
<title>Problem</title>
|
||||||
|
|
||||||
|
<para>You want to apply the same complex animation to several
|
||||||
|
different actors.</para>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="animations-reuse-solution">
|
||||||
|
<title>Solution</title>
|
||||||
|
|
||||||
|
<para>Instead of animating each actor separately, create a
|
||||||
|
<emphasis>rig</emphasis>: an empty container with an associated
|
||||||
|
animation, which will be animated in lieu of
|
||||||
|
animating the actor directly. Do this as follows:</para>
|
||||||
|
|
||||||
|
<orderedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>Initialise the stage and actors, including those
|
||||||
|
to be animated.</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>Define a <type>ClutterContainer</type> and a
|
||||||
|
<type>ClutterAnimator</type> animation to animate it.</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>When you need to animate an actor:</para>
|
||||||
|
|
||||||
|
<orderedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>Create an instance of the rig and its animator.</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>Reparent the actor to the rig.</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>Run the rig's animation.</para>
|
||||||
|
</listitem>
|
||||||
|
</orderedlist>
|
||||||
|
</listitem>
|
||||||
|
</orderedlist>
|
||||||
|
|
||||||
|
<para>For this solution, we're using
|
||||||
|
<ulink url="http://json.org/">JSON</ulink> to define the
|
||||||
|
animation and the user interface elements. For more
|
||||||
|
details about this approach, see
|
||||||
|
<link linkend="script-introduction">the chapter
|
||||||
|
on <type>ClutterScript</type></link>.</para>
|
||||||
|
|
||||||
|
<para>Here's an extract of the JSON definition for the stage and
|
||||||
|
one of five rectangles placed at its left edge (the full definition
|
||||||
|
is in <link linkend="animations-reuse-example-1">the
|
||||||
|
appendix</link>):</para>
|
||||||
|
|
||||||
|
<informalexample>
|
||||||
|
<programlisting>
|
||||||
|
<![CDATA[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type" : "ClutterStage",
|
||||||
|
"id" : "stage",
|
||||||
|
|
||||||
|
... stage properties, signal handlers etc. ...
|
||||||
|
|
||||||
|
"children" : [
|
||||||
|
{
|
||||||
|
"type" : "ClutterRectangle",
|
||||||
|
"id" : "rect1",
|
||||||
|
"color" : "white",
|
||||||
|
"width" : 50,
|
||||||
|
"height" : 50,
|
||||||
|
"y" : 50,
|
||||||
|
"reactive" : true,
|
||||||
|
"signals" : [
|
||||||
|
{ "name" : "button-press-event", "handler" : "foo_button_pressed_cb" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
... more children defined here ...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]]>
|
||||||
|
</programlisting>
|
||||||
|
</informalexample>
|
||||||
|
|
||||||
|
<para>The key point to note is how a signal handler is defined
|
||||||
|
for the <code>button-press-event</code>, so that the
|
||||||
|
<function>foo_button_pressed_cb()</function> function will trigger
|
||||||
|
the animation when a (mouse) button is pressed on each rectangle.</para>
|
||||||
|
|
||||||
|
<para>The second JSON definition includes the rig
|
||||||
|
(an empty <type>ClutterGroup</type>) and a
|
||||||
|
<type>ClutterAnimator</type> to animate it. The animation moves the
|
||||||
|
container across the stage and scales it to twice its original
|
||||||
|
size. (This is the <link linkend="animations-reuse-example-2">same
|
||||||
|
code as in the appendix</link>):</para>
|
||||||
|
|
||||||
|
<informalexample>
|
||||||
|
<programlisting>
|
||||||
|
<xi:include href="examples/animations-reuse-animation.json" parse="text">
|
||||||
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
||||||
|
</xi:include>
|
||||||
|
</programlisting>
|
||||||
|
</informalexample>
|
||||||
|
|
||||||
|
<para>The remaining parts of the application code load
|
||||||
|
the user interface definition, setting up the stage and rectangles;
|
||||||
|
and define the callback. The full code is
|
||||||
|
<link linkend="animations-reuse-example-3">in the appendix</link>,
|
||||||
|
but below is the most important part, the callback function:</para>
|
||||||
|
|
||||||
|
<informalexample>
|
||||||
|
<programlisting>
|
||||||
|
<![CDATA[
|
||||||
|
gboolean
|
||||||
|
foo_button_pressed_cb (ClutterActor *actor,
|
||||||
|
ClutterEvent *event,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
ClutterScript *ui = CLUTTER_SCRIPT (user_data);
|
||||||
|
ClutterStage *stage = CLUTTER_STAGE (clutter_script_get_object (ui, "stage"));
|
||||||
|
|
||||||
|
ClutterScript *script;
|
||||||
|
ClutterActor *rig;
|
||||||
|
ClutterAnimator *animator;
|
||||||
|
|
||||||
|
/* load the rig and its animator from a JSON file */
|
||||||
|
script = clutter_script_new ();
|
||||||
|
|
||||||
|
/* use a function defined statically in this source file to load the JSON */
|
||||||
|
load_script_from_file (script, ANIMATION_FILE);
|
||||||
|
|
||||||
|
clutter_script_get_objects (script,
|
||||||
|
"rig", &rig,
|
||||||
|
"animator", &animator,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
/* remove the button press handler from the rectangle */
|
||||||
|
g_signal_handlers_disconnect_matched (actor,
|
||||||
|
G_SIGNAL_MATCH_FUNC,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
foo_button_pressed_cb,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
/* add a callback to clean up the script when the rig is destroyed */
|
||||||
|
g_object_set_data_full (G_OBJECT (rig), "script", script, g_object_unref);
|
||||||
|
|
||||||
|
/* add the rig to the stage */
|
||||||
|
clutter_container_add_actor (CLUTTER_CONTAINER (stage), rig);
|
||||||
|
|
||||||
|
/* place the rig at the same coordinates on the stage as the rectangle */
|
||||||
|
clutter_actor_set_position (rig,
|
||||||
|
clutter_actor_get_x (actor),
|
||||||
|
clutter_actor_get_y (actor));
|
||||||
|
|
||||||
|
/* put the rectangle into the top-left corner of the rig */
|
||||||
|
clutter_actor_reparent (actor, rig);
|
||||||
|
|
||||||
|
clutter_actor_set_position (actor, 0, 0);
|
||||||
|
|
||||||
|
/* animate the rig */
|
||||||
|
clutter_animator_start (animator);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</programlisting>
|
||||||
|
</informalexample>
|
||||||
|
|
||||||
|
<para>The code creates a new rig and associated animation
|
||||||
|
at the point when the rectangle is clicked. It then positions the
|
||||||
|
rig at the same coordinates as the rectangle, reparents
|
||||||
|
the rectangle to the rig, and starts the rig's animation.</para>
|
||||||
|
|
||||||
|
<note>
|
||||||
|
<para>The signal handler has to be declared non-static and
|
||||||
|
you must use <code>-export-dynamic</code> as an option to the
|
||||||
|
compiler, otherwise the function isn't visible to
|
||||||
|
<type>ClutterScript</type> (as outlined
|
||||||
|
<link linkend="script-signals">in this recipe</link>).</para>
|
||||||
|
</note>
|
||||||
|
|
||||||
|
<para>This is what the animation looks like:</para>
|
||||||
|
|
||||||
|
<inlinemediaobject>
|
||||||
|
<videoobject>
|
||||||
|
<videodata fileref="videos/animations-reuse.ogv"/>
|
||||||
|
</videoobject>
|
||||||
|
<alt>
|
||||||
|
<para>Video of a simple reusable animation</para>
|
||||||
|
</alt>
|
||||||
|
</inlinemediaobject>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="animations-reuse-discussion">
|
||||||
|
<title>Discussion</title>
|
||||||
|
|
||||||
|
<para>The above solution reparents an actor to be animated
|
||||||
|
into a rig (an empty placeholder). The rig is a container
|
||||||
|
which acts as a temporary parent for the actor we
|
||||||
|
<emphasis>really</emphasis> want to animate. By animating the rig,
|
||||||
|
it appears as though the actor inside it is being animated (but
|
||||||
|
<link linkend="animations-reuse-discussion-rig-not-actor">see
|
||||||
|
these caveats</link>). This means the same animation can be
|
||||||
|
easily applied to different actors: create an
|
||||||
|
instance of the rig, reparent an actor to it, then
|
||||||
|
run the rig's animation. This is simpler than creating
|
||||||
|
a separate animation for each actor individually, or
|
||||||
|
reusing a single <type>ClutterAnimator</type> on different
|
||||||
|
actors (see
|
||||||
|
<link linkend="animations-reuse-discussion-one-or-many">this
|
||||||
|
section</link>).</para>
|
||||||
|
|
||||||
|
<para>Using JSON enhances the animation's reusability (it's even
|
||||||
|
potentially reusable in another application), makes the code
|
||||||
|
simpler (an animation can be loaded directly from the script),
|
||||||
|
and makes refactoring easier (the animation can be modified
|
||||||
|
without recompiling the application code). However, it also puts
|
||||||
|
some minor limitations on the animation's reusability; namely, you
|
||||||
|
can only set absolute property values in a JSON animation
|
||||||
|
definition. This makes JSON less useful in cases where
|
||||||
|
you need to animate properties relative to their starting
|
||||||
|
values: for example, "move 50 pixels along the x axis" or
|
||||||
|
"rotate by 10 degrees more on the z axis". (This type of animation
|
||||||
|
is probably less portable anyway.) In such cases, the programmable
|
||||||
|
API may be a better option: see the <type>ClutterAnimator</type>
|
||||||
|
documentation for examples.</para>
|
||||||
|
|
||||||
|
<section id="animations-reuse-discussion-one-or-many">
|
||||||
|
<title>One animation vs. many</title>
|
||||||
|
|
||||||
|
<para>In the sample code, a new instance of the rig and its
|
||||||
|
animation are created for each actor. One side effect of this
|
||||||
|
is that all of the actors can animate simultaneously with the
|
||||||
|
"same" animation. If you don't want this behaviour, but still
|
||||||
|
want to use a rig approach, you could create a single instance
|
||||||
|
of the rig and its animation. Then, you could reparent each actor
|
||||||
|
to it in turn.</para>
|
||||||
|
|
||||||
|
<para>To ensure that the rig only animates one actor (or group
|
||||||
|
of actors) at a time, you could track whether the rig is
|
||||||
|
currently animating (e.g. by examining the animation's
|
||||||
|
timeline with <function>clutter_animator_get_timeline()</function>).
|
||||||
|
Then, if the animation is running, prevent any other actor
|
||||||
|
from being reparented to the rig.</para>
|
||||||
|
|
||||||
|
<para>Note that you would also need to "reset" the rig each time the
|
||||||
|
animation completed (move it back to the right start values for
|
||||||
|
its properties), ready to animate the next actor.</para>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="animations-reuse-discussion-rig-not-actor">
|
||||||
|
<title>Caveats about animating a rig instead of an actor</title>
|
||||||
|
|
||||||
|
<para>There are a few issues to be aware of in cases
|
||||||
|
where you animate a rig with contained actors, rather than
|
||||||
|
animating the actor directly:</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>Animating a rig doesn't <emphasis>always</emphasis>
|
||||||
|
produce the same visual effect as animating an actor directly.
|
||||||
|
For example, compare the following cases:</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>You rotate an actor by 180 degrees in the
|
||||||
|
<code>y</code> axis, then by 90 degrees in the
|
||||||
|
<code>z</code> axis. The actor appears to rotate in
|
||||||
|
a <emphasis>clockwise</emphasis> direction.</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>You rotate the parent container of an actor
|
||||||
|
by 180 degrees in the <code>y</code> axis; then rotate
|
||||||
|
the actor by 90 degrees in the <code>z</code> axis.
|
||||||
|
The actor appears to rotate in an
|
||||||
|
<emphasis>anti-clockwise</emphasis> direction. By
|
||||||
|
rotating the container, the "back" of the
|
||||||
|
actor faces the view point, so the actor's movement
|
||||||
|
appears reversed. See
|
||||||
|
<link linkend="animations-rotating-discussion-direction">this
|
||||||
|
recipe</link> for more details.</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>There may be other situations where you get similar
|
||||||
|
discrepancies.</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>Animating a rig doesn't change an actor's properties,
|
||||||
|
but animating the actor does.</para>
|
||||||
|
|
||||||
|
<para>When you animate a container rather than the actor
|
||||||
|
directly, the reported properties of the actor may not
|
||||||
|
reflect its visual appearance. For example, if you apply
|
||||||
|
a scale animation to a container, the final scale of
|
||||||
|
actors inside it (as returned by
|
||||||
|
<function>clutter_actor_get_scale()</function>) will not
|
||||||
|
reflect the scaling applied to their container; whereas
|
||||||
|
directly animating the actors would cause their scale
|
||||||
|
properties to change.</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>Reparenting an actor to a rig can cause the actor
|
||||||
|
to "jump" to the rig's position, unless you align the
|
||||||
|
actor to the rig first.</para>
|
||||||
|
|
||||||
|
<para>Note that in the sample code, the position of the actor
|
||||||
|
(<code>x</code>, <code>y</code> coordinates) is copied to
|
||||||
|
the rig before the reparenting happens. The actor is then
|
||||||
|
reparented to the rig, and positioned in the rig's
|
||||||
|
top-left corner. So the actor appears to be in the same
|
||||||
|
position, but is now actually inside a rig at the actor's old
|
||||||
|
position.</para>
|
||||||
|
|
||||||
|
<para>Why bother to do this? Because the rig has a default
|
||||||
|
position of <code>0,0</code> (top-left of <emphasis>its</emphasis>
|
||||||
|
container, the stage). If you reparent the actor to the rig,
|
||||||
|
without first copying the actor's position to the rig, the
|
||||||
|
actor appears to "jump" to the rig's position.</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="animations-reuse-examples">
|
||||||
|
<title>Full example</title>
|
||||||
|
|
||||||
|
<note>
|
||||||
|
<para>The three separate code examples in this section
|
||||||
|
constitute a single application which implements the above
|
||||||
|
solution.</para>
|
||||||
|
</note>
|
||||||
|
|
||||||
|
<example id="animations-reuse-example-1">
|
||||||
|
<title><type>ClutterScript</type> JSON defining several
|
||||||
|
rectangles with signal handlers</title>
|
||||||
|
<programlisting>
|
||||||
|
<xi:include href="examples/animations-reuse-ui.json" parse="text">
|
||||||
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
||||||
|
</xi:include>
|
||||||
|
</programlisting>
|
||||||
|
</example>
|
||||||
|
|
||||||
|
<example id="animations-reuse-example-2">
|
||||||
|
<title><type>ClutterScript</type> JSON describing a "rig"
|
||||||
|
and a <type>ClutterAnimator</type> animation</title>
|
||||||
|
<programlisting>
|
||||||
|
<xi:include href="examples/animations-reuse-animation.json" parse="text">
|
||||||
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
||||||
|
</xi:include>
|
||||||
|
</programlisting>
|
||||||
|
</example>
|
||||||
|
|
||||||
|
<example id="animations-reuse-example-3">
|
||||||
|
<title>Loading <type>ClutterScript</type> from JSON files
|
||||||
|
in response to events in a user interface</title>
|
||||||
|
<programlisting>
|
||||||
|
<xi:include href="examples/animations-reuse.c" parse="text">
|
||||||
|
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
|
||||||
|
</xi:include>
|
||||||
|
</programlisting>
|
||||||
|
</example>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
</chapter>
|
</chapter>
|
||||||
|
BIN
doc/cookbook/videos/animations-reuse.ogv
Normal file
BIN
doc/cookbook/videos/animations-reuse.ogv
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user