Ren'Py How to make an image (or images) move up as another appears in its place?

MissFortune

I Was Once, Possibly, Maybe, Perhaps… A Harem King
Respected User
Game Developer
Aug 17, 2019
4,941
8,066
It's kind of a tricky question to phrase. But basically, what I'm looking to do is have a series of tweets/social media posts pop up on the screen as the protagonist reads it. So, basically post #1 shows up on the screen > the player clicks > that first post moves upward and another takes its place. Think of it as something like a chat on Twitch. Here's a shitty image example:

1.png
2new.png

In essence, the tweets would be PNGs and would slide up with the behavior described above. I'm guessing this would either but done with ATL or NVL, but I'm not sure how I'd approach it with multiple images effectively interacting with each other in this way. A little guidance would be appreciated. :)
 

Winterfire

Forum Fanatic
Respected User
Game Developer
Sep 27, 2018
5,058
7,413
I'd use a vbox:


"This displays its children one above the other, in an invisible vertical box."

Which sounds about what you're looking for...
You could have an integer which increases everytime you want a new box to pop up, something akin to:


Python:
screen msgbox():
    vbox:
         if (i >= 1):
             #show box 1
         if (i >= 2):
             #show box 2
         (...)
Untested, and I don't remember if screens update automatically... if not, you can create a function (def) which does both increasing the integer AND recalling the screen so it updates.
 

Saint_RNG

Member
Game Developer
Apr 2, 2018
167
210
In my own use case I used something like this:
Python:
    show pic at fast_moveoutleft
    show pic2 at fast_moveinright
with transforms like this :
Python:
# custom moveoutleft (for image already in the center)
transform fast_moveoutleft:
    linear(.1) xpos -1440

# custom moveinright (for image on outside right of the screen)
transform fast_moveinright:
    xpos 2650
    linear(.1) xalign .5
pic is already displayed at the center of my screen before the code show pic at fast_moveoutleft.
This line first moves the image named pic (in the center) to the left outside of the screen, while at the same time it displays pic2 on the outside right of the screen, before moving it to where pic was just before.
pic and pic2 are PNG with the same resolution as the game (2560 x 1440).

In your case, I assume you'd rather work with ypos or yalign than with xpos/xalign. You can also replace linear with easein or easeout for a more 'stylish' look, if that makes sense.

And if I understand correctly, you don't want the first to leave the screen after the second is displayed, but I imagine you want to make a kind of upward increment as new images appear. Maybe it's possible to make this dynamic, or maybe you need to create a transform for each "level" (the resolution of your images must be the same every time, otherwise reuse could be a nightmare).

I also believe it's important not to forget to hide the image once it's been "taken" off the screen.

I'm not entirely sure that this is the best solution in your case, but you can always give it a try until someone more experienced than me comes up with something more suitable.


I'm guessing, but in your case it would look something like this :
Python:
    show pic at up_to_lvl1 # if you want the image to appear from below, otherwise you can just use with Dissolve(.5) or something equivalent
    pause()
    show pic at up_to_lvl2
    show pic2 at up_to_lvl1
    pause()
    show pic at up_to_lvl3
    show pic2 at up_to_lvl2
    show pic3 at up_to_lvl1
    pause()
    .
    .
    .
    hide pic # or a new scene statement should also work I think
    .
    .
    .
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,400
15,312
Both answers are valid, and both have flaws. So, it totally depend what you effectively intend to do.


I'd use a vbox:
That one will have the messages pill up, what would looks like the Twitter effect, but soon cover all the screen and more. What mean that you'll need a viewport to make the oldest ones disappear, or rely on something like:
Python:
screen whatever()

    default first = 0
    default count = 1

    vbox:
        for i in range( first, first + count ):
            add listOfImages[i]

    if first <= 10:
        textbutton "click for next":
            action If( last <= 5, SetScreenVariable( count, count+1 ), SetScreenVariable( first, first + 1 ) )
    else:
        textbutton "No more message":
            action Return()
As long as there's less than 6 messages displayed, you display one more message.
Once there's 6 messages displayed, you stick to the number of message displayed, and change the first of those message.
And there's the security net, only 16 messages are waiting. So, once all are displayed, you'll not anymore pass to the next one, but close the screen.

There's also an issue, message will be displayed from the top. What mean that when there's only one message, it will be displayed at ypos 100.
Then you'll add the second message, and the first one will still be at ypos 100, while the second will be at ypos 200.
And so on.

It's possible to make the first message be at the bottom, by changing the loop condition into
for i in range( first + count, first, -1 ):. What would goes, by example, from 5 to 1, instead of 1 to 5 as usual.

I kind of remember that it's also possible to make the vbox itself act differently, looking like the message are pilling up one on top of the other. But right now I can't search back how to do this.



In my own use case I used something like this:
Python:
    show pic at fast_moveoutleft
    show pic2 at fast_moveinright
with transforms like this :
Python:
# custom moveoutleft (for image already in the center)
transform fast_moveoutleft:
    linear(.1) xpos -1440

# custom moveinright (for image on outside right of the screen)
transform fast_moveinright:
    xpos 2650
    linear(.1) xalign .5
This one give the visual effect, but there's only one message visible at once.

It's possible to change this by having more than one transform, and so something like:
Python:
label whatever:
    show message1 at bottomSlot
    pause
    show message1 at moveToSlot1
    show message2 at bottomSlot
    pause
    show message1 at moveToSlot2
    show message2 at moveToSlot1
    show message3 at bottomSlot
with the transforms being:
Python:
transform bottomSlot:
    xpos 100
    ypos 1000

transform moveToSlot1:
    linear 1 ypos 800

transform moveToSlot2:
    linear 1 ypos 600
But it imply more coding.

There's another catch, the message will move thought the whole screen, what isn't necessarily what you seek for. Plus, if the messages are small and the screen is big, either it will feel long for the player, or the moving speed will be too high and it will not be really smooth.
But this too is solvable, this time with a transition:

Python:
label whatever:
    show message1 at bottomSlot
    pause
    show message2 with messageTransition( 2.0 )
With the transition being:
/!\ Warning, wrote on the fly /!\
Python:
transform bottomSlot:
    xpos 100
    ypos 1000

transform messageTransition( duration=1.0, new_widget=None, old_widget=None):
    delay duration

    # Display the previous image.
    contains:
        old_widget
        #  Normally those two lines are not needed, but I can't test right now, so I add them.
        # In the same time it can help understanding how it works since it give an actual position.
        xpos 100     
        ypos 1000
        #  Move it up half the height of a message, for half the transition duration.
        #  Yeah, I know, message need to have the same height, it's a annoying constraint.
        linear (duration/2) ypos 900
        # Then continue to move it up, while making it fade away.
        linear (duration/2) ypos 800 alpha 0.0

    # Concurrently display the next image.
    contains:
        new_widget
        #  Here the position *is* mandatory, because the image isn't displayed yet.
        xpos 100
        ypos 1200
        # Slowly move it up to its position.
        linear duration ypos 1000

    # Here's the real trick.
    contains:
        #  Display an image that is the bottom of the background.
        #  Like it's displayed *after* /new_widget/, it will hide it. At least, it will hide
        # the part of it that is still bellow "ypos 1200".
        "images/messages/bottomMask.png"
        xpos 100
        ypos 1200
In words, the transition will do this:
The message currently displayed will scroll up half of its size, then continue to move up will becoming more and more transparent.
In the same time, the next message will scroll up to its position.
Like there's a mask, it looks like the said next message appear pixel line by pixel line from nowhere.

From what I get of your post, MissFortune , it's something like this that you're seeking for.
So, just a last bit of code:

/!\ Warning, wrote on the fly /!\
Python:
define listOfMessage = [ "images/message/message1.png", "images/message/message2.png", "images/message/message3.png" ]

label showMessages:

    scene yourBackground

    pause # or not.

    #  The first message is particular.
    show expression listOfMessage[0]
    pause 2.0 # or whatever waiting method you want.

    #  No need to pollute the game space with such variable. Make it temporary.
    $ renpy.dynamic( i )
    $ i = 1

    #  As long as there's still a message to show
    while i < len( listOfMessage ):
       #  Display the next message with the right transition.
       #  Change the duration (2.0) with what feel the best.
       show expression listOfMessage[i] with messageTransition( 2.0 )
       #  One more message shown.
       $ i += 1
       pause 2.0 # or whatever waiting method you want.

    # It's done, all messages have been shown.