Ren'Py Creating custom scene function for auto-compositing images

slitherhence

Member
Sep 24, 2017
426
335
I am attempting to create a drop-in replacement for the renpy scene function. It needs to be capable of everything scene is... but it also needs to automatically "show" an additional image unique to each scene when certain criteria are met. The idea is that I can have an element of all scenes (such as a character's outfit) that can be changed by player choices yet is seamlessly displayed in each scene as if the scene had been rendered with that element.

Since every single render is going to have characters is different poses and positions within the scene... renpy's layered image features seem far too cumbersome to work here. If I understand it right, because I am not using sprites, I would need to write out a layered image definition for every single scene.

What I have doesn't work:

Python:
init python:
    def cscene(name, at_list=[], layer='master', what=None, zorder=0, tag=None, behind=[]):
        renpy.scene(layer)
        renpy.show(name, at_list, layer, what, zorder, tag, behind)
        if pc and not pc.outfit == 0:
            ofname = name + ' of' + str(pc.outfit)
            if renpy.loadable(ofname):
                renpy.show(ofname, at_list, layer, what, zorder, tag, behind)
        return
    config.scene = cscene
Code:
I'm sorry, but an uncaught exception occurred.

While running game code:
  File "renpy/common/00start.rpy", line 205, in script
    scene black
  File "game/script.rpy", line 10, in cscene
    renpy.show(name, at_list, layer, what, zorder, tag, behind)
AttributeError: 'NoneType' object has no attribute 'split'

-- Full Traceback ------------------------------------------------------------

Full traceback:
  File "renpy/common/00start.rpy", line 205, in script
    scene black
  File "renpy/ast.py", line 1279, in execute
    renpy.config.scene(self.layer)
  File "game/script.rpy", line 10, in cscene
    renpy.show(name, at_list, layer, what, zorder, tag, behind)
  File "renpy/exports.py", line 678, in show
    name = tuple(name.split())
AttributeError: 'NoneType' object has no attribute 'split'

Windows-10-10.0.19041
Ren'Py 7.4.4.1439
The Long Dark 0.1
Sun Jun  6 11:33:31 2021
Unfortunately I'm rather new to both python and renpy. I have quite a bit of experience with javascript, perl, and php... but not python.

I'm certainly open to a different approach. I don't particularly like overriding core functionality.
 

slitherhence

Member
Sep 24, 2017
426
335
I've made some progress on the code above.
Python:
init python:
    def cshow(name, at_list=[ ], layer=None, what=None, zorder=None, tag=None, behind=[ ], atl=None, transient=False, munge_name=True):
        renpy.show(name, at_list, layer, what, zorder, tag, behind, atl, transient, munge_name)
        if not pc_outfit == 0:
            ofname = ''.join([' '.join(name), ' of', str(pc_outfit)])
            if renpy.loadable(ofname): #need some way to turn ofname into a relative path with extension
                renpy.show(ofname, at_list, layer, what, zorder, tag, behind, atl, transient, munge_name)
        return
    config.show = cshow
Redefining the show statement instead of scene solved the prior problem. The main two stumbling blocks now are:
  1. `renpy.loadable()` needs a path relative tot he `game/` folder. And despite what the documentation says about `renpy.file()` it does too. I need someway to search for and find the file using the same methods renpy.show() does... then return a fully qualified path for loadable to use... all without displaying an error if the file doesn't exist (the whole point is to make sure it exists to avoid that problem, after all).
  2. using `renpy.show()` this way apparently clears the scene every time. Thus only the second `renpy.show()` matters... I need the second image to appear over the first as it would if I used a scene statement followed by a show statement normally.
 

slitherhence

Member
Sep 24, 2017
426
335
Here is the solution:

Python:
init python:
    def cshow(name, at_list=[ ], layer=None, what=None, zorder=None, tag=None, behind=[ ], atl=None, transient=False, munge_name=True):
        renpy.show(name, at_list, layer, what, zorder, tag, behind, atl, transient, munge_name)
        if not pc_outfit == 0:
            ofname = ''.join([' '.join(name), ' of', str(pc_outfit)])
            if renpy.has_image(ofname, True):
                renpy.show(ofname, at_list, layer, what, zorder, 'of', behind, atl, transient, munge_name)
        return
    config.show = cshow
Setting the tag argument on the second `renpy.show()` to 'of' solves the issue with the second `renpy.show()` hiding the first. And switching to `renpy.has_image()` has a number of benefits. Including searching for image in exactly the same manner as `renpy.show()`.

This is, thus far, a seamless in-place replacement for show, with the added benefit that it will automatically look for another file with the same name and " of<pc_outfit>" on the end and, if it finds it, displays that on top of the first image.

I'll probably end up expanding on this functionality with time... but it works.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,364
15,281
I am attempting to create a drop-in replacement for the renpy scene function.
Well, it's not surprising that it fail, since you are redefining a function that do not do what you think. As the documentation say, renpy.scene() only clear the screen. The scene statement is in fact a combination of it and renpy.show().


Python:
            ofname = ''.join([' '.join(name), ' of', str(pc_outfit)])
ofname = "{} of{}".format( name, pc_outfit )" is enough. name is a string, your join do nothing.


Setting the tag argument on the second `renpy.show()` to 'of' solves the issue with the second `renpy.show()` hiding the first.
Wouldn't it be a good start to search how show works before trying to redefine it ?
By default, Ren'py consider the first word of the image shown as the name of a sprite, and all the other words as attributes applied to it. Then, every time you are showing an image with a name already shown, Ren'py assume (with reason) that you are simply updating the sprite ; what imply that it replace the image shown by the new image. This being explained, differently, in the documentation ; in the "Getting started" part if my memory don't betray me.

This mean that when you are showing "girlName idle smile", Ren'py name the sprite "girlName". Then, when you try to show "girlName idle smile of1", it take this as a change in the sprite, and do what you seen: it replace the previous image by the new one.
And your fix works because, by giving a value to tag, you are giving a new name to the sprite.

This imply that you've now two sprites shown:
  • "girlName", that correspond to the image "girlName idle smile"
  • "of", that correspond to the image "girlName idle smile of1"

Like you are using the scene statement, your solution works, mostly because the statement clean the screen before everything else. But this is really dirty, due to the creation of a wild sprite, and also due to the fact that you don't redefine renpy.hide to get rid of it.


All this being said, it's a lot of works for something that could have just been:
Code:
label showEx( image ):
    scene expression image
    if pc_outfit != 0:
        show expression "{} of{}".format( image, pc_outfit )
    return

label whatever:
    call showEx( "girlName idle smile" )
 

slitherhence

Member
Sep 24, 2017
426
335
Never mind. After double checking, I've decided to go ahead and show you everything you have gotten wrong.

Well, it's not surprising that it fail, since you are redefining a function that do not do what you think. As the documentation say, renpy.scene() only clear the screen. The scene statement is in fact a combination of it and renpy.show().
I figured this out like... 9 hours before you replied? Even pointed out that I switched to redefining show instead in the second post above. I started learning renpy and python literally the night before. Soooo... *shrug* I made a mistake, caught the mistake, and moved on. Why are you still stuck on it?

ofname = "{} of{}".format( name, pc_outfit )" is enough. name is a string, your join do nothing.
Incorrect. `name` can be a string. In most cases it is a tuple. I'm aware of what the documentation says. The documentation is wrong. I already dealt with errors from renpy about me trying to concatenate a tuple and a "unicode". I could have concatenated tuples by making `pc_outfit` a tuple... but then that would fail when `name` is a string. So I concatenate `name`'s elements into a string and then concatenate that string with `pc_outfit`... which works in all cases.

I also actually read the show function in the renpy code. (and the one I'm using):
Python:
    if not isinstance(name, tuple):
        name = tuple(name.split())
It expects a tuple. And if you give it a string it turns it into a tuple.

Wouldn't it be a good start to search how show works before trying to redefine it ?
Yes. That's where I started... by reading the documentation and by reading the code for `renpy.show()`... but as I said I literally started learning renpy and python the night before. I also did this thing where I ask for help on several different forums. Strangely, people on other forums didn't feel a need to condescend to me about how I should learn how show works before redefining it. They seemed much more focused on actually helping me understand things.

By default, Ren'py consider the first word of the image shown as the name of a sprite, and all the other words as attributes applied to it. Then, every time you are showing an image with a name already shown, Ren'py assume (with reason) that you are simply updating the sprite ; what imply that it replace the image shown by the new image. This being explained, differently, in the documentation ; in the "Getting started" part if my memory don't betray me.
Literally. Just. Started. Learning. Renpy. And. Python. The. Night. Before. Yet you're still going out of your way to point out what a beginner mistake it is to not know about image tags. Except I do know about them. I knew about them when I was commenting on the first image being replaced. I simply didn't put two and two together till later and realize it was tags at work. Might have something to do with how I Literally. Just. Started. Learning. Renpy. And. Python. The. Night. Before. Again, dial the condescension back to an 8 or a 9 will ya?

Plus, I clearly already moved past this point. So again, why are you still stuck on it? Why are you focusing on mistakes I've already caught and overcome?

This mean that when you are showing "girlName idle smile", Ren'py name the sprite "girlName". Then, when you try to show "girlName idle smile of1", it take this as a change in the sprite, and do what you seen: it replace the previous image by the new one.
And your fix works because, by giving a value to tag, you are giving a new name to the sprite.
Yes. I know why my fix works. That's why I did that fix. Also... it's not a sprite.

This imply that you've now two sprites shown:
Tags. Not sprites. Images with tags. Yes, tags are often used to identify and group the components of a sprite. But a sprite is more than just a tagged image. Literally every image displayed with show or scene has a tag. Not all of them qualify as sprites.

And yes, showing two images at once is exactly the plan. Which requires that they have different tags.

Like you are using the scene statement, your solution works, mostly because the statement clean the screen before everything else. But this is really dirty, due to the creation of a wild sprite, and also due to the fact that you don't redefine renpy.hide to get rid of it.
I don't need to redefine `renpy.hide()` to get rid of the so-called "wild sprite". As you just said yourself that happens automatically when I use the scene statement. The unmodified `renpy.show()` works the same way... displaying an image until `renpy.scene()` clears the layer or another image with the same tag is shown.

And it literally says in the documentation that hide is not something most people have any reason to use. And if I did want to use `renpy.hide()` or the hide statement to clear the images I wouldn't need to modify it to do so. `renpy.hide()` has to be told what image to remove. It in any way and has no idea what images are currently displayed. So I'd simply call it twice, once for each tag. Or... yes... I could extend hide the same way so it would only require one hide statement. I could also add an else clause to automatically call `renpy.hide()` on the `outfit` tag when no outfit image is found or applicable... literally a two line edit. But I don't currently see a need for it... I call scene to display images and manage the window show/hide manually. If a need for using the show statement instead of the scene statement comes up and the `outfit`-tagged image is somehow an issue, I know what I need to do to fix it.

And stop this "wild sprite" nonsense. It's just an image. Like any other image displayed by `renpy.show()`. I asked in the renpy discord and no one has any idea what you are talking about.

All this being said, it's a lot of works for something that could have just been:
Code:
label showEx( image ):
    scene expression image
    if pc_outfit != 0:
        show expression "{} of{}".format( image, pc_outfit )
    return

label whatever:
    call showEx( "girlName idle smile" )
This won't even come close to working. You can't use `with`, `as`, `at` or anything else with `call` statements. I'd need to add arguments to let me tell show what transition, if any, to use. What translation, if any, to use. And on and on and on. And then the flow control to manage all of it. Basically reinventing the wheel for all the functionality renpy already handles with show and scene statements. Sounds like a lot more work than what I've already accomplished.

Frankly, this whole reply by you looks like the same kind of ego trip you went on the last time you "helped" me. It's just you lording your supposed knowledge over me and throwing shade at all my supposed failings. And, as before, you don't actually know as much as you think you do. Frankly I'd be a lot more willing to consider the possibility that you possess some secret knowledge that even the developers of Renpy lack if you weren't such a twat about it. Also if you weren't constantly wrong about so many things.
 
Last edited:

Deleted member 1121028

Well-Known Member
Dec 28, 2018
1,716
3,295
Never mind. After double checking, I've decided to go ahead and show you everything you have gotten wrong.



I figured this out like... 9 hours before you replied? Even pointed out that I switched to redefining show instead in the second post above. I started learning renpy and python literally the night before. Soooo... *shrug* I made a mistake, caught the mistake, and moved on. Why are you still stuck on it?



Incorrect. `name` can be a string. In most cases it is a tuple. I'm aware of what the documentation says. The documentation is wrong. I already dealt with errors from renpy about me trying to concatenate a tuple and a "unicode". I could have concatenated tuples by making `pc_outfit` a tuple... but then that would fail when `name` is a string. So I concatenate `name`'s elements into a string and then concatenate that string with `pc_outfit`... which works in all cases.

I also actually read the show function in the renpy code. (and the one I'm using):
Python:
    if not isinstance(name, tuple):
        name = tuple(name.split())
It expects a tuple. And if you give it a string it turns it into a tuple.



Yes. That's where I started... by reading the documentation and by reading the code for `renpy.show()`... but as I said I literally started learning renpy and python the night before. I also did this thing where I ask for help on several different forums. Strangely, people on other forums didn't feel a need to condescend to me about how I should learn how show works before redefining it. They seemed much more focused on actually helping me understand things.



Literally. Just. Started. Learning. Renpy. And. Python. The. Night. Before. Yet you're still going out of your way to point out what a beginner mistake it is to not know about image tags. Except I do know about them. I knew about them when I was commenting on the first image being replaced. I simply didn't put two and two together till later and realize it was tags at work. Might have something to do with how I Literally. Just. Started. Learning. Renpy. And. Python. The. Night. Before. Again, dial the condescension back to an 8 or a 9 will ya?

Plus, I clearly already moved past this point. So again, why are you still stuck on it? Why are you focusing on mistakes I've already caught and overcome?



Yes. I know why my fix works. That's why I did that fix. Also... it's not a sprite.



Tags. Not sprites. Images with tags. Yes, tags are often used to identify and group the components of a sprite. But a sprite is more than just a tagged image. Literally every image displayed with show or scene has a tag. Not all of them qualify as sprites.

And yes, showing two images at once is exactly the plan. Which requires that they have different tags.



I don't need to redefine `renpy.hide()` to get rid of the so-called "wild sprite". As you just said yourself that happens automatically when I use the scene statement. The unmodified `renpy.show()` works the same way... displaying an image until `renpy.scene()` clears the layer or another image with the same tag is shown.

And it literally says in the documentation that hide is not something most people have any reason to use. And if I did want to use `renpy.hide()` or the hide statement to clear the images I wouldn't need to modify it to do so. `renpy.hide()` has to be told what image to remove. It in any way and has no idea what images are currently displayed. So I'd simply call it twice, once for each tag. Or... yes... I could extend hide the same way so it would only require one hide statement. I could also add an else clause to automatically call `renpy.hide()` on the `outfit` tag when no outfit image is found or applicable... literally a two line edit. But I don't currently see a need for it... I call scene to display images and manage the window show/hide manually. If a need for using the show statement instead of the scene statement comes up and the `outfit`-tagged image is somehow an issue, I know what I need to do to fix it.

And stop this "wild sprite" nonsense. It's just an image. Like any other image displayed by `renpy.show()`. I asked in the renpy discord and no one has any idea what you are talking about.



This won't even come close to working. You can't use `with`, `as`, `at` or anything else with `call` statements. I'd need to add arguments to let me tell show what transition, if any, to use. What translation, if any, to use. And on and on and on. And then the flow control to manage all of it. Basically reinventing the wheel for all the functionality renpy already handles with show and scene statements. Sounds like a lot more work than what I've already accomplished.

Frankly, this whole reply by you looks like the same kind of ego trip you went on the last time you "helped" me. It's just you lording your supposed knowledge over me and throwing shade at all my supposed failings. And, as before, you don't actually know as much as you think you do. Frankly I'd be a lot more willing to consider the possibility that you possess some secret knowledge that even the developers of Renpy lack if you weren't such a twat about it. Also if you weren't constantly wrong about so many things.

lol golden troll :LOL:
If not, get some serious help.
 
  • Angry
Reactions: slitherhence