Sample Code
Below you will find a number of examples of how you can use gewel to create simple animations. Each is self-contained and designed to illustrate just one of two specific concepts. Each code sample is followed by a gif image that illustrates the result of running it.
You can combine the concepts illustrated here to create all kinds of different animations of your own.
Each of the code samples is mostly made up of scripting phase code. (See The Scripting Phase.) We can think of scripting phase code as code that writes a script describing what various objects in the animation should do at various times.
The lines in the scripting phase that are most critical to the feature the code sample is attempting to illustrate are highlighted.
The final two lines of each code sample are rendering phase code.
(See The Rendering Phase.)
In order to keep the samples simple and portable, and to produce the
animations shown in this document, we simply write each
Scene
we produce out to a .gif file.
In many cases, you will want to use a Mp4Recorder
instead of a GifRecorder
in the rendering phase.
When you are developing and interactively debugging, you are likely to want to
use a Player
. When you use a
Player
, you get interactive controls and you
don’t have to wait for the entire scene to render before you start playing it.
If you would like to switch to using a player, replace the rendering phase lines:
recorder = GifRecorder('solar_system.gif')
recorder.record(scene)
with
from gewel.player import Player
player = Player(scene)
player.mainloop()
in any of the code samples below. Instead of rendering and saving the scene to a .gif file, it will pop up an interactive player that looks like this:
Note
Each of the code samples can be copied by moving to the upper right corner of the code sample and clicking on the copy to clipboard icon that appears.
Examples
Fading
Using fade_to()
Here is an example of how
fade_to()
works. We will create aMarkerDot
, which is a class derived fromDrawable
and give it an initial alpha of 0.0, meaning it is entirely transparent. We will then fade it in to full opacity (alpha = 1.0) and then fade it back to 0.0 so it is fully transparent again. We will then wrap it in aScene
and save that scene to a .gif file.1from gewel.color import ORANGE 2from gewel.draw import MarkerDot, Background, Scene 3from gewel.record import GifRecorder 4 5# Scripting phase: 6 7background = Background() 8 9drawable = MarkerDot(x=320, y=240, width=240, color=ORANGE, alpha=0.0) 10 11drawable.fade_to(1.0, duration=2.0) 12drawable.fade_to(0.0, duration=2.0) 13 14scene = Scene([background, drawable]) 15 16# Rendering phase: 17 18recorder = GifRecorder('fade_to.gif') 19recorder.record(scene)That code produces the following gif as output:
Moving and Rotating
Using move_to()
Here is an example of how move_to()
works. We will
create a XYDrawable
and move it, then wrap it in a
Scene
and save that scene to a .gif file.
1from gewel.color import ORANGE
2from gewel.draw import MarkerUpArrow, Background, Scene
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8
9drawable = MarkerUpArrow(x=32, y=240, height=64, color=ORANGE, line_width=3)
10
11drawable.move_to(608, 240, duration=2.0)
12
13scene = Scene([background, drawable])
14
15# Rendering phase:
16
17recorder = GifRecorder('move_to.gif')
18recorder.record(scene)
That code produces the following gif as output:
Using rotate_to()
Here is an example of how rotate_to()
works. We will
create a XYDrawable
and rotate it, then wrap it in a
Scene
and save that scene to a .gif file.
1import numpy as np
2
3from gewel.color import ORANGE
4from gewel.draw import MarkerUpArrow, Background, Scene
5from gewel.record import GifRecorder
6
7# Scripting phase:
8
9background = Background()
10
11drawable = MarkerUpArrow(x=320, y=240, height=64, color=ORANGE, line_width=3)
12
13drawable.rotate_to(2 * np.pi, duration=2.0)
14
15scene = Scene([background, drawable])
16
17# Rendering phase:
18
19recorder = GifRecorder('rotate_to.gif')
20recorder.record(scene)
That code produces the following gif as output:
Using rotate_to_degrees()
In Using rotate_to() we used rotate_to()
to rotate a XYDrawable
. If you prefer
degrees to radians, you can use rotate_to_degrees()
instead of rotate_to()
. All you have to
do is change line 13 in the code above to line 11 in the code below
and remove the now unneeded import numpy as np
at the top to produce the following code:
1from gewel.color import ORANGE
2from gewel.draw import MarkerUpArrow, Background, Scene
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8
9drawable = MarkerUpArrow(x=320, y=240, height=64, color=ORANGE, line_width=3)
10
11drawable.rotate_to_degrees(360, duration=2.0)
12
13scene = Scene([background, drawable])
14
15# Rendering phase:
16
17recorder = GifRecorder('rotate_to_degrees.gif')
18recorder.record(scene)
That code produces the exact same gif as the one in Using rotate_to().
Markers
In the examples above, we created objects of the classes
MarkerDot
and
MarkerUpArrow
, but we did not
properly introduce the classes. Both are derived from the
abstract base class MarkerDot
,
which has other derived classes such as MarkerX
,
MarkerO
, and MarkerPlus
.
These are all illustrated in the following code sample:
1from gewel.color import BLUE, BLACK, ORANGE, PURPLE, RED
2from gewel.draw import Background, Scene, MarkerX, MarkerO, \
3 MarkerPlus, MarkerDot, MarkerUpArrow
4from gewel.record import GifRecorder
5
6# Scripting phase:
7
8background = Background()
9
10mp = MarkerPlus(80, 120, line_width=5, width=100, color=BLUE)
11mo = MarkerO(200, 360, line_width=5, width=100, color=ORANGE)
12mx = MarkerX(320, 120, line_width=5, width=100, color=BLACK)
13md = MarkerDot(440, 360, line_width=5, width=100, color=RED)
14ma = MarkerUpArrow(560, 120, line_width=5, width=50, height=100, color=PURPLE)
15
16mp.rotate_to_degrees(360, duration=3.0)
17ma.rotate_to_degrees(-360, duration=3.0)
18
19mo.move_to(200, 120, duration=1.5)
20md.move_to(440, 120, duration=1.5)
21
22mo.move_to(200, 360, duration=1.5)
23md.move_to(440, 360, duration=1.5)
24
25scene = Scene([background, mx, mp, ma, mo, md])
26
27# Rendering phase:
28
29recorder = GifRecorder('markers.gif')
30recorder.record(scene)
That code produces the following gif as output:
PNGs
Markers are good enough for sample animations that are just
trying to teach someone about APIs, but they are rarely
sufficient for real animations. Often you will want to import
.png files created elsewhere into your animations.
PngDrawable
is the class that
lets you do this.
Here is an example of how to construct a PngDrawable
and rotate it 360 degrees, then wrap it in a
Scene
and save that scene to a .gif file. Note that if
you want to run this code sample you will also need to download
donut.png
and put it in the same directory
you run the sample code from.
1import numpy as np
2
3from gewel.draw import PngDrawable, Background, Scene
4from gewel.record import GifRecorder
5
6# Scripting phase:
7
8background = Background()
9
10drawable = PngDrawable('donut.png', x=320, y=240)
11
12drawable.rotate_to(2 * np.pi, duration=2.0)
13
14scene = Scene([background, drawable])
15
16# Rendering phase:
17
18recorder = GifRecorder('png_drawable.gif')
19recorder.record(scene)
That code produces the following gif as output:
Non-Linear Movement and Scaffolding
move_to()
is a great way to create
movement in your animations. But it is limited to linear movement. There are
two more advanced methods we can use:
quadratic_move_to()
, which follows
a quadratic path, and bezier_move_to()
,
which follows a cubic path.
When working with these higher degree polynomial paths, it is sometimes
useful to have a visual guide indicating the desired path. We call these
visual guides scaffolding. All of the methods that move objects have
and optional scaffolding
argument whose default value is False.
If it is set to true, scaffolding is generated and returned as a
Drawable
.
We illustrate both quadratic and cubic paths, along with the use of scaffolding, below.
Using quadratic_move_to()
Here is an example of how quadratic_move_to()
works. We will
create a XYDrawable
and move it, then wrap it in a
Scene
and save that scene to a .gif file. In order
to make it more clear what is happening, we will also use the scaffolding
feature to show where the current position, control point, and final
position are, as well as the quadratic path we expect the object to follow.
1from gewel.color import ORANGE
2from gewel.draw import MarkerUpArrow, Background, Scene
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8
9drawable = MarkerUpArrow(x=32, y=440, height=64, color=ORANGE, line_width=3)
10
11scaffold = drawable.quadratic_move_to(320, 40, 608, 440, duration=2.0, scaffold=True)
12
13scene = Scene([background, drawable, scaffold])
14
15# Rendering phase:
16
17recorder = GifRecorder('quad_move_to.gif')
18recorder.record(scene)
That code produces the following gif as output:
Notice that the current and final positions as well as the control point and the path of motion are rendered as scaffolding.
Using bezier_move_to()
Here is an example of how bezier_move_to()
works. We will
create a XYDrawable
and move it, then wrap it in a
Scene
and save that scene to a .gif file. In order
to make it more clear what is happening, we will also use the scaffolding
feature to show where the current position, two control points, and final
position are, as well as the path we expect the object to follow.
1from gewel.color import ORANGE
2from gewel.draw import MarkerUpArrow, Background, Scene
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8
9drawable = MarkerUpArrow(x=32, y=440, height=64, color=ORANGE, line_width=3)
10
11scaffold = drawable.bezier_move_to(
12 x1=320, y1=40, x2=580, y2=100, x3=608, y3=440,
13 duration=2.0, scaffold=True
14)
15
16scene = Scene([background, drawable, scaffold])
17
18# Rendering phase:
19
20recorder = GifRecorder('bezier_move_to.gif')
21recorder.record(scene)
That code produces the following gif as output:
The path that the object follows is defined by its initial location \((x_0, y_0)\), two control points \((x_1, y_1)\) and \((x_2, y_2)\), and it’s final location \((x_3, y_3)\). The initial location is the location of the object before the method is called. The control points and final location are specified as parameters.
Notice that the current and final positions as well as the control points and the path of motion are rendered as scaffolding.
Update Time
Scripting-time methods like move_to()
,
rotate_to()
, and
fade_to()
take
an optional parameter update_time
that has a default value of
True
. If this parameter is True
then the object’s notion
of what time it should start the next action (known as the next-action time)
is increased by the duration
parameter. Setting update_time=False
causes this
not to happen. This enables the object to take several actions
simultaneously.
For example, suppose we want an object to move, but also rotate at the same time.
We can call move_to()
with
update_time = False
, then call rotate_to()
.
Since the next-action time was not updated during the move, the rotation starts when
the move started.
In addition to the update_time
parameter, we can use the
wait()
method to update the time but not
create any additional motion.
The following example demonstrates several how we can create a variety of effects
by combining these techniques. Note that we also add a TimeClock
to the scene to keep track of time. This class is normally added for visual
debugging during scene development and removed before final rendering. Watch the clock
and the motion and that should help you reason about what the code is doing and
why rotation and motion happen when they do.
1from gewel.color import ORANGE
2from gewel.draw import MarkerUpArrow, Background, Scene, TimeClock
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8clock = TimeClock(x=20, y=30, font_size=20, z=10.0)
9
10drawable = MarkerUpArrow(x=32, y=240, height=64, color=ORANGE, line_width=3)
11
12# Rotate while moving. We do this by not updating the
13# drawable's next-action time during the move. As a
14# result, the next action, which is the rotation, starts
15# at the same time the move started. Since we did not
16# add update_time=False to the rotation, the next-action time
17# will be set to when the rotation ends, two seconds
18# into the scene.
19drawable.move_to(608, 240, duration=2.0, update_time=False)
20drawable.rotate_to_degrees(360, duration=2.0)
21
22# The next-action time is now two seconds into the scene.
23# Let's wait two seconds doing nothing. That will leave the
24# next-action time set to four seconds into the scene.
25drawable.wait(duration=2.0)
26
27# Now let's move back, again without updating the next-action
28# time.
29drawable.move_to(32, 240, duration=2.0, update_time=False)
30
31# But this time we will wait for one second while we are moving,
32# so the next-action time will be one second after the move
33# started, which will be five seconds into the scene.
34drawable.wait(duration=1.0)
35
36# And them spin back during the final second of motion.
37# This should begin at five seconds into the scene, when
38# we have moved halfway back to the starting position. It
39# should end six seconds into the scene when we are back
40# at the initial position.
41drawable.rotate_to_degrees(0, duration=1.0)
42
43# Now wait for another two seconds. This takes us to the
44# end of the scene at the eight second mark.
45drawable.wait(duration=2.0)
46
47scene = Scene([background, drawable, clock])
48
49# Rendering phase:
50
51recorder = GifRecorder('update_time.gif')
52recorder.record(scene)
That code produces the following gif as output:
Motion Linking
In the examples above, we used scripting methods such as
bezier_move_to()
, with or
without update_time=True
, and
wait()
to script
animation scenes.
In addition to these methods, there is another approach we can use,
which is called motion linking. Motion linking connects one or
more properties of a Drawable
to that
of another. It can be used to produce effects like one object following
another.
Motion linking is typically done in two steps. First, we create one or
more Drawable
objects and script them as
in the examples above. Second, we create one or more additional
Drawable
objects and assign one or more
of their properties (typically things like their x and y location)
to values from the first set of objects.
The key thing to remember is that the properties we are working with are
time-varying values, meaning that they are not fixed, but rather vary over
time. For example, once we have called the
bezier_move_to()
method on an object,
both it’s x and y coordinates are time-varying. That’s what produces
the motion. The value at time t1 is different than the value at t0.
When we do motion linking, we step outside the timeline that
bezier_move_to()
,
wait_for()
, and all the other
methods we discussed above operate on, and assign a property of one object
to match a property of another at all times. This is why we need to fully
script the first object before we do motion linking.
Note that we can also create time-varying values as expressions of
time-varying values and constants. For example, if x
is a time-varying
value then v = 2 * x + 10
is also. The value of v
at any given
time is twice that of x
at the time plus 10.
Tracking With Motion Linking
Here is a simple example where we have one object track the x coordinate of another and the y coordinate track an expression of the y coordinate of the other.
1from gewel.color import GREEN, ORANGE
2from gewel.draw import MarkerX, MarkerPlus, Background, Scene
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8
9# Script d0
10d0 = MarkerX(x=32, y=100, color=ORANGE, line_width=3)
11d0.move_to(608, 200, duration=2.0)
12d0.move_to(608, 100, duration=1.0)
13d0.move_to(32, 100, duration=1.5)
14
15# Link the motion of d1 to that of d0.
16# It will follow d0 but 100 pixels below.
17d1 = MarkerPlus(x=0, y=0, color=GREEN, line_width=3)
18d1.x = d0.x
19d1.y = d0.y + 100
20
21# Note that we can produce the same results by
22# setting x and y directly in the constructor.
23d1 = MarkerPlus(x=d0.x, y=d0.y + 100, color=GREEN, line_width=3)
24
25# Assemble the scene.
26scene = Scene([background, d0, d1])
27
28# Rendering phase:
29
30recorder = GifRecorder('motion_track_x.gif')
31recorder.record(scene)
That code produces the following gif as output:
Motion Linking with track()
In addition to the tracking methods shown above, there
is a track()
method that can be
used to have one object track another at a given x and
y offset. Here is an example of how it is used.
1from gewel.color import GREEN, ORANGE
2from gewel.draw import MarkerX, MarkerPlus, Background, Scene
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8
9# Script d0
10d0 = MarkerX(x=32, y=100, color=ORANGE, line_width=3)
11d0.move_to(32, 200, duration=1.0)
12d0.move_to(608, 100, duration=2.0)
13d0.move_to(32, 100, duration=2.0)
14
15# Link the motion of d1 to that of d0.
16# It will follow d0 but 100 pixels below.
17d1 = MarkerPlus(x=32, y=200, color=GREEN, line_width=3)
18d1.track(d0, 0.0, 100.0)
19
20# Assemble the scene.
21scene = Scene([background, d0, d1])
22
23# Rendering phase:
24
25recorder = GifRecorder('motion_track.gif')
26recorder.record(scene)
That code produces the following gif as output:
A Motion Linked Solar System
We can now put together some of the motion linking
approaches we just saw above, along with a new one called
orbit()
, to create an animated
solar system. Here is the code:
1from gewel.color import YELLOW, BLUE, GRAY
2from gewel.draw import MarkerDot, Background, Scene, TextBox, TextVerticalPosition
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8
9# Sun
10sun = MarkerDot(x=320, y=240, width=100, color=YELLOW)
11sun.wait(4.0)
12sun_label = TextBox(
13 "Sun", x=370, y=190, width=100, height=16,
14 vertical_position=TextVerticalPosition.BOTTOM,
15 z=1.0
16)
17sun_label.track(sun, 50, -50)
18
19# Planet
20planet = MarkerDot(x=120, y=240, width=30, color=BLUE)
21planet.orbit(sun, 200, 200, orbit_duration=4.0)
22planet_label = TextBox(
23 "Planet", x=140, y=220, width=100, height=16,
24 vertical_position=TextVerticalPosition.BOTTOM,
25 z=1.0, font_size=10.0
26)
27planet_label.track(planet, 20, -20)
28
29# Moon
30moon = MarkerDot(x=70, y=240, width=10, color=GRAY)
31moon.orbit(planet, 50, 50, orbit_duration=1.0)
32moon_label = TextBox(
33 "Moon", x=85, y=225, width=100, height=16,
34 vertical_position=TextVerticalPosition.BOTTOM,
35 z=0.9, font_size=9.0
36)
37moon_label.track(moon, 15, -15)
38
39# Assemble the scene.
40scene = Scene([background, sun, sun_label, planet, planet_label, moon, moon_label])
41
42# Rendering phase:
43
44recorder = GifRecorder('solar_system.gif')
45recorder.record(scene)
That code produces the following gif as output:
Notice that track()
causes the
labels to track at a fixed offset from the celestial
bodies, even when they follow complicated paths, as
moon
does.
Partial Motion Linking
Sometimes we don’t want one object to completely track another, but instead we want just part of it, like an endpoint or a control point, to track another.
Here is an example, using a BezierDrawable
with it’s points linked to other objects. We will
create some MarkerPlus
objects for the end points and some
MarkerX
objects for the control points. We will then
animate the end points so they move up and down. Next, we will construct
a BezierDrawable
from the points. Finally, we will render this all in a
Scene
and save that scene to a .gif file.
1from gewel.color import BLUE, ORANGE, PURPLE, RED
2from gewel.draw import Background, BezierDrawable, Scene, MarkerPlus, MarkerX
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8
9point0 = MarkerPlus(40, 40, line_width=2, color=RED)
10point1 = MarkerX(80, 240, line_width=2, color=ORANGE)
11point2 = MarkerX(560, 240, line_width=2, color=PURPLE)
12point3 = MarkerPlus(600, 440, line_width=2, color=BLUE)
13
14# Make the endpoints move up and down in
15# sequence.
16
17point0.move_to(40, 440, duration=1.5)
18
19point3.wait_for(point0)
20point3.move_to(600, 40, duration=1.5)
21
22point0.wait_for(point3)
23point0.move_to(40, 40, duration=1.5)
24
25point3.wait_for(point0)
26point3.move_to(600, 440, duration=1.5)
27
28# A Bezier based on the points above, some
29# of which will be in motion.
30bezier = BezierDrawable(
31 point0.x, point0.y,
32 point1.x, point1.y,
33 point2.x, point2.y,
34 point3.x, point3.y,
35)
36
37scene = Scene([background, point0, point1, point2, point3, bezier])
38
39# Rendering phase:
40
41recorder = GifRecorder('linked_bezier.gif')
42recorder.record(scene)
That code produces the following gif as output:
Notice that as the end points move, the shape of the Bezier curve changes accordingly. This is a simple example, but it illustrates how properties of objects that are time-varying can be linked.
Here is an another example, using a QuadraticDrawable
with it’s points linked to other objects. We will
create some MarkerPlus
objects for the end points and a
MarkerX
object for the control point. We will then
animate the control point so it moves up and down. Next, we will construct
a QuadraticDrawable
from the points. Finally, we will render this all in a
Scene
and save that scene to a .gif file.
1from gewel.color import BLUE, ORANGE, RED
2from gewel.draw import Background, QuadraticCurveDrawable, Scene, MarkerPlus, MarkerX
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8
9point0 = MarkerPlus(40, 40, line_width=2, color=RED)
10point1 = MarkerX(40, 440, line_width=2, color=ORANGE)
11point2 = MarkerPlus(600, 440, line_width=2, color=BLUE)
12
13# Make the control point move from corner to corner.
14
15point1.move_to(600, 40, duration=1.5)
16point1.move_to(40, 440, duration=1.5)
17
18# A quadratic curve based on the points above, some
19# of which will be in motion.
20quad = QuadraticCurveDrawable(
21 point0.x, point0.y,
22 point1.x, point1.y,
23 point2.x, point2.y,
24)
25
26scene = Scene([background, point0, point1, point2, quad])
27
28# Rendering phase:
29
30recorder = GifRecorder('linked_quadratic.gif')
31recorder.record(scene)
That code produces the following gif as output:
Notice that as the control point moves, the shape of the quadratic curve changes accordingly. This is a simple example, but it illustrates how properties of objects that are time-varying can be linked.
Advanced Motion Linking
We can use expressions and multiple objects to create a variety of effects with motion linking. In the next example, we will use motion linking to create wheels on a wagon that rotate as the wagon moves.
1from gewel.color import BLACK, DARK_GRAY, RED
2from gewel.draw import Box, MarkerPlus, MarkerO, Background, Scene
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8
9# The wagon.
10wagon = Box(x=20, y=100, width=160, height=32, color=DARK_GRAY, fill_color=RED)
11wagon.move_to(620 - wagon.width, 100, duration=1.5)
12wagon.move_to(20, 100, duration=3.0)
13
14# Wheels move with wagon and rotate as it moves.
15wheels = []
16tires = []
17for ii in range(2):
18 wheel = MarkerPlus(
19 x=wagon.x + 10 + 140 * ii, y=wagon.y + wagon.height + 4,
20 width=32, line_width=2, color=DARK_GRAY, z=1.0
21 )
22
23 tire = MarkerO(
24 x=wheel.x, y=wheel.y,
25 width=wheel.width + 2, line_width=5, color=BLACK, z=2.0
26 )
27 wheels.append(wheel)
28 tires.append(tire)
29
30 # Rotate with linear motion of the wagon. When the
31 # wheel rotates theta radians, the x distance the
32 # wagon travels is theta * r, where r is
33 # the radius of the tire. Since we know x,
34 # we can compute theta = x / r.
35 radius = (tire.width + tire.line_width) / 2
36 wheel.theta = wagon.x / radius
37
38# Assemble the scene.
39scene = Scene([background, wagon] + wheels + tires)
40
41# Rendering phase:
42
43recorder = GifRecorder('motion_track_wheels.gif')
44recorder.record(scene)
That code produces the following gif as output:
Notice that the wagon moves at twice as fast from left to right as it does from right to left. Since the angle of the wheels is linked to position, the wheels rotate twice as fast when the wagon is moving from left to right as they do when it moves from right to left. We never had to explicitly specify that behavior for the wheels. The fact that their angle is linked to the x position of the wagon made it happen.
If you would like to learn more about how the time-varying values (tvx) that enable motion linking work, see tvx: An Introduction.
Using a Teleprompter
Teleprompter
objects are boxes of
scrolling text that can be synchronized
to the actions of other objects on the screen. They are typically
used to synchronize voice-over dialog with other actions in
the animation. A voice-over artist can then read along with them
to record an audio track to go with the animation. They can also
be left in the final animation as subtitles.
1from gewel.color import ORANGE, YELLOW, Color
2from gewel.draw import MarkerDot, Background, Scene, Teleprompter, sync
3from gewel.record import GifRecorder
4
5# Scripting phase:
6
7background = Background()
8
9# Create our teleprompter.
10teleprompter = Teleprompter(
11 30, 400, 580, 70,
12 font_size=24,
13 color=YELLOW,
14 fill_color=Color(0.25, 0.25, 0.25, 0.75)
15)
16
17# Start with an invisible drawable.
18drawable = MarkerDot(x=320, y=240, width=64, color=ORANGE, alpha=0.0)
19
20# Now we will alternately add text to the teleprompter
21# and start actions we are describing in the animation.
22
23teleprompter.add_script(
24 """The orange dot is a mysterious creature.
25
26 Sometimes you don't even realize it is there, but
27 it just slowly fades in.
28 """
29)
30
31drawable.fade_to(1.0, duration=8.0)
32
33# Whichever one is slower should wait for the other
34# before the scene continues.
35sync((drawable, teleprompter))
36
37teleprompter.add_script(
38 """But as soon as you see it...
39 It quietly moves away.
40 """
41)
42
43drawable.move_to(800, 600, duration=6.0)
44
45scene = Scene([background, drawable, teleprompter])
46
47# Rendering phase:
48
49recorder = GifRecorder('teleprompter.gif')
50recorder.record(scene)
That code produces the following gif as output: