The Drawable Lifecycle
Drawable
objects typically have two
distinct phases in their lifecycle,
scripting and rendering. Scripting sets up the object
and all the actions it will take during a scene. It is like writing
the script for the animation. Rendering animates the scene, producing
a final video of the scene. It is like filming a scene after
the script has been written.
The Scripting Phase
In the scripting phase, the
full set of movements and transformations the object goes through
are set up, using methods like move_to()
,
fade_to()
, and ramp_attr_to()
.
No actual animation happens during the scripting phase. We are
simply writing the script that the animation will follow.
The scripting
phase typically ends with the Drawable
being placed in
a Scene
.
For a detailed example of how methods like move_to()
are used, during the scripting phase, please see the
Hello, World example.
Next-Action Time During Scripting
Every object in a scene has a next-action time. The next
action time is when the action created by the next call to
a scripting-time method like move_to()
will start. In the examples above, the next-action time
was always the time that the last action ended. But that is not
always what we want. Sometimes we want an action to begin
before an earlier action ends.
To support this, methods like move_to()
take an optional parameter update_time
that has a default value of
True
. If this parameter is True
then the object’s next-action is
updated to a new value by adding the value of the
duration
parameter.
Setting update_time=False
prevents this update from happening, leaving
the next-action time
unchanged. This enables the object to take several actions
simultaneously. The difference can be seen in the following example:
# Option 1: Move and then rotate.
d.move_to(200, 200, 1.0)
d.rotate_to_degrees(360, 1.0)
# Option 2: Rotate while moving.
d.move_to(200, 200, 1.0, update_time=False)
d.rotate_to_degrees(360, 1.0)
In option 2, time was not updated by the call to
move_to()
, so the rotation
will start at the same time the move started.
It is also possible to do more complex combinations
of changes by adding calls to wait()
,
which updates the next-action time without taking any action.
For example
# Move to a new location over the next 3 seconds.
d.move_to(600, 100, 3.0, update_time=False)
# While moving, let the clock advance by 1 second.
d.wait(1.0)
# Now do a complete rotation while moving.
d.rotate_to_degrees(360, 1.0)
# And in the final second of the move, fade out.
d.fade_to(0.0, 1.0)
The net result of these steps is that the object will move to a new location over
the course of three seconds, but one second into moving it will
start to rotate, which it will do for one second, making a full
rotation. At this point it will be 2/3 of the way to it’s destination
since two of the three seconds of movement have elapsed due
to the calls to wait()
and
rotate_to_degrees()
. Finally, during the last
second of the move, it will fade out, reaching complete transparency
at exactly the moment it arrives at it’s destination.
If d
had been initialized as a MarkerUpArrow
then the resulting scene would look like this gif:
The arrow starts moving across the screen, then one second later starts rotating, then one second later stops rotating and starts to fade out.
Motion Linking
In addition to moving or altering Drawable
objects, we can also link the behavior of one object to that of another
in a manner that applies across all time. We call this motion linking.
In order to link the motion of a Drawable
d1
to that of a Drawable
d0
, we
assign the value of a time-varying property of d0
to a time-varying
property of d1
. For example,
d0 = MarkerPlus(100, 100)
d1 = MarkerO(100, 300)
d2 = MarkerO(0, 0)
# Script all of the motion d0 will have during
# the scene.
d0.move_to(400, 200, 1.0)
d0.move_to(200, 200, 1.0)
# Link the motion of d1 in the x direction to
# that of d0. What this means is that at all
# times during the scene, the x coordinate of
# d1 should be the same as the x coordinate of
# d0 as defined by the scripting above.
d1.x = d0.x
# Link the motion of d2 to that of d0 such that
# d2 is always 50 pixels below d0.
d2.x = d0.x
d2.y = d0.y + 50
We can also use expressions when we link properties. For example,
we can position one Drawable
relative
to another, as in the following:
d2 = MarkerO(0, 0)
# Link the motion of d2 to that of d0 such that
# d2 is always 50 pixels below d0.
d2.x = d0.x
d2.y = d0.y + 50
For runnable examples of motion linking in action, see Motion Linking.
The Rendering Phase
In the rendering phase, a Drawable
is asked to render
itself at one or more times, via calls to its draw()
method. These calls are typically made from a renderer that generates
either an interactive view of the scene or saves the scene to a video
file such as an MP4 or GIF. Examples include
gewel.record.Mp4Recorder
, gewel.record.GifRecorder
,
gewel.player.Player
and the gewel.draw.Scene.__repr__()
method that renders scenes in a widget in an IPython notebook.