Ren'Py Randomized main menu backgrounds

SPkiller31

Member
Game Developer
Dec 20, 2020
122
150
Hi,

I'd like to add a semi-animated main menu that will display pairs of images chosen randomly from the collection.

A pair would consist of images that need to be displayed one after another to make sense.

I want RenPy to randomly select one of the pairs, display it, and then transition to another randomly chosen pair, but obviously, not the same one that was displayed a second ago.

I know that I can define a new menu that would become a basic animation consisting of images like:

Code:
image main_menu_test:
    "image1.png"
    pause 1
    "image2.png"
    pause 1
    repeat
But I never played with randomization besides basic 1-10/1-100 rolls etc.

My idea would be to define each pair as a separate animation and then throw them all into the same pool, prevent it from rolling the same animation that was just displayed, and then loop it but I can't get it to work.

Sorry if somebody had the same problem before, I couldn't find anything by searching for similar treads.
 
Aug 28, 2021
150
129
Hi,

I don't know Renpy conventions, functions or anything. Here some pseudocode:

Python:
class main_menu_display
# A data structure which allow to store pairs of String and retrieve with an index, here array within an array
   const img_array : Array = [
      [img1, img2]
      [img3, img4]
      [img5, img6]
]
# A simple but useful integer
   var previous_index : int

# A recursive function. Most of the time, it will return an integer and sometimes, 
# when your previous index match current index, the function will call itself until it find another index.
# One chance on nb of index. So no infinite loop, unless  you provide only one pair.
func pick_a_random_pair()
   var index = random(img_array.size())
   if index == previous_index:
      pick_a_random_pair()
   else: return index

  

func load_a_pair_of_images(idx : int):
   first_texture =  load(img_array[idx][0])
   second_texture =  load(img_array[idx][1])
If Renpy has a timer you can call load_a_pair_of_images(pick_a_random_pair()) every few seconds.
If it doesn't, maybe it got something which run on a frame based rate, you could count frames and call it every n*60 frames.

PS I use Python for formatting but it isn't python inspired. I only know gdscript.
 
  • Red Heart
Reactions: SPkiller31

guest1492

Member
Apr 28, 2018
321
269
Did this using screen language only, but you can probably define a python function and then call it as well. This code shows all the images in the list in a random order, before showing the whole list again in random order, etc.

This doesn't prevent situations where images are first shown in the order ...c1, c2, d1, d2 and then in the order d1, d2, a1, a2... (leading to seeing d1, d2 twice in a row).


Edited the code so now there is a "buffer" and a "queue" for images. I set the buffer as 4 images, but that can be changed. I noticed some weird shenanigans when I used on "show" action ... so that's been removed. I also noticed problems when I used a whole list of actions in the timer, so much of that has been moved into a python function instead.

(Likely explanation is because multiple actions mean multiple calls to renpy.restart_interaction which means running on "show" again and who knows what else to do with the screen.)

Python:
# define a function for use in the screen later
init python:
    def test_func(x, y, z):
        x.pop(0)
        x.append(y.pop(0))
        if not len(y):
            y += [img for pair in renpy.random.sample(z, len(z)) for img in pair if img not in x]

# define a 2d array/list of images to show
# you can also use names of predefined images instead
# you can have this as a screen variable instead as well
define test_list = [
    ('images/a1.png', 'images/a2.png'),
    ('images/b1.png', 'images/b2.png'),
    # ...
    ('images/z1.png', 'images/z2.png'),
]

# used test_screen instead of main_menu because it was easier for testing
screen test_screen():
   # buffer will hold 2 pairs of images (so 4 images total), change the '2' to whatever you want
    default buffer = [img for pair in renpy.random.sample(test_list, 2) for img in pair]
    default queue = [img for pair in renpy.random.sample(test_list, len(test_list)) for img in pair if img not in buffer]

    add buffer[0]

    # adjust the interval as you wish
    timer 2.0 action [
        Function(test_func, buffer, queue, test_list),
        With(dissolve),
    ] repeat True

    # for testing purposes
    frame:
        background '#00000080'
        padding 8, 8
        has vbox
        hbox:
            text 'Buffer: ' color '#FFF'
            text '\n'.join(buffer) color '#FFF'
        hbox:
            text 'Queue: ' color '#FFF'
            text '\n'.join(queue) color '#FFF'

    button:
        background '#000'
        padding 8, 8
        align 1.0, 1.0
        text 'Exit'
        action Return()

label start:
    'Begin Test'
    call screen test_screen
    'End Test'
    return
 
Last edited:
  • Red Heart
Reactions: SPkiller31

Ryunocore

New Member
Jul 2, 2022
6
2
A very cheap/easy way to prevent such situations would be to keep track of the last x pairs and comparing them to a "tentative" shuffle before "resolving" if we keep or mulligan it. What we lose out on true randomness, we gain on quality of selection.
 
  • Like
Reactions: guest1492

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,222
3,742
Nearly all of your requirements are achievable using pure Renpy ATL, no need for complex python solutions.

WARNING: Written from my head without using renpy tools, might have syntax errors.

I've assumed if you've got pairs of images with code "a", "b, "c", the names for display are "img {code} 1" and "img {code} 2"

* "choice" will make a random selection from among the sequential "choice" options.
* within each choice: use alpha fade and pause to show images {code} 1 and then {code} 2
* then pause a bit, until repeat to the start again.

You could "show" this displayable on top of whatever background you want on the main menu, then hide when a button is clicked.

The only downside is there is no protection against the same pair being shown twice in a row. But if you have lots of image pairs it won't happen very often.

Code:
image main_menu_images:
    choice:
        alpha 0.0
        "img a 1"
        linear 0.25 alpha 1.0
        pause 0.5
        linear 0.25 alpha 0.0
        "img a 2"
        linear 0.25 alpha 1.0
        pause 0.5
        linear 0.25 alpha 0.0
    choice:
        alpha 0.0
        "img b 1"
        linear 0.25 alpha 1.0
        pause 0.5
        linear 0.25 alpha 0.0
        "img b 2"
        linear 0.25 alpha 1.0
        pause 0.5
        linear 0.25 alpha 0.0
    choice:
        alpha 0.0
        "img c 1"
        linear 0.25 alpha 1.0
        pause 0.5
        linear 0.25 alpha 0.0
        "img c 2"
        linear 0.25 alpha 1.0
        pause 0.5
        linear 0.25 alpha 0.0

    # etc etc for more image pairs

    pause 1.0
    repeat
 
Last edited:
  • Red Heart
Reactions: SPkiller31

SPkiller31

Member
Game Developer
Dec 20, 2020
122
150
For anybody having a similar problem in the future:
I ended up using osanaiko's code as it seemed most foolproof and easiest to modify.

After experimenting a bit with options I decided to use dissolve with default "1" timer instead of alpha and linear to make transitions a bit smoother to my liking. (At least seems like it didn't break anything.)

Pause 4.0 or 3.5 seems like a sweet spot to make the image last long enough to "see" what is happening but not to stay long enough to become boring.

4 and more pairs seem to be enough to avoid repeating the same pair, or the player would need to be really unlucky.

Python:
define gui.main_menu_background = "main_menu_images
image main_menu_images:
    choice:
        "images/test1.webp" with Dissolve(1.0)
        pause 4
        "images/test2.webp" with Dissolve(1.0)
        pause 4
    choice:
        "images/test3.webp" with Dissolve(1.0)
        pause 4
        "images/test4.webp" with Dissolve(1.0)
        pause 4

#etc,etc
#very short pause between resets
    pause 0.5
    repeat