Ren'Py easier way to make a Draggable map?

Hysocs

Newbie
Mar 5, 2024
17
8
Ok, so somewhere a while back, I played a game that had a map you could drag around, etc. So, I went out of my way to make one for my game. I tried to do this on a screen by setting a viewport and displaying my image for the map into it. However, I ran into an issue when I then added a frame or any clickable UI elements; it prevented scrolling in the viewport.

I tried so many things, like separating the image and UI into vbox and hbox. It restored the viewport, but the UI didn't follow it. I did end up getting something that works 100%, but I ended up switching to Python and using Pygame to create my own custom module that could detect mouse events and that could be added to the screen's viewport. It's working all fine and dandy; it's just the logic is really large, 200 lines. So, I was wondering if I could indeed pull this off in Ren'Py only and not need to do Python logic.


this is what i got so far


You might think 200 lines isn't bad, but I haven't integrated it into any other systems yet. After integrations, it's going to be quite large and probably slow down a lot, especially since I will be displaying images for the NPCs present at locations.

I would prefer to handle it as a viewport with a square size and place image buttons inside of it. I'm hoping someone here knows how to get the viewport to function with an image and clickable objects on top of it. If I remove the image, it works; for instance, if I add a frame that's blank. I mean, I do scrollable menus all the time. It's just something wonky with Ren'Py and images on top of images in a viewport.

I feel like using Pygame for mouse events will probably cause issues later especially on other platforms
Python:
# how its used in a screen
            side "c b r":
                    viewport id "map_viewport":
                        draggable True
                        mousewheel True
                        xinitial 400
                        yinitial 500
                        add DraggableMap("images/Map2.png", 2000, 2000, buttons)
                    bar value XScrollValue("map_viewport")
                    vbar value YScrollValue("map_viewport")

#this is how im rendering the image. i use another class for buttons, this isnt all of the logic

    class DraggableMap(renpy.Displayable):
        def __init__(self, image, width, height, buttons, **properties):
            super(DraggableMap, self).__init__(**properties)
            self.image = renpy.easy.displayable(image)
            self.image_width = width
            self.image_height = height
            self.buttons = buttons
        def render(self, width, height, st, at):
            render = renpy.Render(self.image_width, self.image_height)
            base = renpy.render(self.image, self.image_width, self.image_height, st, at)
            render.blit(base, (0, 0))
            for button in self.buttons:
                button.render(render)
            return render
# here im using pygame to handle clicking and i also handle alot of button logic with it also


        def event(self, ev, x, y, st):
            hover_changed = False  
            for button in self.buttons:
                if button.check_hover(x, y):
                    if not button.hovering: 
                        button.on_hover()
                        button.hovering = True  
                        hover_changed = True
                    if ev.type == pygame.MOUSEBUTTONUP:
                        button.on_click()
                        return None
                else:
                    if button.hovering: 
                        button.on_leave()
                        button.hovering = False  
                        hover_changed = True
            if hover_changed:
                renpy.redraw(self, 0)  # Redraw the map if any button's hover state changed
            return None
If it's possible, I will just redo my code and handle it via a screen instead of this. It's mainly because I'm lazy and don't want to write any more logic. It took me hours to get hovering over the button to work properly.
 

Hysocs

Newbie
Mar 5, 2024
17
8
edit. my main concern is with "pygame.MOUSEBUTTONUP:" and if that will cause issues for instance with ios/android. im thinking they wont be able to click on the map

any posts i find about draggable maps only have a working map image and never any clickable objects or function onto the map, so i went at this blind
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,130
14,809
However, I ran into an issue when I then added a frame or any clickable UI elements; it prevented scrolling in the viewport.
Python:
screen myBigScreen():
    viewport:
        mousewheel True
        scrollbars "both"
        arrowkeys True

        frame:
            # Commented since you'll use your own overflowing image.
            #background "images/really big.jpg"
            has vbox
            for i in range( 0, 50 ):
                textbutton "choice [i] {}".format( "A"*300 ):
                    action Function( renpy.notify, "Choice {}".format( i ) )

label start:
    call screen myBigScreen
    "END"
There's a frame in a viewport, overflowing both due to its background and its content ; content that is exclusively clickable elements. Yet the viewport can still be perfectly scrolled through the arrow keys, the scrollbars clearly show that it only display a part of its content, and the clicks are caught by the right buttons.


