Ren'Py Request help from renpy/python pro

MTY Games

Newbie
Game Developer
Jan 12, 2019
55
90
Hi all.

I have a scene that has a clickable hotspot(s). I've created a screen for this hotspot(s) as follows:


Code:
screen box():

    imagebutton:
        idle "gui/button/hotspot_idle.png"
        hover "gui/button/hotspot_hover.png"
        xpos 262
        ypos 685
        action Jump("pick_up_box")

All is well, works perfectly. However, I plan to have these dotted around many different scenes in the game, and for each one I will have to create a new screen. I'm wondering if there is a more elegant way to do this without having to create a new screen everytime?

Basically, I need to be able to define the location of the hotspot each time using xpos/ypos and define which label it jumps to when clicked.

Appreciate any help with this.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
The normal way of doing this would be a single screen, with multiple hotspots. You can make each hotspot optional, by coding statements in there.

Something like:

Python:
screen box():

    imagebutton:
        pos (262,685)
        focus_mask True
        auto "gui/button/hotspot_%s.png"
        action Jump("pick_up_box")

    if lamp_available:
        imagebutton:
            pos (1250,250)
            focus_mask True
            auto "gui/button/hotspot_%s.png"
            action ( SetVariable ("lamp_available",  False), Jump("pick_up_lamp") )

The SetVariable() is a bit of an extravagance on my part... it could just as easily be done as part of the label pick_up_lamp:. It's only there to show the possibility.

I understand that isn't an answer to the question you asked, but it is the more common solution that someone coding the game mechanics you seem to be planning.

My previous examples of similar code can be found here:


All of which is the quick answer... I'm currently playing with a bit of code to test something I have in mind to answer your actual question.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,355
15,269
Basically, I need to be able to define the location of the hotspot each time using xpos/ypos and define which label it jumps to when clicked.
Well, you answered your question.

As for a less theoretical answer, there's so many possible ones, and this for every each one piece of the problem. Some of them being :

1) How to store the information related to the button(s):

a) As a dictionary of tuples:

Each entry of the dictionary is one of the button, an each element of the tuple is one of the information.
Python:
#  Those information SHOULD NOT be saved. This permit you to change the values
# from one update to another if you made an error.
define myButtons = {
    # One of the entries.
    "thisButton": ( "gui/button/hotspot_%s.png", 262, 685, "pick_up_box" ),
    # Another one.
    "thatButton": ( "gui/button/that_%s.png", 321, 123, "aLabel" ),
    # Continue like that.
    }
Then your button would looks like that:
Code:
    imagebutton:
        auto myButtons["thisButton"][0]
        xpos myButtons["thisButton"][1]
        ypos myButtons["thisButton"][2]
        action Jump(myButtons["thisButton"][3])

b) As an series of functions:

You'll always take the information from the same source, but the information will changes depending of the context.
Python:
init python:

    # Function to know what image to use for the button.
    def buttonImage():
        # If the screen "thisScreen" is actually shown.
        if renpy.get_screen( "thisScreen" ):
            # Use this button.
            return "gui/button/hotspot_%s.png"
        # If it's the screen 'thatScreen" that is actually shown.
        elif renpy.get_screen( "thatScreen" ):
            # Then use this button instead.
            return "gui/button/that_%s.png"

    # Function to know the x position for the button.
    def buttonXPos():
        if renpy.get_screen( "thisScreen" ):
            return 262
        elif renpy.get_screen( "thatScreen" ):
            return 321

    # Function to know the y position for the button.
    def buttonYPos():
        if renpy.get_screen( "thisScreen" ):
            return 685
        elif renpy.get_screen( "thatScreen" ):
            return 123

    # Function to know the label where to jump for the button.
    def buttonLabel():
        if renpy.get_screen( "thisScreen" ):
            return "pick_up_box"
        elif renpy.get_screen( "thatScreen" ):
            return "aLabel"
Then your button would looks like that:
Code:
    imagebutton:
        auto buttonImage()
        xpos buttonXPos()
        ypos buttonYPos()
        action Jump( buttonLabel() )
Alternatively you can select the value according to an information past as argument.
Python:
init python:

    # Function to know what image to use for the button.
    def buttonImage( screenInfo ):
        # If the parameter is "this".
        if screenInfo == "this":
            # Use this button.
            return "gui/button/hotspot_%s.png"
        # If it's the "that" parameter.
        elif screenInfo == "that:
            # Then use this button instead.
            return "gui/button/that_%s.png"

    # Function to know the x position for the button.
    def buttonXPos( screenInfo ):
        if screenInfo == "this":
            return 262
        elif screenInfo == "that:
            return 321

    # Function to know the y position for the button.
    def buttonYPos( screenInfo ):
        if screenInfo == "this":
            return 685
        elif screenInfo == "that:
            return 123

    # Function to know the label where to jump for the button.
    def buttonLabel( screenInfo ):
        if screenInfo == "this":
            return "pick_up_box"
        elif screenInfo == "that:
            return "aLabel"
In this case the button would looks like this:
Code:
    imagebutton:
        auto buttonImage( "thisButton" )
        xpos buttonXPos( "thisButton" )
        ypos buttonYPos( "thisButton" )
        action Jump( buttonLabel( "thisButton" ) )

c) Directly in the screen:

If the button is in an external screen, you can then decide to change the information directly in the screen.
Python:
    # If the screen "thisScreen" is actually shown.
    if renpy.get_screen( "thisScreen" ):
#    OR alternatively
#    if screenInfo == "this"
        imagebutton:
            auto "gui/button/hotspot_%s.png"
            xpos 262
            ypos 685
            action Jump( "pick_up_box" )
    elif renpy.get_screen( "thatScreen" ):
#    if screenInfo == "that"
        imagebutton:
            auto "gui/button/that_%s.png"
            xpos 321
            ypos 123
            action Jump( "aLabel" )

2) How to let the button know what data have to be used:

a) With the help of a global variable:

You've one variable that will store a keyword defining what button is to show.
Python:
# This time, the variable MUST be saved.
# By default there's no button shown.
default actualButton = None
Then you use it like that
Code:
label whatever:
    $ actualButton = "thisButton"
    show screen whatever
or like this
Code:
screen whatever():
    $ store.actualButton = "thisButton"

b) By passing the information as argument to the screen:

Screen can get arguments, so you can benefit to that.

The screen would start like that
Code:
screen whatever( screenInfo ):
    [...]
and the button can look like this (I'll use the way 1a, but it apply for the others)
Code:
    imagebutton:
        auto myButtons[screenInfo][0]
        xpos myButtons[screenInfo][1]
        ypos myButtons[screenInfo][2]
        action Jump(myButtons[screenInfo][3])
And finally your code would be this one
Code:
label whatever:
    show screen whatever("thisButton")

c) Directly pass the data to the screen:

Like you can pass arguments to a screen, you don't necessarily need to store the data.
Code:
screen whatever( buttonImage, buttonXPos, buttonYPos, buttonLabel ):

    imagebutton:
        auto buttonImage
        xpos buttonXPos
        ypos buttonYPos
        action Jump( buttonLabel )
What you use this way
Code:
label whatever:
    call screen whatever( "gui/button/hotspot_%s.png", 262, 685, "pick_up_box" )

3) How to define the button(s):

Note: Again I'll just use the method 1a for the data storage, and 2a for to know what data to use. But obviously the same apply for all the methods.

a) Directly in the screen:

You can put the button in each screen that will need it
Code:
screen whatever():
   [...]
    imagebutton:
        auto myButtons[actualButton ][0]
        xpos myButtons[actualButton ][1]
        ypos myButtons[actualButton ][2]
        action Jump(myButtons[actualButton ][3])

b) As used screen:

Ren'py permit you to use an external screen inside another screen. This let you have only one screen for the button, while using it in many screens. Therefore you've the screen for the button
Code:
screen myButton():
    imagebutton:
        auto myButtons[actualButton ][0]
        xpos myButtons[actualButton ][1]
        ypos myButtons[actualButton ][2]
        action Jump(myButtons[actualButton ][3])
that you include in your screens
Code:
screen whatever():
    [...]

    use myButton

c) As an independent screen:

