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.