Animations¶
Now that we’ve got the Clock basics out of the way, let’s talk about Animations: a way to smoothly change an object’s attribute over time.
First, let’s take a look at this piece of code (differences from the previous page are highlighted):
from gillcup_graphics import Layer, Rectangle, Window, run, RealtimeClock
from gillcup import Animation
root_layer = Layer()
rect = Rectangle(root_layer,
size=(0.5, 0.5),
position=(0.5, 0.5),
relative_anchor=(0.5, 0.5),
rotation=45,
)
Window(root_layer, width=400, height=400)
clock = RealtimeClock()
def blink(on):
if on:
clock.schedule(Animation(rect, 'opacity', 1, time=0.3))
else:
clock.schedule(Animation(rect, 'opacity', 0, time=0.3))
if not rect.dead:
clock.schedule(lambda: blink(not on), 0.5)
blink(True)
clock.schedule(Animation(rect, 'rotation', 0, time=1, timing='infinite'))
clock.schedule(Animation(rect, 'color', 1, 0.5, 0, time=5))
run()
Aside from a new import, the first difference is in the blink function:
if on:
The opacity
attribute affects the visibility of our Rectangle – 0 means
completely transparent, 1 is fully opaque, anything in between that would
make the Rectangle see-through.
Opacity, and other properties like size
, scale
,
relative_anchor
, rotation
or color
, are Gillcup
animated properties.
There are several ways to manipulate them:
they can be set when creating a GraphicsObject, as we’ve seen
previously in the tutorial;
they become attributes of the object so you can set them normally, as in
rest.opacity = 0.4
,
and finally, they can be animated, as we’ll see later.
Here, instead of just changing the value, we schedule an
Animation
on our clock.
The Animation will smoothly change rect
‘s opacity
from its current
value to 1
in the interval of 0.3
seconds (that is, 0.3 of whatever
units of time clock
uses).
The second changed line is similar – it just animates the value back to zero.
The next added line reads:
Similar to the above, this smoothly changes the rotation
from the current
value (45) to 0 in the course of 1 second.
However, due to the infinite
timing, it doesn’t stop there: it continues
past 0° and goes on, through -45° in the second second, -90° in the third,
and so on.
The final effect is that our square will rotate forever.
The last added line reads:
Here, we re-color our square from white to orange over the course of 5 seconds.
We’re animating a tuple property – the color
consists of three values,
red
, green
, and blue
.
So, we need to pass three values to the Animation.
We could also animate the component properties individually, with the same
effect.
Most tuple properties are like this: position
‘s components are is x
and y
, scale
‘s components include scale_x
and scale_y
, and so
on.
Whether you animate the entire tuple or the compoents is up to you.
The Animation
class has several options and subclasses that
let you take control of exactly how the attribute is changed;
the infinite
timing is just one of them.
If you wish to control an animated property in ways unrelated to a clock,
take a look in the gillcup.effect
module.
The Effect class provides the control over an attribute; Animation is an Effect
subclass that adds time-related stuff. And chaining.
Chaining animations¶
Animations offer a way to schedule an action to do after they are done. In the following code:
from gillcup_graphics import Layer, Rectangle, Window, run, RealtimeClock
from gillcup import Animation
root_layer = Layer()
rect = Rectangle(root_layer,
size=(0.5, 0.5),
position=(0.25, 0.25),
relative_anchor=(0.5, 0.5),
)
Window(root_layer, width=400, height=400)
clock = RealtimeClock()
def announce_end():
print 'done'
animation = Animation(rect, 'x', 0.75, time=1)
clock.schedule(animation)
animation = animation.chain(Animation(rect, 'y', 0.75, time=1))
animation = animation.chain(Animation(rect, 'x', 0.25, time=1))
animation = animation.chain(Animation(rect, 'y', 0.25, time=1))
animation.chain(announce_end)
run()
…the chain()
method does just that:
when an Animation is done, chain
‘s argument is scheduled on that
Animation’s clock.
The argument can be another Animation, or just a regular callable.
When doing complex stuff, be aware that each Animation can only be scheduled
and run once.
If you need to do the same thing several times, you can make an Animation
factory (look at blink
in the first Animation example).
Building animations¶
Using the chain
method can be clumsy at times.
Another way to build complex animations from the basic building blocks is
provided by the +
and |
operators:
from gillcup_graphics import Layer, Rectangle, Window, run, RealtimeClock
from gillcup import Animation
root_layer = Layer()
rect = Rectangle(root_layer,
size=(0.5, 0.5),
position=(0.25, 0.25),
relative_anchor=(0.5, 0.5),
)
Window(root_layer, width=400, height=400)
clock = RealtimeClock()
def announce_end():
print('done')
animation = (
Animation(rect, 'x', 0.75, time=1) +
0.5 + # a half-second delay
Animation(rect, 'y', 0.75, time=1))
animation += [0.5, Animation(rect, 'x', 0.25, time=1), 0.5]
animation += (
Animation(rect, 'y', 0.25, time=1) |
Animation(rect, 'rotation', -90, time=1))
animation += announce_end
clock.schedule(animation)
run()
The operators create a larger animation, which runs its components one after
the other (+
) or in parallel (|
).
To create delays, you can “add” numbers to Animations.
You can also use lists (or other iterables) of animations; these work as if
all their components were “added” toghether (except iterators are evaluated
lazily).
However, be sure that you don’t accidentally pass a raw number of list to
chain
or Clock.schedule
.
Since each animation can only be scheduled once, be careful to not accidentally schedule any “parts” of a larger animation individually.
If you wish to look how this is implemented, or how to extend it in
different ways, see Gillcup’s actions
module.
Actions implement the concept of a chainable event; Animation (an Action
subclass) adds the changing of an object’s attribute.
Processes¶
Finally, we can schedule actions via a Process
generator.
This allows you to lay out an animation in a procedural manner, as if
you were writing a script for a movie.
Things that don’t happen in a single instant (of Clock time) are handled
by yielding actions.
This works best for complex animations that require additional processing or
looping, but don’t “branch out” too much.
from gillcup_graphics import Layer, Rectangle, Window, run, RealtimeClock
from gillcup import Animation
from gillcup.actions import process_generator
root_layer = Layer()
rect = Rectangle(root_layer,
size=(0.5, 0.5),
position=(0.25, 0.25),
relative_anchor=(0.5, 0.5),
)
Window(root_layer, width=400, height=400)
clock = RealtimeClock()
@process_generator
def process():
while not rect.dead:
for prop, value in (
('x', 0.75),
('y', 0.75),
('x', 0.25),
('y', 0.25)):
print('Animating {0} towards {1}'.format(prop, value))
yield Animation(rect, prop, value, time=0.5, easing='sine.in_out')
clock.schedule(process())
run()
Any action [1] that is yield
-ed is scheduled on the clock, and the rest of
the process is chained to it.
Note that, again, the “infinite” animation loop is broken when its object dies.
[1] | You can also yield numbers or lists/iterables, as with the + and
| operators. |