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.