Ren'Py Trouble with getting a Dissolve when changing displayable in a screen

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,145
3,483
I'm having trouble getting a dissolve effect when changing between two images that are part of a screen.

The game I am working on has a complex user interface with an image of the heroine in the center. Depending on the chosen action, I'd like to switch the "layered image" parameters to show different clothing states or states with different ATL animations.

It's possible to get the effect I want in a normal "renpy dialog" as follows:

Code:
label layered_image_demo:
    scene black
    show heroine with Dissolve(1.0)
    "take off the top"
    show heroine notop with Dissolve(0.5)
    "touch the weiner"
    show heroine notop hj with Dissolve(0.5)
    "play hj animation"
    show heroine notop hjanim with Dissolve(0.2)
    pause 3.0
    show heroine notop hj with Dissolve(0.2)
    "finished"
    hide heroine with Dissolve(1.0)
    return
But I haven't not found a way to do a dissolve or crossfade between "states" when the layered image displayable is "add"ed to a Screen.

Here's some sample code that demonstrates what I am trying to achieve, it should work in any Renpy 8.2+ project environment (uses CycleVariable so need 8.2+):

Code:
label start:
    scene black
    call screen screen_example()

# using placeholders instead of images for this example
image image_one = Solid("#A22", xsize=100, ysize=100)
image image_two = Solid("#777", xsize=100, ysize=100)

default test_image_ref = "image_one"

screen screen_example():
    vbox:
        at truecenter
        spacing 50

        text "Screen add displayable crossfade test"


        ### I want to have a dissolve transition (or crossfade transform?) applied to just this image when the source displayable
        add test_image_ref

        textbutton "switch image":
            action CycleVariable("test_image_ref", ["image_one", "image_two"])

        textbutton "Quit":
            action Return(True)
 

MrSilverLust

MSL Games
Game Developer
May 22, 2021
440
2,876
Hi Osa!

First things first:

I didn't know that there is a CycleVariable function!!! I've coded something similar from scratch a few times... fml.

Now, your problem.

You need to create a transform to use with your add image. For example:


Code:
transform dissolve_t(t):
    alpha 0.0
    linear t alpha 1.0
And then, in the screen:

Code:
add test_image_ref at dissolve_t(1)

But that just solves part of the issue. That way, the test_image_ref will just dissolve the first time the screen is loaded. But every time the image changes, there is no dissolve effect.

I'm assuming that you want the dissolve effect every time you change the image.

I'm not sure if there is a proper way to do that, the best I could do was to hack a silly solution together. The screen will just add the dissolve effect to the image the first time the image is shown on screen. So, you need to re-add the image every time you change the variable so that the dissolve effect is reapplied.

The easiest way I found was to do a for loop:


Code:
label start:
    scene black
    call screen screen_example()

# using placeholders instead of images for this example
image image_one = Solid("#A22", xsize=100, ysize=100)
image image_two = Solid("#777", xsize=100, ysize=100)

default test_image_ref = "image_one"
default list_of_possible_images = ["image_one", "image_two"]

transform dissolve_t(t):
    alpha 0.0
    linear t alpha 1.0

screen screen_example():
    vbox:
        at truecenter
        spacing 50

        text "Screen add displayable crossfade test"


        ### I want to have a dissolve transition (or crossfade transform?) applied to just this image when the source displayable
        for i in list_of_possible_images:
            if test_image_ref == i:
                add test_image_ref at dissolve_t(1)

        textbutton "switch image":
            action CycleVariable("test_image_ref", list_of_possible_images)

        textbutton "Quit":
            action Return(True)
That does the trick. Don't know if there is a smarter way to go about it though.
 
  • Red Heart
Reactions: osanaiko

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,145
3,483
Thank you MrSilverLust !

I actually arrived at a similar solution:
Code:
label start:
    scene black
    call screen screen_example()

# using placeholders instead of images for this example
image image_one = Composite((100,100), (0,0), Solid("#555", xsize=100, ysize=100),  (15,0), Text("A", size=100, color="#55C"))
image image_two = Composite((100,100), (0,0), Solid("#555", xsize=100, ysize=100),  (15,0), Text("B", size=100, color="#C55"))
image background = Solid("#fff200", xsize=100, ysize=100)

default image_ref_A = "image_one"
default image_ref_B = "image_two"
default current_image = "A"

transform image_cross_fade:
    on show:
        alpha 0.0
        linear 0.5 alpha 1.0
    on hide:
        linear 0.5 alpha 0.0

screen screen_example():
    vbox:
        at truecenter
        spacing 50

        text "Screen add displayable crossfade test"

        fixed:
            xsize 100
            ysize 100

            add "background":
                    anchor (0.5, 0.0)
                    xpos 0.5
                    ypos 0

            showif current_image == "A":
                add image_ref_A at image_cross_fade:
                    anchor (0.5, 0.0)
                    xpos 0.5
                    ypos 0
            else:
                add image_ref_B at image_cross_fade:
                    anchor (0.5, 0.0)
                    xpos 0.5
                    ypos 0

        textbutton "switch image":
            action CycleVariable("current_image", ["A", "B"])

        textbutton "Quit":
            action Return(True)
It *does* do a crossfade... but it turns out that, as you can see if you run the example, that is not what i want!

The start and end images have grey base color with the letters. Behind them in the screen layout is another image that is solid yellow. This simulates the situation I'm seeing with actual images in my work-in-progress game.

At the midpoint of the alpha fade, both the old image and the new image are at 50% alpha, and the background is visible through them both. :mad:

1714468125193.png


The actual function that I want is a true Dissolve, which does a smooth RGB blend transition between the old and new pixels at 100% alpha.

I've done a lot of hunting through the renpy docs and various forums so far have not been able to find how to do the dissolve effect using ATL.

The *actual* Dissolve transition function in the Renpy sourcecode is implemented using a GL2 Shader (in renpy\common\_shaders.rpym) ... but getting that effect into a transform is beyond my skill level at the moment :cry:


!!! EDIT: I just found the "AlphaBlend" displayable in the docs. this might be the solution :KappaPride:!!!!
 
Last edited:

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,145
3,483
SUCCESS!!!

"Alphablend" as described in the docs was not clear at all, but the following Lemmasosft Forum post saved the day

By combining a fading transform with alphablend to create a displayable, and then using a "swap while not visible" technique to switch between two "add"ed images that are actually AlphaBlend displayables, we can get a working dynamic cross-dissolve between two arbitrary images inside a screen. Woot!

When implemented in my example the background "yellow" image is never visible through the old or new images during the transition.

Here's the code:

Code:
label screen_add_cross_fade_example_3:
    scene black
    call screen screen_example()

# using placeholders instead of images for this example
image image_one = Composite((100,100), (0,0), Solid("#555", xsize=100, ysize=100),  (15,0), Text("A", size=100, color="#55C"))
image image_two = Composite((100,100), (0,0), Solid("#555", xsize=100, ysize=100),  (15,0), Text("B", size=100, color="#C55"))
image background = Solid("#fff200", xsize=100, ysize=100)

default image_ref_A = "image_one"
default image_ref_B = "image_two"
default image_switch = "A"
default current_image = "image_one"

transform alphatransform(img, duration=1.0):
    img
    alpha 0.0
    linear duration alpha 1.0

init python:
    def switch_to_image(new_img):
        ab = AlphaBlend(alphatransform(current_image), current_image, new_img)
        if store.current_image == "A":
            store.image_ref_B = ab
            store.image_switch = "B"
            store.current_image = new_img
        else:
            store.image_ref_A = ab
            store.image_switch = "A"
            store.current_image = new_img

screen screen_example():
        vbox:
            at truecenter
            spacing 50
            text "Screen add displayable crossfade test"
            vbox:
                at truecenter
                fixed:
                    xsize 100
                    ysize 100

                    add "background":
                        anchor (0.5, 0.0)
                        xpos 0.5
                        ypos 0

                    showif image_switch == "A":
                        add image_ref_A:
                            anchor (0.5, 0.0)
                            xpos 0.5
                            ypos 0
                    else:
                        add image_ref_B:
                            anchor (0.5, 0.0)
                            xpos 0.5
                            ypos 0

            textbutton "set image one":
                action [ Function(switch_to_image, "image_one")]

            textbutton "set image two":
                action [ Function(switch_to_image, "image_two")]

            textbutton "Quit":
                action Return(True)
 
  • Like
Reactions: MrSilverLust