This being said, yes, if the scrollbars aren't displayed, the viewport will expand as much as the frame if the said frame is its child (therefore the first element created in the viewport). And like, unless given a specific size, frame always expand to the size of their content, and its content is what is supposed to be scrolled, it create a viewport that overflow from the screen and, by then do not even need to be scrolled since it display the entirety of its content. In the same time, giving a specific size to the frame would limits it to the display window size, and therefore smaller than its content, that would still don't need to be scrolled.

So, either you don't use the totally useless frame:
/!\ wrote on the fly, may have some typo /!\
Python:
screen myMap():

    viewport:
        mousewheel True
        arrowkeys True

        add "images/map.png"

        imagebutton:
            auto "clickable1_%s.png"
            xpos 100
            ypos 100
            action Jump( "location1" )
            tooltip ( 100, 100, "location 1" )

        [...] 

        imagebutton:
            auto "clickablex_%s.png"
            xpos 200
            ypos 200
            action Jump( "locationx" )
            tooltip ( 200, 200, "location x" )

        [...]

        imagebutton:
            auto "clickablen_%s.png"
            xpos 300
            ypos 300
            action Jump( "locationn" )
            tooltip ( 300, 300, "location n" )

        $ tt = GetTooltip()
        if tt:
            text "{}".format( tt[2] ):
               xpos tt[0] - 50
               ypos tt[1] - 50
Or you are a bit creative and make the scrollbars none visible:
Python:
screen myBigScreen():
    viewport:
        mousewheel True
        arrowkeys True
        scrollbars "both"

        # The scrollbar space will be left empty, so center the viewport
        xpos 20
        ypos 20
        xsize config.screen_width - 20
        ysize config.screen_height - 20

        scrollbar_top_bar None
        scrollbar_bottom_bar None
        scrollbar_base_bar None
        scrollbar_thumb None
        vscrollbar_left_bar None
        vscrollbar_right_bar None
        vscrollbar_base_bar None
        vscrollbar_thumb None

        frame:
            add "images/_big.jpg"
            vbox:
                xpos 0
                ypos 0
                for i in range( 0, 30 ):
                    textbutton "choice [i] {}".format( "A"*300 ):
                        action Function( renpy.notify, "Choice {}".format( i ) )
In both case you've what you want to achieve, and without the need for a single Python instruction.
 

Hysocs

Newbie
Mar 5, 2024
17
8
Python:
screen myBigScreen():
    viewport:
        mousewheel True
        scrollbars "both"
        arrowkeys True

        frame:
            # Commented since you'll use your own overflowing image.
            #background "images/really big.jpg"
            has vbox
            for i in range( 0, 50 ):
                textbutton "choice [i] {}".format( "A"*300 ):
                    action Function( renpy.notify, "Choice {}".format( i ) )

label start:
    call screen myBigScreen
    "END"
There's a frame in a viewport, overflowing both due to its background and its content ; content that is exclusively clickable elements. Yet the viewport can still be perfectly scrolled through the arrow keys, the scrollbars clearly show that it only display a part of its content, and the clicks are caught by the right buttons.


This being said, yes, if the scrollbars aren't displayed, the viewport will expand as much as the frame if the said frame is its child (therefore the first element created in the viewport). And like, unless given a specific size, frame always expand to the size of their content, and its content is what is supposed to be scrolled, it create a viewport that overflow from the screen and, by then do not even need to be scrolled since it display the entirety of its content. In the same time, giving a specific size to the frame would limits it to the display window size, and therefore smaller than its content, that would still don't need to be scrolled.

So, either you don't use the totally useless frame:
/!\ wrote on the fly, may have some typo /!\
Python:
screen myMap():

    viewport:
        mousewheel True
        arrowkeys True

        add "images/map.png"

        imagebutton:
            auto "clickable1_%s.png"
            xpos 100
            ypos 100
            action Jump( "location1" )
            tooltip ( 100, 100, "location 1" )

        [...]

        imagebutton:
            auto "clickablex_%s.png"
            xpos 200
            ypos 200
            action Jump( "locationx" )
            tooltip ( 200, 200, "location x" )

        [...]

        imagebutton:
            auto "clickablen_%s.png"
            xpos 300
            ypos 300
            action Jump( "locationn" )
            tooltip ( 300, 300, "location n" )

        $ tt = GetTooltip()
        if tt:
            text "{}".format( tt[2] ):
               xpos tt[0] - 50
               ypos tt[1] - 50
Or you are a bit creative and make the scrollbars none visible:
Python:
screen myBigScreen():
    viewport:
        mousewheel True
        arrowkeys True
        scrollbars "both"

        # The scrollbar space will be left empty, so center the viewport
        xpos 20
        ypos 20
        xsize config.screen_width - 20
        ysize config.screen_height - 20

        scrollbar_top_bar None
        scrollbar_bottom_bar None
        scrollbar_base_bar None
        scrollbar_thumb None
        vscrollbar_left_bar None
        vscrollbar_right_bar None
        vscrollbar_base_bar None
        vscrollbar_thumb None

        frame:
            add "images/_big.jpg"
            vbox:
                xpos 0
                ypos 0
                for i in range( 0, 30 ):
                    textbutton "choice [i] {}".format( "A"*300 ):
                        action Function( renpy.notify, "Choice {}".format( i ) )
In both case you've what you want to achieve, and without the need for a single Python instruction.

wow, making the textbutton range is so good. i never would have thought of that, the only thing it prevents dragging on the fist menu load sometimes, also its very laggy

one more thing do you know anything about
if ev.type == pygame.MOUSEBUTTONUP:

and if it will work dynamically with renpy so that it doesnt break on android, ect. if not is there an alternative?
 

Hysocs

Newbie
Mar 5, 2024
17
8
nevermind i fixed the lag by using the image buttons instead of a range.

i swear i tried to do this same thing and it didnt work....


Code:
            frame:  # The actual map frame
                xysize (750, 750)
                viewport:
                    mousewheel True
                    arrowkeys True
                    scrollbars "both"
                    draggable True

                    # The scrollbar space will be left empty, so center the viewport
                    xpos 20
                    ypos 20
                    xsize config.screen_width - 20
                    ysize config.screen_height - 20

                    scrollbar_top_bar None
                    scrollbar_bottom_bar None
                    scrollbar_base_bar None
                    scrollbar_thumb None
                    vscrollbar_left_bar None
                    vscrollbar_right_bar None
                    vscrollbar_base_bar None
                    vscrollbar_thumb None

                    frame:
                        add "images/Map2.png"
                        xysize (2000, 2000)
                        vbox:
                            xysize (2000, 2000)
                            imagebutton:
                                idle im.Scale("images/ui/Mapbutton.png", 55, 55)
                                hover im.Scale("images/ui/MapbuttonHover.png", 55, 55)
                                xpos 100
                                ypos 100
                                action Jump( "location1" )
                                tooltip ( 100, 100, "location 1" )

i got this working, thanks. as my typical self with coding. i always get what i want, just not in the simplest way.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,130
14,809
nevermind i fixed the lag [...]
This lag thing really disturb me.
I know that, due to the way it's designed, Ren'Py can have some slowdown when there's too many buttons in a screen, but with my extended variable viewer you can goes up to 250 buttons in the same screen, and my 10yo computer do not experience a single lag due to this ; nor have I heard people complain about this. There's possible a lag at the opening, when the variables are processed, but none in the display itself.

What usually make a screen lag is the over abusive use of inline python or python block in it. Not the number of buttons.


As for "MOUSEBUTTONUP", I'm not sure that someone ever used it.
A flag saying that [whatever] is in progress is generally he way to go. Then, when the button isn't down anymore, you'll perform the drop (or whatever you need to do) action if the flag is raised.
 

Hysocs

Newbie
Mar 5, 2024
17
8
This lag thing really disturb me.
I know that, due to the way it's designed, Ren'Py can have some slowdown when there's too many buttons in a screen, but with my extended variable viewer you can goes up to 250 buttons in the same screen, and my 10yo computer do not experience a single lag due to this ; nor have I heard people complain about this. There's possible a lag at the opening, when the variables are processed, but none in the display itself.

What usually make a screen lag is the over abusive use of inline python or python block in it. Not the number of buttons.


As for "MOUSEBUTTONUP", I'm not sure that someone ever used it.
A flag saying that [whatever] is in progress is generally he way to go. Then, when the button isn't down anymore, you'll perform the drop (or whatever you need to do) action if the flag is raised.
Yeah, maybe the lag is related to my PC or something. It gets worse the more buttons I add, so maybe it's related to image caching. Though, I suspect it's probably due to my use of heavy nesting to align text perfectly over each text button and further nesting to incorporate a side panel that's also interactive.|

My debug menu is also displaying all my variables and live data for everything I can possibly fit into it. It's massive, and I experience no lag with it. Additionally, I have a large cheats menu that allows me to set values for each NPC's stats, etc., and it scrolls fine, despite having plenty of text buttons. I guess the issue could be related to anything—image size, scaling of each button, etc. I will probably need to strip things down until it's fixed.

Anyways thanks for the help code is now at 145 lines and uses only 1 screen with no python, this is with the integration into my other systems, you saved me so much trouble thanks
 
Last edited: