Ren'Py Variables: variable not defined & image not found [Solved]

Lou111

Active Member
Jun 2, 2018
543
686
I got problems. Ren'Py doesn't care about my variables and I can't seem to call a video with a variable.

Some Poopy is defined in some python code:

Python:
python:
    total_count = len(big_list)
    random_number = renpy.random.randint(0, (total_count -1))
        if click_type == "whatever" and len(big_list) > 0:
            random_other_number = renpy.random.randint(0, (other_image_count -1))
            Poopy = big_list[random_other_number]
        else:
            Poopy = big_list[random_number]
Poopy is a .webp file name and path name.
Now check this out:

Python:
image game_video = Movie(size=(1920,1080), channel="movie", play="[Poopy]" , loop=True)
show game_video
"The variable Poopy doesn't exist"... like for real? Cause I think it does.
Cause I've been using it successfully until this one line of movie code.
I'm missing some very, very minor detail here and I'm out of gas. I went back and made some functions which required functions which required me to redo almost all of my code in order to use:

Python:
default Poopy = big_list[random_number]
Which returned "Random number is not defined"... turns out that every variable in my code needed another variable to be "defined" in such a way that would please Goddess Ren'Py and I'm out of gas. But that isn't what defeated me... THIS did:

Python:
image game_video = Movie(size=(1920,1080), channel="movie", play="images/movie folder/movie.webp" , loop=True)
show game_video
It works!!!

Python:
default game_video = "images/movie folder/movie.webp"
image game_video = Movie(size=(1920,1080), channel="movie", play="[game_video]" , loop=True)
show game_video
Image 'game_video' not found... like for real.
That means I wasted my entire day and I don't know what the word variable means. I don't even know what my question is anymore.

I want the variable to be defined in a python block and use it to show a video... I think that's all I need. Maybe a link to a "What variables really are in Ren'Py" video too. (That wasn't sarcasm)
Thanks Team
 
Last edited:
Apr 24, 2020
192
257
As far as I'm aware, things like image gets set in stone, so to speak, when the game starts and can't be changed dynamically. You need to create an image for each video and then randomly pick which video to play.

I think it will look something like this:
Python:
image game_video_1 = Movie(size=(1920,1080), channel="movie", play="images/movie folder/movie_1.webp" , loop=True)
image game_video_2 = Movie(size=(1920,1080), channel="movie", play="images/movie folder/movie_2.webp" , loop=True)
image game_video_3 = Movie(size=(1920,1080), channel="movie", play="images/movie folder/movie_3.webp" , loop=True)

label playVideo:
    python:
        total_count = len(big_list)
        random_number = renpy.random.randint(0, (total_count -1))
            if click_type == "whatever" and len(big_list) > 0:
                random_other_number = renpy.random.randint(0, (other_image_count -1))
                Poopy = big_list[random_other_number]
            else:
                Poopy = big_list[random_number]
    $ renpy.show(Poopy)
 

Lou111

Active Member
Jun 2, 2018
543
686
As far as I'm aware, things like image gets set in stone, so to speak, when the game starts and can't be changed dynamically. You need to create an image for each video and then randomly pick which video to play.

I think it will look something like this:
Python:
image game_video_1 = Movie(size=(1920,1080), channel="movie", play="images/movie folder/movie_1.webp" , loop=True)
image game_video_2 = Movie(size=(1920,1080), channel="movie", play="images/movie folder/movie_2.webp" , loop=True)
image game_video_3 = Movie(size=(1920,1080), channel="movie", play="images/movie folder/movie_3.webp" , loop=True)

label playVideo:
    python:
        total_count = len(big_list)
        random_number = renpy.random.randint(0, (total_count -1))
            if click_type == "whatever" and len(big_list) > 0:
                random_other_number = renpy.random.randint(0, (other_image_count -1))
                Poopy = big_list[random_other_number]
            else:
                Poopy = big_list[random_number]
    $ renpy.show(Poopy)
Thank you for the reply! If that is true then it will not work in my game because it relies entirely on files with undetermined file names... I'm both relieved and heartbroken. :oops: thank you friend.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,607
2,256
Can you not simply use:

Python:
    $ renpy.movie_cutscene(Poopy)

I appreciate that requires the video to play in full before the game continues (or the player clicks through it). But it will accept a variable to play.
 
  • Thinking Face
Reactions: Lou111

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,188
I got problems. Ren'Py doesn't care about my variables and I can't seem to call a video with a variable.
Because the variable isn't changed.

You are in a Python block, therefore all variables you'll create will be local to this Python block.
Prefix the variable with the name of Ren'py store, that is store and then you'll effectively change the value of the Ren'py variable named Poppy.

Python:
default Poopy = None     # Add this a 0 level of indentation to declare the variable.

python:
    total_count = len(big_list)
    random_number = renpy.random.randint(0, (total_count -1))
        if click_type == "whatever" and len(big_list) > 0:
            random_other_number = renpy.random.randint(0, (other_image_count -1))
            store.Poopy = big_list[random_other_number]  # <-- Correct this.
        else:
            store.Poopy = big_list[random_number]   # <-- And correct that.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,607
2,256
Messing around with the edges of this problem...

You can create a series of displayables, which is based on your list of video stored in big_list.
Then show one of those displayables at random.

Python:
define big_list = ["anne_vid1", "anne_vid2", "anne_vid3"]

init python:

    for i in big_list:
        setattr(store, i, Movie(play="/movies/{}.webm".format(i), channel="movie"))

label start:

    scene expression getattr(store, renpy.random.choice(big_list)) with dissolve
    "Line of dialogue"

The filename is built dynamically based on the list and getattr() is used to get the Movie() reference rather than the string variable that contains that name.

This is essentially what RenPy does automatically to other image types. It's just doing the same for specifically named .webm files.

If you need to show show rather than scene, it would look something more like this...

Python:
define big_list = ["anne_vid1", "anne_vid2", "anne_vid3"]

init python:

    for i in big_list:
        setattr(store, i, Movie(play="/movies/{}.webm".format(i), channel="movie"))

label start:

    scene black with fade
    show expression getattr(store, renpy.random.choice(big_list)) as tmpmovie with dissolve
    "Line of dialogue"
    hide tmpmovie

    # onward...

Using as to make it practical to hide the movie after it's no longer required.


I'm seriously distracted with RL right now, otherwise I would have tried to match it more to your original example.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,188
Python:
   show expression getattr(store, renpy.random.choice(big_list)) as tmpmovie with dissolve
Because image create its entries outside of the store, I knew that something like
Code:
default myMovie = Movie( play="whatver.webm" )

label whatever:
    show myMovie
will not works. But I never thought to use show expression store.myMovie, or your getattr version, to make it works.

What mean that, if you don't need to define the channel (because there's no sound, or because you let Ren'py deal with this), you can even directly go for:
Code:
    show expression Movie( play="/movies/{}.webm".format( renpy.random.choice(big_list) ) )  as tmpmovie with dissolve
But I don't guaranty the impact on the performance.
The creation of the object will be atomic, but it will probably mess with the cache. Therefore the use of the property is recommended in such case, in order to have a starting frame display as static until the movie is fully loaded.
 

Lou111

Active Member
Jun 2, 2018
543
686
You are in a Python block, therefore all variables you'll create will be local to this Python block.
That's the way I understood it. Whenever I came across "variable is not defined" I defined it on a variable label before the start of the game and it seemed to solve everything. The reason this confused me so much is that with the variable defined in a python block at start I could manipulate it with python functions THEN use it with
Python:
call screen main_screen(variable)
No problems, ever. (even loading saves) Only when I tried calling the variable with movies did I run into issues. It is so troubling because I fear that the way I define variables will be very detrimental to me in the future if I don't get a better understanding of this.

Can you not simply use:
Python:
$ renpy.movie_cutscene(Poopy)
I tried this first to see if it could be that simple. The video utterly consumed the screen and even covered my background and other screen()s.

init python: for i in big_list: setattr(store, i, Movie(play="/movies/{}.webm".format(i), channel="movie"))
Beautiful. This is exactly what the doctor ordered. Never in all my years would I have figured that out on my own...

I spent all day repairing my code back to a state where I could even check to see if this would work. I apologize for the delay. Here's what we got:

Make the list:
Python:
init python:
    def Refresh_Image_List():
        image_list = []
        for f in renpy.list_files():  # The O'nymous Loop
            if not f.startswith("images/Your Image Folder/"): continue
            n, e = os.path.splitext(os.path.basename(f))
            if not e.lower() in [ ".jpg", ".jpeg", ".png", ".webp", ".webm", ".ogv" ]: continue
            image_list.append(f)  #Add all compatible images and videos
            if e.lower() in [ ".webm", ".ogv" ]:  # I know there are more compatible extensions, will add later
                setattr(store, f, Movie(play="{}".format(f), channel="movie"))  # The 79 Special Flavor
        for i in trash_list:
            if i in image_list:
                image_list.remove(i)  #  This removes from the list if player removes it later
        return image_list

python:
     image_list = Refresh_Image_List()  #  List is generated/refreshed
Calling a file from the list:
Python:
# Using renpy.random.randomchoice(7000_index_list) returned really bad results
# images often repeated themselves and otherwise would randomly choose between maybe 7 images...
# So I, instead, used a random integer based on the length of the list and used it as the index

python:
    total_image_count = len(image_list)
    random_number = renpy.random.randint(0, (total_image_count -1))
    shown_image = image_list[random_number]
Using the file:
Python:
# ----- IF IMAGE -----
call screen main_screen(shown_image)

screen main_screen(shown_image):
    use random_image(shown_image) # One of the several screens used by main_screen

# I'm pretty sure I should put the if statement before even using the screen...
screen random_image(shown_image):
    $ (n, e) = os.path.splitext(os.path.basename(shown_image))
    if e.lower() not in [ ".webm", ".ogv" ]:  # AKA not animation
        add Resize(shown_image):  #  Resize is a class that resizes the image
            xalign 0.5
            yalign 0.5
            if PP.show_UI:
                xoffset -100  # Offsets in REAL TIME when the UI screen is hidden by player
                
# ----- IF MOVIE -----
python:
    if Is_Animated(shown_image):
            if PP.show_UI:           
                show expression Movie(play="{}".format(shown_image)) as tmpmovie:
                    xalign 0.5
                    yalign 0.5
                    # Cannot put if statement here
                    xoffset -100  # Does NOT offset in REAL TIME
            else:
                show expression Movie(play="{}".format(shown_image)) as tmpmovie:
                    xalign 0.5
                    yalign 0.5
                
call screen main_screen(shown_image)
show expression getattr(store, renpy.random.choice(big_list)) as tmpmovie with dissolve
show expression Movie( play="/movies/{}.webm".format( renpy.random.choice(big_list) ) ) as tmpmovie with dissolve
I can't say I knew which one was better. They both looked the same to me. However, I just "assumed" that getattr() takes longer than Movie(). That's what my normal person brain said.

As far as I know everything is working as intended. The only things standing out to me is the fact that I was able to offset images when the player UI was shown/hidden in real-time while the video sits there and looks tacky. Furthermore, I feel like the videos shown are borderless and uncontrolled, I have been trying to attach the show expression to a screen() with no success. Google searches for videos within screens return False.
I will continue to study this topic and try to figure it out but after I saw your responses when I woke up I've been coding ever since. I need a shower.

InCompleteController 79flavors anne O'nymous
Thank you so much my friends for your valuable time.
 
  • Like
Reactions: anne O'nymous

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,188
I can't say I knew which one was better. They both looked the same to me. However, I just "assumed" that getattr() takes longer than Movie(). That's what my normal person brain said.
Well, sorry, but it's the opposite.

If I left apart the randomization and store (implicit/explicit), because they are exactly the same in both version, in my version, Python will use an implicit getattr to find Movie, then proceed to the creation of the object. This while in 79flavors version, Python will use an explicit getattr to find an object already created. Therefore, his version is the fastest one ; mine take the same time + the time needed to create the object.

This being said, the difference is near to few micro seconds, if not nano seconds, so totally insignificant. At this level, what matter isn't which one is the fastest, but which one you understand the most.
In fact, it's what always matter. Despite Python being one of the slowest script language, there's really few case where your code can lead to Ren'py significantly slowing down. And 95% of those cases are due to some code put inside a screen.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,188
I had no reservations about putting code in screens and knowing this for future reference is awesome.
Well, you can put code in a screen, you just have to remember that it will be proceeded each time the screen will be refreshed, and Ren'py refresh rate can goes near to 50Hz (50 times by second).

In normal use, a screen will be refreshed once by interaction.
Code:
default counter = 0

screen countMe:
    $ counter += 1
    text "This screen have been refreshed [counter] times."

label start:
    show screen countMe
    "This is just an example"
    menu:
        "choice 1":
            pass
        "choice 2":
            pass
    "Obviously it's better when there's real code behind"
    "But it's just a demonstration"
    "Sorry, it's the end"
    return
But if there's a button with a hovered property, it will also be refreshed each time the said button is hovered.
Code:
default counter = 0

screen countMe:
    default hovered = 0
    $ counter += 1
    text "This screen have been refreshed [counter] times, and the button hovered [hovered] times."
    textbutton "close" xalign 0.5 yalign 0.5 action Return() hovered SetScreenVariable( "hovered", hovered + 1 )

label start:
    call screen countMe
    "END"
Move the cursor around the button, and look at the counters increasing.
Note that there's a refresh more than hovering, because it was kind of "refreshed" when the screen was shown for the first time.

Many games offering a free roaming option have this kind of screens. Many buttons, many hovered properties, and so many reasons for Ren'py to refresh the screen and proceed the code in it once again.
There's other cases, but globally it still follow the same logic. Each time Ren'py consider that it's the start of what it call "an interaction", then the screen will be refreshed ; and so every Python code in it will be proceeded again.

And finally, just for the fun, the 50Hz refresh rate.
Code:
default counter = 0

screen countMe:
    default tick = 0
    default seconds = 0
    $ counter += 1
    text "This screen have been refreshed [counter] times in [seconds] seconds. ticks [tick]"

    timer 0.02 repeat True action SetScreenVariable( "tick", tick + 0.02 )
    timer 1.0 repeat True action SetScreenVariable( "seconds", seconds + 1 )

label start:
    show screen countMe
    "END"
If you play a movie with sound while the screen is showing, you'll need to wait around 3 minutes before you reach 1 seconds of difference between the ticks and the effective seconds (perhaps more, my computer is doing something right now, what make the CPU at 70% average charge). Not bad for a game engine that some claim to be extremely slow.

But obviously, once again if there's Python code in your screen, the performance will drop, because Ren'py will have to proceed it each time it refresh the screen.
Therefore, if you need to do some pre-processing that will give a constant result, the trick is to assign the result to a defaulted variable. As you can see in my examples, the default screen statement is proceeded only when the screen is shown, what mean that your own code will also be proceeded only once :
Code:
init python:
    def myPreProcessing():
        [do whatever you need]

screen myScreen:
     default doNotMatter = myPreProcessing()
And, if your pre-processing is here to compute values that will be used by the screen, make your function return an tuple with those values (or an object hosting those values):
Code:
init python:
    def myPreProcessing():
        [do whatever you need]
        return ( value1, value2, value3, value4 )

screen myScreen:
     default values = myPreProcessing()

     if values[0] == "something":
         text "blabla"

     if value[1] == "otherthing":
         textbutton ( "{}".format( value[2] ) ):
             action Return( value[3] )
 

Lou111

Active Member
Jun 2, 2018
543
686
Well, you can put code in a screen, you just have to remember that it will be proceeded each time the screen will be refreshed, and Ren'py refresh rate can goes near to 50Hz (50 times by second).

In normal use, a screen will be refreshed once by interaction.
Code:
default counter = 0

screen countMe:
    $ counter += 1
    text "This screen have been refreshed [counter] times."

label start:
    show screen countMe
    "This is just an example"
    menu:
        "choice 1":
            pass
        "choice 2":
            pass
    "Obviously it's better when there's real code behind"
    "But it's just a demonstration"
    "Sorry, it's the end"
    return
But if there's a button with a hovered property, it will also be refreshed each time the said button is hovered.
Code:
default counter = 0

screen countMe:
    default hovered = 0
    $ counter += 1
    text "This screen have been refreshed [counter] times, and the button hovered [hovered] times."
    textbutton "close" xalign 0.5 yalign 0.5 action Return() hovered SetScreenVariable( "hovered", hovered + 1 )

label start:
    call screen countMe
    "END"
Move the cursor around the button, and look at the counters increasing.
Note that there's a refresh more than hovering, because it was kind of "refreshed" when the screen was shown for the first time.

Many games offering a free roaming option have this kind of screens. Many buttons, many hovered properties, and so many reasons for Ren'py to refresh the screen and proceed the code in it once again.
There's other cases, but globally it still follow the same logic. Each time Ren'py consider that it's the start of what it call "an interaction", then the screen will be refreshed ; and so every Python code in it will be proceeded again.

And finally, just for the fun, the 50Hz refresh rate.
Code:
default counter = 0

screen countMe:
    default tick = 0
    default seconds = 0
    $ counter += 1
    text "This screen have been refreshed [counter] times in [seconds] seconds. ticks [tick]"

    timer 0.02 repeat True action SetScreenVariable( "tick", tick + 0.02 )
    timer 1.0 repeat True action SetScreenVariable( "seconds", seconds + 1 )

label start:
    show screen countMe
    "END"
If you play a movie with sound while the screen is showing, you'll need to wait around 3 minutes before you reach 1 seconds of difference between the ticks and the effective seconds (perhaps more, my computer is doing something right now, what make the CPU at 70% average charge). Not bad for a game engine that some claim to be extremely slow.

But obviously, once again if there's Python code in your screen, the performance will drop, because Ren'py will have to proceed it each time it refresh the screen.
Therefore, if you need to do some pre-processing that will give a constant result, the trick is to assign the result to a defaulted variable. As you can see in my examples, the default screen statement is proceeded only when the screen is shown, what mean that your own code will also be proceeded only once :
Code:
init python:
    def myPreProcessing():
        [do whatever you need]

screen myScreen:
     default doNotMatter = myPreProcessing()
And, if your pre-processing is here to compute values that will be used by the screen, make your function return an tuple with those values (or an object hosting those values):
Code:
init python:
    def myPreProcessing():
        [do whatever you need]
        return ( value1, value2, value3, value4 )

screen myScreen:
     default values = myPreProcessing()

     if values[0] == "something":
         text "blabla"

     if value[1] == "otherthing":
         textbutton ( "{}".format( value[2] ) ):
             action Return( value[3] )
Another lesson bookmarked for reference! I can't wait for the day I read this with my coffee, nodding "of course of course" ahahaha stoked! I can't wait to show you what I'm going to do with this information :LOL: Thank you so much
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,607
2,256
Never in all my years would I have figured that out on my own...

Me neither.

This was me experimenting with google search results from the Lemmasoft forums.
I played around with various solutions and ideas. init -1 python, default my_vid_file = and all sorts of quick tests.

If I'm honest, I still don't understand quite how it's working. I thought I did at one point... and then I tried to refine it... and it broke. At that point, I realized I was misunderstanding something but came to the opinion that a solution I didn't understand but worked was "good enough"... so I posted it.

Edit:
Code:
    show expression Movie( play="/movies/{}.webm".format( renpy.random.choice(big_list) ) )  as tmpmovie with dissolve

And this is almost what my "refined" version looked like and I couldn't get it to work without preloading things during the init: phase. That's the point when I threw in the towel. 'Tis nice to see I was close though, even if I was in mode.

btw. the with dissolve is my standard solution to the blank screen/frame problem people get into with .webm movies. The transition ensures that the previous image is not removed before the video starts playing... and while there is a bit of overlap for a fraction of a second, it's never been enough for me to care about when the alternative is so obnoxious.

Now... fingers crossed for a day that doesn't include a phone call just as I sit down to work on the bigger task I've been trying to get done for the last two weeks.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,188
If I'm honest, I still don't understand quite how it's working. I thought I did at one point... and then I tried to refine it... and it broke. At that point, I realized I was misunderstanding something but came to the opinion that a solution I didn't understand but worked was "good enough"... so I posted it.
Before writing what I said regarding the screens refresh and what it imply for Python code embedded in screens, I past at least two weeks desperately trying everything, even the stupid ones, to make my Extended Variable Viewer not lag too much when displaying too many information. And truth be told, so far I only came with a work around ; remove the hovering when there's too many information.

Readers, never be afraid to fail. It's not a sign of weakness, it will teach you a lot, in first place it will teach you how to not fail the next time. Try to make your idea come true, don't hesitate to search if someone have had the same problem, and if really you struggle with it, don't hesitate to ask.
And never forgot that it's not because someone will come with the answer, that it's something obvious. It happen, probably more often than you can imagine, that we didn't knew the answer. What we have is just a better knowing regarding how to find it, and a lot of curiosity that made us want to know the answer ; this even if it's something we will never use ourselves.

had a quote precisely for that few days ago:
"The best learners are the people who push through the discomfort of being objectively bad at something." – Tommy Collison


And this is almost what my "refined" version looked like and I couldn't get it to work without preloading things during the init: phase. That's the point when I threw in the towel. 'Tis nice to see I was close though, even if I was in mode.
Every coder I know have done, is doing, and will do, . And sometimes, for a given task it will stay Voodoo, because it's somehow too confusing and we don't achieve to understand what's behind.
Anyway, you goes further than me on this. It's because you almost came to this, that the idea to see if this works crossed my mind.

I guess it come from the fact that show expect directly a displayable.

When you write show myMovie, behind the scene what Ren'py pass to the "execute code" is the string "myMovie", what imply that there's an equivalent to getattr( somewhere, "myMovie" ) somewhere in the process ; what provide to show the displayable it need. But the "somewhere" is not store, what make displayable defined otherwise than with image fail.
Your expression getattr( [...] ) past the getattr from implicit to explicit, removing the "somewhere" problem. Then, I wondered if, in this case, any expression that return a displayable would works as fine ; and it was.


btw. the with dissolve is my standard solution to the blank screen/frame problem people get into with .webm movies. The transition ensures that the previous image is not removed before the video starts playing...
Did your tests told you if the blank frame happen even when you use the first_image attribute of Movie and/or, did the with dissolve trick works with it ?


Now... fingers crossed for a day that doesn't include a phone call just as I sit down to work on the bigger task I've been trying to get done for the last two weeks.
I wish you good luck.
 

Lou111

Active Member
Jun 2, 2018
543
686
You guys are awesome.
This was me experimenting with google search results from the Lemmasoft forums.
What we have is just a better knowing regarding how to find it
I think this is the most frustrating part for me. It isn't trying to solve the riddle in my code or that I lack the capability, it's that I know the answers are out there and I can't find the words to search for them.
I was in mode.
Your spaghetti noodle transformed my first project into a whole new animal. The idea of enabling the player to modify content is critical in my next project. A "true" game that embraces my ideal of putting the player first which includes the ability to apply content with no hassle. This changes everything.
the with dissolve is my standard solution to the blank screen/frame problem people get into with .webm movies.
I'll have to test this further and I will keep this fix in mind. The issue I was having with the dissolve was that it made all my screens dissolve with it, looked very intrusive. If the dissolve only dissolved the video I would love to use it. Perhaps there is an alternative? Maybe I can just make the dissolve a fraction of a second or something...