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:

Animation of an arrow moving, flipping, then fading.

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.