Part 10: Transitions

We’re already familiar with the SpriteNodeTransition object which we used to make Node’s sprite change, creating a frame-by-frame animation effect. We mentioned that transitions are much general and powerful mechanism. It’s time to explain what they are and how to use them.

When writing a game you’ll often want to apply a set of known transformations to an object. For example, you want your object to move 100 pixels to the right, then wait 3 seconds and return 100 pixels to the left. Or you want to implement pulsation effect where an object would smoothly change its scale between some min and max values. There’s an unlimited number of such visual transformations that you may want in your games as they greatly improve the game experience.

You can of course implement all this by having a set of boolean flags, time trackers, etc. and use all those helper variables to change the desired properties of your nodes over time manually. But there is an easier way: Transitions.

A single Transition object is a ‘recipe’ of how a given property of a Node (position, scale, rotation, etc.) should change over time. Transition can be applied to object once, given number of times or in a loop. You can chains transitions to run serially or in parallel.

It’s best to illustrate on an example, so let’s do it!

Adding a Transition to a Node

Let’s practice transitions on a text node we have in the title screen.

Let’s start by refactoring the code in __init__:

scenes/title_screen.py
class TitleScreenScene(Scene):

    def __init__(self):
        # ... cut the rest of the function ....
        self.exit_label = TextNode(font=registry.global_controllers.assets_controller.font_2, font_size=30,
                                     position=Vector(settings.VIEWPORT_WIDTH/2, 550), text="Press ESC to exit",
                                     z_index=1, origin_alignment=Alignment.center)
        self.root.add_child(self.exit_label)
        self.transitions_fun_stuff()

Then add the transitions_fun_stuff method:

scenes/title_screen.py
from kaa.transitions import *

def transitions_fun_stuff(self):
    my_transition = NodePositionTransition(Vector(300, 850), duration=3000)
    self.exit_label.transition = my_transition

Run the game and see the label moving from its original position to (300, 850), the movement takes 3 seconds! We did not have to change its position manually inside update(), it all happened automatically. Isn’t it cool?

Changing a value incrementally

The transition we wrote takes node position and changes it (over 3 seconds) to the final value. But what if we don’t want to move a node to a know position, but just 50 pixels to the left and 200 pixels down?

scenes/title_screen.py
def transitions_fun_stuff(self):
    my_transition = NodePositionTransition(Vector(-50, 200), duration=3000,
                                           advance_method=AttributeTransitionMethod.add)
    self.exit_label.transition = my_transition

Available advance_method values are:

  • kaa.transitions.AttributeTransitionMethod.set - the default mode. The target value is set directly.

  • kaa.transitions.AttributeTransitionMethod.add - The target value will be calculated by adding operation

  • kaa.transitions.AttributeTransitionMethod.multiply - The target value will be calculated by multiplying operation

Running transition back and forth

To run transition back and forth simply set back_and_forth=True on a transition object:

scenes/title_screen.py
def transitions_fun_stuff(self):
    my_transition = NodePositionTransition(Vector(-50, 200), duration=3000,
                                           advance_method=AttributeTransitionMethod.add,
                                           back_and_forth=True)
    self.exit_label.transition = my_transition

Notice that the 3000 milisecond is the one-way time duration. Total transition duration back and forth takes 6000 miliseconds

Running transition specific number of times

To run transition specific number of times, set loops on a transition object to a desired value:

scenes/title_screen.py
def transitions_fun_stuff(self):
    my_transition = NodePositionTransition(Vector(-50, 200), duration=3000,
                                           advance_method=AttributeTransitionMethod.add,
                                           back_and_forth=True, loops=3)
    self.exit_label.transition = my_transition

Run it and see that it moves back and forth 3 times.

Note

See what happens if you set loops to some value without back_and_forth set to False

Running transition infinite number of times

To run transition in an infinite loop set loops on a transition object to 0.

All types of transitions

We’ve learned about NodePositionTransition but what other transitions are available?

  • kaa.transitions.NodeSpriteTransition - changes sprite of a node (we’ve already learned that)

  • kaa.transitions.NodePositionTransition - changes position of a node

  • kaa.transitions.NodeRotationTransition - changes rotation of a node

  • kaa.transitions.NodeScaleTransition - changes scale of a node

  • kaa.transitions.NodeColorTransition - changes color of a node

  • kaa.transitions.BodyNodeVelocityTransition - changes velocity of a node (applicable to BodyNodes only)

  • kaa.transitions.BodyNodeAngularVelocityTransition - changes angular velocity of a node (applicable to BodyNodes only)

  • kaa.transitions.NodeTransitionDelay - waits for given number of miliseconds - useful when you chain few transitions together

It is also possible to write custom transitions, it’s covered further below.

Chaining transitions

Let’s build a chain of transitions: first we want the node to change its position, then rotate, then wait 0.5 second, then scale, and finally change color. To build such a sequence we’ll use NodeTransitionsSequence

scenes/title_screen.py
from kaa.colors import Color
import math