This method works with less storage methods, but it worth being mentioned as well. The screen will be always displayed, but it will show a button only when it's needed.
Python:
screen myButton():
    # If the screen "thisScreen" is actually shown, show this button.
    if renpy.get_screen( "thisScreen" ):
        imagebutton:
            auto "gui/button/hotspot_%s.png"
            xpos 262
            ypos 685
            action Jump( "pick_up_box" )
    # If it's the screen "thatScreen" that is actually shown, then show that one.
    elif renpy.get_screen( "thatScreen" ):
        imagebutton:
            auto "gui/button/that_%s.png"
            xpos 321
            ypos 123
            action Jump( "aLabel" )
    # And if it's none of those screens, then just show nothing
You just need to show the screen at the start of the game, and it will be shown forever unless you explicitly hide it, or you use the scene statement alone to clear the screen.
Code:
label start:
    show screen myButton
Note that I used renpy.get_screen to decide which button have to be shown, but it works also with a global variable. It would obviously reduce drastically the size of the screen.
Python:
screen myButton():
    # The button will be shown only if /actualButton/ have a value.
    if not actualButton is None:
        imagebutton:
            auto buttonImage( actualButton )
            xpos buttonXPos( actualButton )
            ypos buttonYPos( actualButton )
            action Jump( buttonLabel( actualButton ) )
In this case, you use it this way
Python:
label whatever:
    # Define the button to show.
    $ actualButton = "thisButton"
    show screen whatever

label pick_up_box:
    # Do not show a button anymore.
    $ actualButton = None


All this being said, according to the example you gave, and its "pick_up_box" label, I don't think that having an "one for all" button is the solution. Those buttons aren't generics one (like navigation buttons, menu buttons, or things like that) that will be shown a lot of times, but not necessarily in the same position and with the same icon ; by example a menu button could be invalidated, or a navigation button could include the face of the girl present in this place. Instead they looks like very particular buttons that will apply once and only once ; by example, I doubt that the player will have to pick up the box every two minutes.
Therefore, using an "one for all" would be way too much works, and open the gate to way too many bugs, than having to write again and again the four lines defining a button.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,355
15,269
The SetVariable() is a bit of an extravagance on my part...
Oh, it make me realize something not directly related to the question, and that I forgot to address in my answer : Is the Jump screen action really needed here ?

The label is named "pick_up_box", what can mean "there will be a scene in which the MC will carry the box", and in this case Jump is the solution.
But it can also mean "add the box to your inventory", and in this case a simple SetVariable would be way better, since the label itself would looks like :
Code:
label pick_up_box:
    $ haveTheBox = True
    jump whatever
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
Took me a lot longer than Anne to get to my actual answer.

This is my example.

Python:
define multiple_hotspots_list = [
            [ 100, 100, "my_hotspot_1" ],
            [ 300, 300, "my_hotspot_2" ],
            [ 900, 150, "my_hotspot_3" ]
        ]


screen multiple_hotspots():
    modal True

    for i in multiple_hotspots_list:
        imagebutton:
            focus_mask True
            xpos i[0]
            ypos i[1]
            auto ("gui/circle_hotspot_%s.png")
            action Jump( i[2] )

    textbutton "Exit":
        align (0.5, 0.9)
        action Return()


label start:

    scene black with fade
    "Start:.... "


label repeat_screen:

    call screen multiple_hotspots()

    "*** THE END ***"
    return


label my_hotspot_1:

    "This is hotspot 1"
    jump repeat_screen


label my_hotspot_2:

    "This is hotspot 2"
    jump repeat_screen


label my_hotspot_3:

    "This is hotspot 3"
    jump repeat_screen

This example uses define multiple_hotspots_list = [ because I'm assuming the list of hotspots will never be changed while the user is playing the game.

If however, the list of hotspots can be altered during the game session - then that define would need to be changed to a default statement (since variables created with define should be considered constants and should never be altered and therefore not saved within the save file, whereas default variables are expected to change while the game is running and therefore need their values saved).

In effect, I'm creating a list of hotspots - then using a for: loop to create as many hotspots are defined within the array. I'm nesting one array within another, because it reads better to my limited brain. This solution assumes an array per screen.

My example images are attached, for anyone wanting to run my code as an example.
 
Last edited: