Ren'Py Creating a "Build Your Scene" menu - Not able to reset characters in the new screen [SOLVED]

grima_grima

Verified Developer
Game Developer
Nov 24, 2021
495
1,824
Hi All!

I am creating a "Build Your Scene" menu for my game (Girl In Charge):
1666576851494.png


1) When you click the menu, this screen is displayed, where you can select which background you want:
1666576991346.png


2) When you select the background, this new screen is displayed, where you select which character you want to add to the scene:
1666577132102.png


3) When you select your desired character, this new screen is displayed, where you can setup the character (flip, select face and clothes):
1666577363146.png


4) When you are done and click the character image, you have to select which position the character will be placed in the screen:
1666577512731.png


5) After this selection, you can choose to add more chars or not (up to 5 chars per scene):
1666577662560.png


6) If you select "Yes", you go back to step 2 (any characters that were already select will be unavailable). If you select "No", the next screen is displayed, where you select who is the speaker on the scene you are creating:
1666577823060.png


7) You can select one of the characters you've added to the scene or "Someone else" (where you can type the character name). I selected "Vanessa" and this screen is presented, where you can type the text she will speak:
1666578013451.png


8) After you type your text and hit "Enter", you will see the scene you just built. So far, so good:
1666578122559.png

9) If you click anywhere, you'll get the option to modify the text or to close the scene. All this is working fine.

PROBLEM: I am not able to reset the characters when I enter the screen again. I use the same character images used in the game itself, but if I try to reset them to the initial state from inside the screens, I'm not able to modify the characters anymore. Does anyone has any idea? I can share pieces of the code if needed.


Sample character images for Victoria:
Python:
# Victoria
image victoria_unflipped = Composite(
    (640, 1080),
    (0, 0), ConditionSwitch(# Hair / Body
        "vi.hair == '01'", "actors/vi/vi_hair_01.webp",
        "True", "actors/vi/vi_hair_01.webp" #shows the default naked body
    ),
    (0, 0), ConditionSwitch(# Face
        "vi.face == 'happy'", "actors/vi/vi_happy_01.webp",
        "vi.face == 'shock'", "actors/vi/vi_shock_01.webp",
        "vi.face == 'smirk'", "actors/vi/vi_smirk_01.webp",
        "True", Null() #shows default face, from the body image
    ),
    (0, 0), ConditionSwitch(# Clothing
        "vi.clothes == 'bar'", "actors/vi/vi_bar_01.webp",
        "vi.clothes == 'dress01'", "actors/vi/vi_dress_01.webp",
        "True", Null() #no clothes
    )
)
image victoria_flipped:
    "victoria_unflipped"
    xzoom -1.0 # flips sprite
    #xpos 0.25 # shifts flipped sprite, if needed

image victoria = ConditionSwitch("vi.flipped == 'N'", "victoria_unflipped", "vi.flipped == 'Y'", "victoria_flipped")
image crop_victoria_unflipped = Crop((120, 30, 400, 320), "victoria_unflipped")
image crop_victoria_flipped:
    "crop_victoria_unflipped"
    xzoom -1.0 # flips sprite
image side victoria = ConditionSwitch("vi.flipped == 'N'", "crop_victoria_unflipped", "vi.flipped == 'Y'", "crop_victoria_flipped")
Person class:
Python:
    class Person:
        def __init__(self, character, name, hair_list, face_list, clothes_list, tool_list):
            self.c = character
            self.name = name
            self.flipped = "N"
            self.hair_list = hair_list
            self.hair = hair_list[0]
            self.face_list = face_list
            self.face = face_list[0]
            self.clothes_list = clothes_list
            if clothes_list <> "":
                self.clothes = clothes_list[0]
            else:
                self.clothes = ""
            self.tool_list = tool_list
            if tool_list <> "":
                self.tool = tool_list[0]
            else:
                self.tool = ""

        def initChar(self):
            self.flipped = "N"
            self.hair = self.hair_list[0]
            self.face = self.face_list[0]
            if self.clothes_list <> "":
                self.clothes = self.clothes_list[0]
            else:
                self.clothes = ""
            if self.tool_list <> "":
                self.tool = self.tool_list[0]
            else:
                self.tool = ""
            return

        def flip(self):
            if self.flipped == "N":
                self.flipped = "Y"
            else:
                self.flipped = "N"
            return

        def getLayerNames(self):
            layerNames = []
            if len(self.hair_list) > 0:
                layerNames.append("Hair")
            if len(self.face_list) > 0:
                layerNames.append("Face")
            if self.clothes <> "":
                layerNames.append("Clothes")
            if self.tool <> "":
                layerNames.append("Tool")
            return layerNames

Defining Victoria (vi) as a person:
Python:
define vi = Person(Character("Victoria", color="#FF0C20", image="victoria"),
    "Victoria",
    ["01"],
    ["happy", "shock", "smirk", "none"],
    ["bar", "dress01", "none"],
    ""
)
If I use "$vi.initChar()" from whithin the game, it works fine, the person is reset to its initial state. However, this does not work from whithin the screens... If I do that in the screens, the character does not change its face, clothes or flips, and no errors happen...

Any ideas / suggestions?

Thanks in advance!!! :)
 
Last edited:
  • Hey there
Reactions: Saint Blackmoor

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,130
14,809
If I use "$vi.initChar()" from whithin the game, it works fine, the person is reset to its initial state. However, this does not work from whithin the screens... If I do that in the screens, the character does not change its face, clothes or flips, and no errors happen...
Hmm...

Both ConditionSwitch and Composite displayable update correctly in real time, even from a screen. This while the screen correctly catch the update of an object attribute. And even the three put together works correctly. I just tested it with the 7.5.0 and 8.0 to be sure. It was a limited test, but I don't see a reason for the behavior to change just because the displayable are a bit more complex.
Perhaps an interaction issue, in which case calling right after the reset would solve it. But I have doubts.

The error more likely lay in the most significant, yet only one to be absent, part, the screen. Or in the way it/they is/are proceeded.
If I had to take a wild guess, I would say, replace Function( vi.initChar() ) (incorrect) by Function( vi.initChar ) (correct). But it's a wild guess.
 

grima_grima

Verified Developer
Game Developer
Nov 24, 2021
495
1,824
Hmm...

Both ConditionSwitch and Composite displayable update correctly in real time, even from a screen. This while the screen correctly catch the update of an object attribute. And even the three put together works correctly. I just tested it with the 7.5.0 and 8.0 to be sure. It was a limited test, but I don't see a reason for the behavior to change just because the displayable are a bit more complex.
Perhaps an interaction issue, in which case calling right after the reset would solve it. But I have doubts.

The error more likely lay in the most significant, yet only one to be absent, part, the screen. Or in the way it/they is/are proceeded.
If I had to take a wild guess, I would say, replace Function( vi.initChar() ) (incorrect) by Function( vi.initChar ) (correct). But it's a wild guess.
Hey, thanks for taking the time to reply here :)

This is how I'm calling the screen, from the menu:

In the "build_scene" screen, if I add that line ($ vi.initChar()), the character does not udpate anymore. If I remove it, I'm able to select face, clothes, etc, but it is not reset when I return to the screen...

Python:
# Build Your Scene menu item
    textbutton _("Build Your Scene") action ShowMenu("build_scene")


screen build_scene():
    $ vi.initChar() # if I add this line, the character freezes
    tag menu
    use build
    textbutton _("Return"):
        style "return_button"
        action Call("reset_bg")


screen build():
    frame:
        vpgrid:
            cols 4
            spacing 10
            draggable True
            mousewheel True
            allow_underfull True
            xsize 1910
            ysize 980
            scrollbars "vertical"
            side_xalign 0.5

            for q in backgrounds: #shows the backgrounds
                imagebutton:
                    idle im.Scale(q[1], thumb_length, thumb_width)
                    insensitive im.Scale("locked.webp", thumb_length, thumb_width)
                    hovered Notify(q[0])
                    unhovered Hide('notify')
                    action [SetVariable("selected_bg", q[2]), Show("build_chars")]
                    selected False
 

grima_grima

Verified Developer
Game Developer
Nov 24, 2021
495
1,824
One more thing, if I add this code to a textbutton, it works...
Python:
textbutton "Reset Caracter" action Function(ResetChar, selected_char_name)


#function definition
    def ResetChar(_char_name):
        person = chars_dict.get(_char_name)
        person.initChar()
        chars_dict[_char_name] = person
        return
But I would rather have an automatic reset than having the user click the button.
 

gojira667

Member
Sep 9, 2019
252
229
One more thing, if I add this code to a textbutton, it works...
Python:
textbutton "Reset Caracter" action Function(ResetChar, selected_char_name)
...
But I would rather have an automatic reset than having the user click the button.
Add that action(s) to entry/reentry?
Python:
# Build Your Scene menu item
    textbutton _("Build Your Scene") action ShowMenu("build_scene")
...
Is chars_dict a dict of Person objects? Then something like:
Python:
# Build Your Scene menu item
    textbutton _("Build Your Scene") action [Function(ResetPersons), ShowMenu("build_scene")]

#function definition
    def ResetPersons():
        for person in chars_dict:
            person.initChar()
        return

Otherwise, you can pass in a list of people:
Python:
#function definition
    def ResetPersons(list_of_persons):
        for person list_of_persons:
            chars_dict.get(person).initChar()
        return
 
  • Like
Reactions: grima_grima

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,130
14,809
In the "build_scene" screen, if I add that line ($ vi.initChar()), the character does not udpate anymore.
Of course that it do not update in this case, but it's not a freeze, it's a constant reset.

As said in :
Screens must not cause side effects that are visible from outside the screen. Ren'Py will run a screen multiple times, as it deems necessary. It runs a screen as part of the image prediction process, before the screen is first shown. As a result, if running a screen has side effects, those side effects may occur at unpredictable times.
Said otherwise, each time Ren'Py will proceed the screen (what is around 5 times by second), it will proceed your $ vi.initChar() line and reset the character.
What in a way is a good sign, since it prove that the character is correctly reset from a screen.


Python:
# Build Your Scene menu item
    textbutton _("Build Your Scene") action ShowMenu("build_scene")
So it's a menu screen and not a game screen, and I guess that it's at main menu level. But both aren't a problem. A menu screen update perfectly well, and since it's only the reset that don't works prove that Ren'Py access the objects with their correct values even at this time.


Code:
screen build_scene():
    $ vi.initChar() # if I add this line, the character freezes
    tag menu
    use build
    textbutton _("Return"):
        style "return_button"
        action Call("reset_bg")


screen build():
    frame:
        vpgrid:
            cols 4
            spacing 10
            draggable True
            mousewheel True
            allow_underfull True
            xsize 1910
            ysize 980
            scrollbars "vertical"
            side_xalign 0.5

            for q in backgrounds: #shows the backgrounds
                imagebutton:
                    idle im.Scale(q[1], thumb_length, thumb_width)
                    insensitive im.Scale("locked.webp", thumb_length, thumb_width)
                    hovered Notify(q[0])
                    unhovered Hide('notify')
                    action [SetVariable("selected_bg", q[2]), Show("build_chars")]
                    selected False
Hmm... I have some difficulties to figure the screens process here.
Why the need to include "build" into "build_scene" since you'll show all the other screens on top of those two ?

use is made to include a screen as embedded part. It is not another way to show a screen. Both "built_scene" and "build" will still be displayed after the Show( "build_chars" ) ; they are just displayed behind it.
Yet, by itself the presence of is not wrong here. It's the way you use it that is wrong. It should be used with its expression keyword, in order to change the embedded screen accordingly to the actual building step.

Something that would looks like this:
Python:
screen build_scene():

    tag menu

    # Name of the screen corresponding to the actual step.
    default actualScreen = "build_background"

    # Include the screen corresponding to the actual step.
    use expression actualScreen

    textbutton _("Return"):
        style "return_button"
        action Call("reset_bg")


screen build_background():
    frame:
        vpgrid:
            cols 4
            spacing 10
            draggable True
            mousewheel True
            allow_underfull True
            xsize 1910
            ysize 980
            scrollbars "vertical"
            side_xalign 0.5

            for q in backgrounds: #shows the backgrounds
                imagebutton:
                    idle im.Scale(q[1], thumb_length, thumb_width)
                    insensitive im.Scale("locked.webp", thumb_length, thumb_width)
                    hovered Notify(q[0])
                    unhovered Hide('notify')
                    #  Keep track of the selected background AND change the name of the
                    # screen to include for it to be the one corresponding to the next step.
                    action [ SetVariable("selected_bg", q[2]), SetScreenVariable( "actualScreen", "build_chars" ) ]
                    selected False
Then, this offer you a way to automatically reset the character, and possibly other variables.
Python:
screen build_scene():

    tag menu

    # Force a reset at each opening of the screen to be always clean.
    default actualScreen = "reset"

    # If it's the reset step...
    if actualScreen == "reset":
        # reset the character...
        $ vi.initChar()
        # then reset the background...
        $ selected_bg = None
        # and finally pass to the first step.
        $ actualScreen = "build_background"

    # Include the screen corresponding to the actual step.
    use expression actualScreen

    textbutton _("Return"):
        style "return_button"
        action Call("reset_bg")

[...]
screen displayTheScene():
    [...]
    textbutton "reset":
        action SetScreenVariable( "actualScreen", "reset" )

One more thing, if I add this code to a textbutton, it works...
Python:
textbutton "Reset Caracter" action Function(ResetChar, selected_char_name)
What definitively demonstrate that it's the way you were doing the reset (that is still not part of the code you shown) that is in fault here.



Add that action(s) to entry/reentry?

Is chars_dict a dict of Person objects? Then something like:
Python:
# Build Your Scene menu item
    textbutton _("Build Your Scene") action [Function(ResetPersons), ShowMenu("build_scene")]
This wouldn't works, ResetPersons would only proceeded once, when the player click to open the scene builder.
For the reset to happen between two scenes with this approach, the player would have to close the scene builder then open it again.
 
  • Like
Reactions: grima_grima

grima_grima

Verified Developer
Game Developer
Nov 24, 2021
495
1,824
Python:
# Build Your Scene menu item
    textbutton _("Build Your Scene") action [Function(ResetPersons), ShowMenu("build_scene")]

#function definition
    def ResetPersons():
        for person in chars_dict:
            person.initChar()
        return
This one did the trick! Thanks a lot! I had tried it before, but I was getting an error and I thought it would not accept functions in the menu call :)
 
  • Like
Reactions: gojira667

grima_grima

Verified Developer
Game Developer
Nov 24, 2021
495
1,824
Of course that it do not update in this case, but it's not a freeze, it's a constant reset.

As said in :


Said otherwise, each time Ren'Py will proceed the screen (what is around 5 times by second), it will proceed your $ vi.initChar() line and reset the character.
What in a way is a good sign, since it prove that the character is correctly reset from a screen.




So it's a menu screen and not a game screen, and I guess that it's at main menu level. But both aren't a problem. A menu screen update perfectly well, and since it's only the reset that don't works prove that Ren'Py access the objects with their correct values even at this time.




Hmm... I have some difficulties to figure the screens process here.
Why the need to include "build" into "build_scene" since you'll show all the other screens on top of those two ?

use is made to include a screen as embedded part. It is not another way to show a screen. Both "built_scene" and "build" will still be displayed after the Show( "build_chars" ) ; they are just displayed behind it.
Yet, by itself the presence of is not wrong here. It's the way you use it that is wrong. It should be used with its expression keyword, in order to change the embedded screen accordingly to the actual building step.

Something that would looks like this:
Python:
screen build_scene():

    tag menu

    # Name of the screen corresponding to the actual step.
    default actualScreen = "build_background"

    # Include the screen corresponding to the actual step.
    use expression actualScreen

    textbutton _("Return"):
        style "return_button"
        action Call("reset_bg")


screen build_background():
    frame:
        vpgrid:
            cols 4
            spacing 10
            draggable True
            mousewheel True
            allow_underfull True
            xsize 1910
            ysize 980
            scrollbars "vertical"
            side_xalign 0.5

            for q in backgrounds: #shows the backgrounds
                imagebutton:
                    idle im.Scale(q[1], thumb_length, thumb_width)
                    insensitive im.Scale("locked.webp", thumb_length, thumb_width)
                    hovered Notify(q[0])
                    unhovered Hide('notify')
                    #  Keep track of the selected background AND change the name of the
                    # screen to include for it to be the one corresponding to the next step.
                    action [ SetVariable("selected_bg", q[2]), SetScreenVariable( "actualScreen", "build_chars" ) ]
                    selected False
Then, this offer you a way to automatically reset the character, and possibly other variables.
Python:
screen build_scene():

    tag menu

    # Force a reset at each opening of the screen to be always clean.
    default actualScreen = "reset"

    # If it's the reset step...
    if actualScreen == "reset":
        # reset the character...
        $ vi.initChar()
        # then reset the background...
        $ selected_bg = None
        # and finally pass to the first step.
        $ actualScreen = "build_background"

    # Include the screen corresponding to the actual step.
    use expression actualScreen

    textbutton _("Return"):
        style "return_button"
        action Call("reset_bg")

[...]
screen displayTheScene():
    [...]
    textbutton "reset":
        action SetScreenVariable( "actualScreen", "reset" )



What definitively demonstrate that it's the way you were doing the reset (that is still not part of the code you shown) that is in fault here.





This wouldn't works, ResetPersons would only proceeded once, when the player click to open the scene builder.
For the reset to happen between two scenes with this approach, the player would have to close the scene builder then open it again.
Thanks for your reply, I really appreciate it! I think I've learned a couple of things here :)