def transitions_fun_stuff(self):
    move_transition = NodePositionTransition(Vector(-50, 200), duration=1000, advance_method=AttributeTransitionMethod.add)
    rotate_transition = NodeRotationTransition(2*math.pi, duration=1000) # rotate 180 degrees (2*pi radians)
    wait_transition = NodeTransitionDelay(duration=500)
    scale_transition = NodeScaleTransition(Vector(2, 2), duration=1000) # enlarge twice
    color_transition = NodeColorTransition(Color(1, 0, 0, 1), duration=1000) # change color to red
    transition_sequence = NodeTransitionsSequence([move_transition, rotate_transition, wait_transition,
                                                   scale_transition, color_transition])
    self.exit_label.transition = transition_sequence

Run the game and enjoy the nice transition sequence!

NodeTransitionsSequence has two already known properties: back_and_forth and loops. You can use them to run the whole sequence back and forth, specific number of times or in an infinite loop.

Knowing that a transition has ended

Sometimes we may want to be able to run some code when transition has ended, or when we reached some point in a chain of transition. We can use NodeTransitionCallback. It’s only parameter is a callable. Let’s show this on an example:

scenes/title_screen.py
def transition_callback_function(self, node):
    # play explosion sound
    registry.global_controllers.assets_controller.explosion_sound.play()

def transitions_fun_stuff(self):
    move_transition = NodePositionTransition(Vector(-50, 200), duration=1000, advance_method=AttributeTransitionMethod.add)
    callback_transition = NodeTransitionCallback(self.transition_callback_function) # call that function
    rotate_transition = NodeRotationTransition(2*math.pi, duration=1000) # rotate 180 degrees (2*pi radians)
    wait_transition = NodeTransitionDelay(duration=500)
    scale_transition = NodeScaleTransition(Vector(2, 2), duration=1000) # enlarge twice
    color_transition = NodeColorTransition(Color(1, 0, 0, 1), duration=1000) # change color to red
    transition_sequence = NodeTransitionsSequence([move_transition, callback_transition,
                                                   rotate_transition, wait_transition,
                                                   scale_transition, color_transition])
    self.exit_label.transition = transition_sequence

It’s pretty self-explanatory isn’t it? callback_transition is executed between move_transition and rotate_transition therefore we hear explosion sound at that very moment.

Running transitions in paralel

Let’s say we want to run some transitions (or sequences of those) in paralel. It’s quite easy: we need to use NodeTransitionsParallel. Let’s have our node rotate, scale, change color and move at the same time.

scenes/title_screen.py
def transitions_fun_stuff(self):
    rotate_transition = NodeRotationTransition(2*math.pi, duration=1000) # rotate 180 degrees (2*pi radians)
    scale_transition = NodeScaleTransition(Vector(2, 2), duration=1000) # enlarge twice
    color_transition = NodeColorTransition(Color(1, 0, 0, 1), duration=1000) # change color to red

    move_transition1 = NodePositionTransition(Vector(-200, 0), duration=1000,
                                       advance_method=AttributeTransitionMethod.add)
    move_transition2 = NodePositionTransition(Vector(200, 200), duration=1000,
                                       advance_method=AttributeTransitionMethod.add)
    move_transition3 = NodePositionTransition(Vector(200, -200), duration=1000,
                                       advance_method=AttributeTransitionMethod.add)
    move_transition4 = NodePositionTransition(Vector(-200, 0), duration=1000,
                                       advance_method=AttributeTransitionMethod.add)

    move_sequence = NodeTransitionsSequence([move_transition1, move_transition2, move_transition3, move_transition4], loops=0)
    paralel_sequence = NodeTransitionsParallel([rotate_transition, scale_transition, color_transition], back_and_forth=True, loops=0)

    # run both the movement sequence and rotate+scale+color sequence in paralel
    self.exit_label.transition = NodeTransitionsParallel([
        move_sequence, paralel_sequence])

Note that NodeTransitionsParallel has two already known properties: back_and_forth and loops.

You can nest transition sequences in other sequences, run such nested sequences in paralel and so on. Be aware on which level you set the back_and_forth and loops param values. Feel free to experiment with transitions on your own.

Contradictory transitions?

What happens if you try to run two position transitions in paralel: one moving a node 100 pixels to the right and the other moving it 100 pixels to the left. Contrary to intuition, they won’t cancel out (regardless of advance_method being add or set). If there are two or more transitions of the same type running in paralel, then the one which is later in the list will be used and the preceding ones will be ignored.

Implementing custom transitions

You can implement your own transition, where you can fully control what’s happening with the node over time.

Use CustomNodeTransition class. It takes 3 parameters:

  • A callable with one parameter of type <Node>. This function will be called once, when the transition is assigned to a Node (it will pass that Node as parameter). Imeplement this function to return a state.

  • A callable with three parameters: state, node and t. It will be called every frame during which the transition is in effect. State parameter is an object you prepared in the previous callable. Node parameter is the node that’s transitioning. t is a value between 0 and 1 indicating time progress of present transition cycle

  • A numerical value - duration of transition in miliseconds

CustomNodeTransition also has the back_and_forth and loops described in sections above.

Different easing patterns

As you probably noticed, transitions change the property of a node over time in a linear fashion. In other words, if transition orders the node to change rotation by 100 degrees in 10 seconds then the node will progress at a steady rate of 10 degrees per second.

Future kaa versions will have more types of “easing functions”, other than linear, expect something similar to this

Let’s move on to the last part of the tutorial where we’ll build the game as executable file (.exe on Windows or binary executable on Linux)