transitions — A quick and easy way to automate transforming your nodes

When writing a game you’ll often want to apply a set of known transformations to a nodes.Node. For example, you want your Node to move 100 pixels to the right, then wait 3 seconds and return 100 pixels to the left. Or you’ll want the node to pulsate (smoothly change its scale between some min and max values), or rotate back and forth. YOu may want to have any combination of those effects applied (either one after another or parallel). There’s an unlimited number of such visual transformations and their combinations, that you may want to have in your game.

You can of course implement all this, by having a set of boolean flags, time trackers, etc. and use all those helper variables to manually change the desired properties of your nodes from within the update() method. But there is an easier way: the mechanism is called Transitions. A single Transition object is a recipe of how a given property of a Node (position, scale, rotation, color, sprite, etc.) should change over time. Transition can be played once, given number of times or in a loop. You can also chains transitions to run one after another or in parallel.

Transitions are the primary way of creating animations. Since animation is nothing else than just changing Node’s sprite over time, the transition mechanism comes useful for that purpose.

Common transition parameters

Note: All transitions are immutable.

To create a Transition you’ll typically need to pass the following parameters:

  • advance_value - advance value for given transition type (e.g. target position for NodePositionTransition).

  • duration - transition duration time, in seconds

  • advance_method - an enum value of AttributeTransitionMethod type which determines how the advance_value will be applied to modify the appropriate node property.
    • AttributeTransitionMethod.set - node’s property will be changed towards the advance_value over time

    • AttributeTransitionMethod.add - node’s property will be changed towards the current value + advance_value over time

    • AttributeTransitionMethod.multiply - node’s property will be changed towards the current value * advance_value over time

  • loops - Optional. How many times the transition should “play”. Set to 0 to play infinite number of times. Default is 1.

  • back_and_forth - Optional. If set to True, the transition will be played back and forth (that counts as one “loop”). Default is False.

  • easing - Optional. An enum value of easings.Easing - specifies the rate of change of a value over time. Default is Easing.none which really means a linear easing.

Note

The duration parameter always refers to one loop, one direction. So for example, transition with the following set of parameters: duration=1., loops=3, back_and_forth=True will take 6 seconds. 1 second played back and forth is 2 seconds, and it will be played 3 times, hence a total time of 6 seconds.

Note

If back_and_forth is set to True, the transition will play back and forth which counts as one loop.

All transitions use linear easing. More built-in easing types are to be added soon.

Examples

Change position of a node, from (100,100) to (30, 70) over 2 seconds.

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
node.transition = NodePositionTransition(Vector(30, 70), 2.)

Change position of a node, from (100,100) by (30, 70), i.e. to (130, 170) over 2 seconds.

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
node.transition = NodePositionTransition(Vector(30, 70), 2., advance_method=AttributeTransitionMethod.add)

Change position of a node, from (100, 100) by (x30, x70), i.e. to (3000, 7000) over 2 seconds.

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
node.transition = NodePositionTransition(Vector(30, 70), 2., advance_method=AttributeTransitionMethod.multiply)

Change position of a node, from (100,100) to (30, 70) then back to the initial position (100,100) over 2 seconds.

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
node.transition = NodePositionTransition(Vector(30, 70), 2., back_and_forth=True)

Change position of a node, from (100,100) to (30, 70) then get back to the initial position over 2 seconds. Repeat it 3 times.

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
node.transition = NodePositionTransition(Vector(30, 70), 2., loops=3, back_and_forth=True)

Change the scale of a node (twice on the X axis and three times on the Y axis) over 1 second.

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
node.transition = NodeScaleTransition(Vector(2, 3), 1.)

Change the scale of a node (twice on the X axis and three times on the Y axis) over 1 second. Repeat indefinitely (creating pulsation effect).

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
node.transition = NodeScaleTransition(Vector(2, 3), 1., loops=0)

Rotate the node 90 degrees clockwise over 3 seconds

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
node.transition = NodeRotationTransition(math.pi/2, 3.)

Change position of a node by (150, 100) over 2 seconds, then enlarge it twice over 1 second, then do nothing for 2 seconds, finally rotate it 180 degrees over 3 seconds. Play the whole sequence two times, back and forth.

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
transitions = [
    NodePositionTransition(Vector(150, 100), 2., advance_method=AttributeTransitionMethod.add),
    NodeScaleTransition(Vector(2, 2), 1.),
    NodeTransitionDelay(2.),
    NodeRotationTransition(math.pi, 3.)
]
node.transition = NodeTransitionsSequence(transitions, loops=2, back_and_forth=True)

Do everything the same like in previous example but have the node simultaneously change its color to red, back and forth in 1500 milisecond intervals.

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
transitions = [
    NodePositionTransition(Vector(150, 100), 2., advance_method=AttributeTransitionMethod.add),
    NodeScaleTransition(Vector(2, 2), 1.),
    NodeTransitionDelay(2.),
    NodeRotationTransition(math.pi, 3.)
]
color_transition = NodeColorTransition(Color(1,0,0,1), 1.5, loops=0, back_and_forth=True)

node.transition = NodeTransitionsParalel([
    color_transition,
    NodeTransitionsSequence(transitions, loops=2, back_and_forth=True)
])

Change position of a node, from (100,100) to (30, 70) over 2 seconds and call function my_func when the transition ends.

def my_func(transitioning_node):
    print('Function called!')

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
node.transition = NodeTransitionSequence([
    NodePositionTransition(Vector(30, 70), 2.),
    NodeTransitionCallback(my_func)])

Change sprite of a node, creating an animation effect:

spritesheet = Sprite(os.path.join('assets', 'gfx', 'spritesheet.png')
frames = split_spritesheet(spritesheet, Vector(100,100)) # cut the spritesheet into <Sprite> instances
animation = NodeSpriteTransition(frames, duration=2., loops=0, back_and_forth=False)
node = Node(position=Vector(100, 100), transition=animation)

Change z_index of a node over time:

node = Node(position=Vector(100, 100), sprite=Sprite('image.png'))
node.transition = NodeZIndexSteppingTransition([1,2,3,4,5,6,10,100], 1000)

NodePositionTransition reference

class transitions.NodePositionTransition(advance_value, duration, advance_method=AttributeTransitionMethod.set, loops=1, back_and_forth=False, easing=Easing.none)

Use this transition to change Node’s position gradually over time, towards given advance_value or by given advance_value.

The advance_value param must be a geometry.Vector and is the target position value (or position change value)

Refer to the Common transition parameters and Examples sections for information on other parameters used by the transition.

NodeRotationTransition reference

class transitions.NodeRotationTransition(advance_value, duration, advance_method=AttributeTransitionMethod.set, loops=1, back_and_forth=False, easing=Easing.none)

Use this transition to change Node’s rotation gradually over time, towards given advance_value or by given advance_value.

The advance_value param must be a float and is the target rotation value (or rotation change value), in radians.

Refer to the Common transition parameters and Examples sections for information on other parameters used by the transition.

NodeScaleTransition reference

class transitions.NodeScaleTransition(value, duration, advance_method=AttributeTransitionMethod.set, loops=1, back_and_forth=False, easing=Easing.none)

Use this transition to change Node’s scale gradually over time, towards given advance_value or by given advance_value.

The advance_value param must be a geometry.Vector and is the target scale value (or scale change value) for X and Y axis respectively.

Refer to the Common transition parameters and Examples sections for information on other parameters used by the transition.

NodeColorTransition reference

class transitions.NodeColorTransition(value, duration, advance_method=AttributeTransitionMethod.set, loops=1, back_and_forth=False, easing=Easing.none)

Use this transition to change Node’s scale gradually over time, towards given advance_value or by given advance_value.

The advance_value param must be a colors.Color and is the target color value (or color change value).

Note that each component of the color (R, G, B, and A) is trimmed to a 0-1 range, so when using advance_method=AttributeTransitionMethod.set or advance_method=AttributeTransitionMethod.multiply which would result in R G B or A going above 1 or below 0 - the value will be capped at 1 and 0 respectively.

Refer to the Common transition parameters and Examples sections for information on other parameters used by the transition.

BodyNodeVelocityTransition reference

class transitions.BodyNodeVelocityTransition(value, duration, advance_method=AttributeTransitionMethod.set, loops=1, back_and_forth=False, easing=Easing.none)

Use this transition to change BodyNode’s velocity gradually over time, towards given advance_value or by given advance_value.

The advance_value param must be a geometry.Vector and is the target velocity value (or velocity change value).

Refer to the Common transition parameters and Examples sections for information on other parameters used by the transition.

BodyNodeAngularVelocityTransition reference

class transitions.BodyNodeAngularVelocityTransition(value, duration, advance_method=AttributeTransitionMethod.set, loops=1, back_and_forth=False, easing=Easing.none)

Use this transition to change BodyNode’s angular velocity gradually over time, towards given advance_value or by given advance_value.

The advance_value param must be a number and is the target angular velocity value (or angular velocity change value), in radians

Refer to the Common transition parameters and Examples sections for information on other parameters used by the transition.

NodeSpriteTransition reference

class transitions.NodeSpriteTransition(sprites, duration, loops=1, back_and_forth=False, easing=Easing.none)

Use this transition to create animations. The transition will change Node’s sprite over time specified by the duration parameter, iterating through sprites list specified by the sprites parameter.

The sprites must be an iterable holding sprites.Sprite instances. To cut a spritesheet file into individual sprites (individual frames) use the utility function sprites.split_spritesheet()

The loops and back_and_forth parameters work normally - refer to the Common transition parameters section for more information on those parameters.

NodeZIndexSteppingTransition reference

class transitions.NodeZIndexSteppingTransition(z_index_list, duration, loops=1, back_and_forth=False, easing=Easing.none)

Allows to change z_index of a node over time.

The z_index_list must be an iterable with z_index values.

NodeTransitionsSequence reference

class transitions.NodeTransitionSequence(transitions, loops=1, back_and_forth=False)

A wrapping container used to chain transitions into a sequence. The sequence will run one transition at a time, next one being executed when the previous one finishes.

The transitions parameter is an iterable of transitions.

The iterable can include a list of ‘atomic’ transitions such as NodePositionTransition, NodeScaleTransition, NodeColorTransition etc. as well as other NodeTransitionSequence, or NodeTransitionsParallel thus building a more complex structure.

The loops and back_and_forth parameters work normally, but are applied to the whole sequence.

See the Examples sections for a sample code using NodeTransitionSequence.

NodeTransitionsParallel reference

class transitions.NodeTransitionsParallel(transitions, loops=1, back_and_forth=False)

A wrapping container used to make transitions run in parallel.

The transitions parameter is an iterable of transitions which will be executed simultaneously.

The iterable can include a list of ‘atomic’ transitions such as NodePositionTransition, NodeScaleTransition, NodeColorTransition etc. as well as other NodeTransitionSequence, or NodeTransitionsParallel thus building a more complex structure.

You may have two contradictory transitions running in parallel, for example two NodePositionTransition trying to change node position in opposite directions. 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 all the preceding ones will be ignored.

Since transitions runing in parallel may have different durations, the loops parameter is using the following logic: The longest duration is considered the “base” duration. Transitions whose duration is shorter than the base duration will wait (doing nothing) when they complete, until the one with the “base” duration ends. When the “base” transition ends, the new loop begins and all transitions start running in parallel again.

The back_and_forth=True is using the same logic: the engine will wait for the longest transition to end before playing all parallel transitions backwards.

See the Examples sections for a sample code using NodeTransitionsParallel.

Like all other transitions, NodeTransitionsParallel is immutable. That causes problems when you want transitions to be managed independently. Consider a situation where you want to have a Node with sprite animation (NodeSpriteTransition) and some other transition (e.g. NodePositionTransition), both running simuntaneously. Suppose you do that by wrapping the two transitions in NodeTransitionsParallel. Now, if you want to change just the sprite animation transition without changing the state of the position transition (a perfectly valid case in many 2D games), you won’t be able to do that because NodeTransitionsParallel is immutable!

To solve that problem, you should use NodeTransitionsManager - it allows running and managing multiple simultaneous transitions on a Node truly independently from each other.

NodeTransitionDelay reference

class transitions.NodeTransitionDelay(duration)

Use this transition to create a delay between transitions in a sequence.

The duration parameter is a number of seconds.

See the Examples sections for more information.

NodeTransitionCallback reference

class transitions.NodeTransitionCallback(callback_func)

Use this transition to get your own function called at a specific moment in a transitions sequence. A typical use case is to find out that a transition has ended.

The callback_func must be a callable.

See the Examples sections for a sample code using NodeTransitionCallback

NodeCustomTransition reference

class transitions.NodeCustomTransition(prepare_func, evaluate_func, duration, loops=1, back_and_forth=False, easing=Easing.none)

Use this class to write your own transition.

prepare_func must be a callable. It will be called once, before the transition is played. It receives one parameter - a node. It can return any value, which will later be used as input to evaluate_func

evaluate_func must be a callable. It will be called on each frame and it’s the place where you should implement the transition logic. It will receive three parameters: state, node and t. The state is a value you have returned in the prepare_func callable. The node is a node which is transitioning. The t parameter is a value between 0 and 1 which indicates transition time duration progress.

The loops and back_and_forth paramters behave normally - see the Common transition parameters section.

custom_transition = NodeCustomTransition(
        lambda node: {'positions': [
            Vector(random.uniform(-100, 100), random.uniform(-100, 100))
            for _ in range(10)
        ]},
        lambda state, node, t: setattr(
            node, 'position',
            state['positions'][min(int(t * 10), 9)],
        ),
        10.,
        loops=5,
    )

NodeTransitionsManager reference

class transitions.NodeTransitionsManager

Node Transitions Manager is accessed by the transitions_manager property on a nodes.Node. It allows to run multiple transitions on a node at the same time. Unlike NodeTransitionsParallel, which also runs multiple transitions simultaneously, the transitions managed by the NodeTransitionsManager are truly isolated. It means you can manage them (stop or replace them) individually not affecting other running transitions. This is not possible with transitions inside NodeTransitionsParallel, because the wrapper is immutable.

The manager offers a simple dictionary-like interface with two methods: get() and set() to access and set transitions by a string key.

Note that the transition manager is used when you set transition on a Node via the transition property. That transition can be accessed via get('__default__')

Similarly to NodeTransitionsParallel when you set two contradictory transitions of the same type to run on the manager (for example position transitions that pull the node in two opposite direction) - they will not cancel out. One of them will ‘dominate’. It is undetermined which one will dominate therefore it’s recommended not to compose transitions that way (why would you want to do it anyway?).

NodeTransitionsManager.get(transition_name)

Gets a transition by name (a string).

Node.transitions_manager.get('__default__') is an equivalent of Node.transition getter.

NodeTransitionsManager.set(transition_name, transition)

Sets a transition with a specific name (a string). The transition object can be any transition, either ‘atomic’ or a serial / parallel combo.

Node.transitions_manager.set('__default__', transition) is an equivalent of Node.transition setter.

node = Node(position=Vector(15, 60))
node.transitions_manager.set('my_transition', NodePositionTransition(Vector(100,100), duration=0.300, loops=0))
node.transitions_manager.set('other_transition', NodeRotationTransition(math.pi/2))
node.transitions_manager.set('can_use_sequence_coz_why_not',  NodeTransitionsSequence([
    NodeScaleTransition(Vector(2, 2), 1.),
    NodeTransitionDelay(2.),
    NodeColorTransition(Color(0.5, 1, 0, 1), 3.)],
    loops=2, back_and_forth=True))

AttributeTransitionMethod reference

class transitions.AttributeTransitionMethod

Enum type used to identify value advance method when using transitions

Available values are:

  • AttributeTransitionMethod.set

  • AttributeTransitionMethod.add

  • AttributeTransitionMethod.multiply