Renpy Tutorials: Custom Transitions Part 1

Transitions in renpy are one of the most commonly used types of "animations" in practice. If you've ever done:

scene i1234 with dissolve

Then you've used transitions, in this case the default Dissolve. If you text search "with dissolve" or "with fade" in any random renpy game's scripts you will most likely get a lot of results. And there's good reason for it: when you are essentially working mostly with stills, or even movies with sharp cuts, using transitions will add some visual interest and even the illusion of movement.

So it's a little odd that something so useful tends to be lack customization in most titles. Very few transitions, outside of fades, and dissolves, are actually utilized; even imagedissolves, which provide a lot of flexibility while still being simple, are not used much by people.

The reason for this, I think, is partly because people often do not understand what transitions actually are, or think the only means of making a new type is something akin to writing a new class like ImageDissolve yourself. In reality, writing a custom transition is no harder than any ATL, which I will demonstrate. Then later, I will show how you can take it even further with shaders.

At the core, transitions are Callables (classes that implement __call__ which includes any python function) that takes a new_widget and an old_widget, and returns a new Displayable that animates between them. The actual result really doesn't matter: you could "animate" the transition by just returning the new_widget and that would be sufficient.

init python:
    def useless_transition(old_widget=None, new_widget=None):
        return Transform(child=new_widget)
...

    scene a9_0611 with useless_transition
    pause

You might notice that we wrapped the new_widget in a basic Transform. The reason why, is that the one requirement for the Displayable returned from the Callable is that it has the delay attribute, which all Transforms have. The delay tells renpy when executing with how long the transition takes. In this case we don't even need to set it because the "animation" is just "immediately move to the new image", but it will still try to read it.

But that actually shows the easiest way to make a custom transition. Not with a function, not with a new class, but with the existing Transform class. Even more specifically, with an ATLTransform:

transform blurdissolve(new_widget=None, old_widget=None):
    delay 3.0
    old_widget
    blur 0.0
    linear 1.5 blur 30.0
    new_widget with dissolve
    linear 1.5 blur 0.0

This is a custom transition that, over 3 seconds, blurs the previous scene, dissolves to the new scene, then unblurs. The usage is the same as ever: with blurdissolve.

Why does this work? Well, Transforms are all Callables, which is to say they implement __call__ in their class. What happens with you call a Transform is that it will copy all of its properties, plus any passed in overrides, into a new Transform and return it. This is the core of what allows Transform chaining to work.

The two keys of an ATLTransform, is the old_widget and new_widget arguments and the delay property. You don't even need to use the widgets passed in, you can make the ATL show a completely unrelated image during the transition (though at the end, the new_widget will still be displayed). But as long as you make your Transform follow this pattern, it can be used as a transition. You can even add parameters:

transform blurdissolve2(blur_amount=30.0, new_widget=None, old_widget=None):
    delay 3.0
    old_widget
    blur 0.0
    linear 1.5 blur blur_amount
    new_widget with dissolve
    linear 1.5 blur 0.0

Then you can use it like:

scene a9_0611 with blurdissolve2(10.0)

Thus adding to the flexibility.

Next time, I will show you how to bring shaders into the mix, thus enabling you to take customization to the max.