Ren'Py "Flashlight effect" not affecting imagebutton

3DeadAngel

Spooky~~~
Game Developer
May 10, 2023
76
84
Hello!

I'm working on a freeroam and I'm kinda stuck for hours now with absolutely no idea how I would be able to solve it. I would like to achieve a flashlight freeroam where some objects are hidden by the black area of the flashlight. Unfortunately all the imagebuttons are completely ignoring my dark overlay... (It's fine for the arrows as their idle state are just blank images but other hovers like the door is just not pretty as its "glowing" in the dark:D)

I have the following code from Lemmasoft forums:
Code:
screen flashlight_screen:
    modal False tag flashlight_screen
    add Flashlight()

init python:
  
    class Flashlight(renpy.Displayable):
        def __init__(self):
            super(Flashlight, self).__init__()
          
            # This image should be twice the width and twice the height
            # of the screen.
            self.child = Image("others/flashlight_cursor.png")

            # (-1, -1) is the way the event system represents
            # "outside the game window".
            self.pos = (-1, -1)

        def render(self, width, height, st, at):
            render = renpy.Render(config.screen_width, config.screen_height)
          
            if self.pos == (-1, -1):
                # If we don't know where the cursor is, render pure black.
                render.canvas().rect("#000", (0, 0, config.screen_width, config.screen_height))
                return render

            # Render the flashlight image.
            child_render = renpy.render(self.child, width, height, st, at)

            # Draw the image centered on the cursor.
            flashlight_width, flashlight_height = child_render.get_size()
            x, y = self.pos
            x -= flashlight_width / 2
            y -= flashlight_height / 2
            render.blit(child_render, (x, y))
            return render

        def event(self, ev, x, y, st):
            # Re-render if the position changed.
            if self.pos != (x, y):
                renpy.redraw(self, 0)

            # Update stored position
            self.pos = (x, y)

        def visit(self):
            return [ self.child ]
This is how I have my imagebuttons are setup + how i call the screens into the freeroam section:
Code:
screen so_wc:
    imagebutton:
            xalign 0.0
            yalign 1.0
            idle "others/arrow_idlev.png"
            hover "others/arrowd_hover.png"
            action Jump("o_wcback")

    imagebutton:
            xalign 0.0
            yalign 0.0
            auto "Fshome/Fshome10_%s.png"
            focus_mask True
            action Play("sound", "audio/sfx/door_open.mp3"), Jump("o_wcoutside")

label o_wc:
    scene foffice54 with Dissolve(.1)
    show screen flashlight_screen
    call screen so_wc
Thanks in advance if anyone decides to help me out.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,653
2,338
Stab in the dark, so to speak...

You'll need to make your Fshome10_idle.png image black (or fully transparent) and then change your imagebutton definition so that the focus_mask is based on your hover image.

Something like:
Python:
   imagebutton:
            xalign 0.0
            yalign 0.0
            auto "Fshome/Fshome10_%s.png"
            focus_mask "Fshome10_hover"
            action Play("sound", "audio/sfx/door_open.mp3"), Jump("o_wcoutside")

I'm not familiar with the flashlight code you are using. But I'm guessing it is effectively showing one image through another image.
I'm assuming that it's a single image and the parts of the flashlight shown are based on that displayable/image, not the whole screen (I could be wrong).

I think the imagebuttons are entirely separate and therefore are not being impacted by the flashlight. Or perhaps they are on different layer (they'd need to be in order to be shown "above" the underlying image).

Making the idle image black will mean it's shown, but the player won't see it (at least until the flashlight overlays it).
It may work better if the whole idle image was transparent with no shapes/pixels except the transparency. But I'd probably want to test it with a black idle image and a fully transparent idle image to see which works better with the flashlight.

Normally, the trigger for the state changing from idle to hover is that the cursor passes over the idle shape. If that's black on a transparent background, you may not actually need my next suggestion. But if the idle image is 100% transparency, you will.

focus_mask "Fshome10_hover" forces the imagebutton to ignore the shapes from the idle image as their "mouseover" trigger and instead using the displayable used for the hover image. That will then show the hover image (presumably fully colored) as the mouse moves within the borders of the the hover shape.

I'm afraid the hover image is going to be displayed in full, regardless of where the flashlight shape is - but as soon as the mouse moves outside the hover image borders, it'll go back to being the black/transparent idle image.
 
Last edited:
  • Like
Reactions: 3DeadAngel

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,653
2,338
Another thought, separate from the first.

You might want to look at the screen's .

I pretty much ignored the Flashlight code on my first pass, but decided to take another look now. I'm still unsure, but it looks like it is instead displaying a black screen over the top of the RenPy background (in the same way all screens appear in front of the background elements). Then poking a hole in it, using the flashlight_cursor.png file - which is probably a transparent circle, maybe with a circular border... or something (I really need to go read what render.blit() does). Or maybe renpy.render really is the combined viewable screen area. Dunno.

Since zorder affects how close to the player an image/screen is... you might be able to put your screen so_wc(): at zorder 1 and screen flashlight_screen(): at zorder 2 - effectively putting so_wc behind the flashlight.

IF it works, this is likely the better solution than my first idea about idle images. But perhaps it'll require a combination of both solutions (or something entirely different someone else comes up with). You'll just need to test it to find out.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
12,245
19,516
Code:
label o_wc:
    scene foffice54 with Dissolve(.1)
    show screen flashlight_screen
    call screen so_wc
The issue come from here. You display the flashlight_screen before the screen itself.

As 79flavors implied, this mean that the effect appear under the buttons, or if you prefer the buttons appear above the effect. Therefore the solution is to inverse this. And for this, there's many way:

The zorder approach presented above.

A use approach:
Python:
screen so_wc():

    [your buttons]

    use flashlight_screen
Or an all in one screen:
Python:
screen so_wc():

    [your buttons]

    [your flashlight effect]
It's also possible to rely on layers:
Python:
init python:
    # Create a new layer for the flashlight and other effects.
    # Place it below the /overlay/ layer, that should always be the top one.
    renpy.add_layer("effect", below="overlay")

screen so_wc():
    layer "screen" # Should be the default layer, but better to enforce it in case of future change
    [...]

screen flashlight_screen():
    layer "effect"  # Place the flashlight on the /effect/ layer.
    [...]
Whatever the method you choose, the important point is to unsure that the effect will cover your buttons.
 

3DeadAngel

Spooky~~~
Game Developer
May 10, 2023
76
84
Wow... Both of you guys just came up with multiple methods meanwhile I couldn't get nowhere for hours. In the end I did stick to the zorder method.

Also to answer how does that flashlight image is looks like: an image which is totally black expect the middle where there is some white in it.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,653
2,338
Wow... Both of you guys just came up with multiple methods meanwhile I couldn't get nowhere for hours.

In fairness, we've both been dealing with these sorts of questions for many, many years. Dealing with so many different people and projects mean we've seen a lot of different ways of doing the same old stuff. What you are doing is absolutely new to me - but dealing with all other projects over the years means that I've seen (and researched) enough things in the past that are similar enough to nudge me in the general direction of a solution - even if I'm wrong. Sometime it's just about knowing where to pick at the edges.

It's also a mindset. I wouldn't know where to start writing a story for a game and I often look at images where people are asking "how do I fix <this>"... and I just think "there's nothing wrong with it".
Then someone who knows what they are talking about comes along and says "do this, change that and move that to there"... and they re-render it and I still can't see the difference - except everyone else is completely happy it's better.

It's experience - and you're just starting out. Don't sweat it.
After you've been aiding other people write RenPy code for 5+ years (and in the case of Anne, python for a lot longer than that)... you'll still have moments where you still get nowhere for hours. But by then, a little sleep and a random idea will get you there eventually.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
12,245
19,516
What you are doing is absolutely new to me - but dealing with all other projects over the years means that I've seen (and researched) enough things in the past that are similar enough to nudge me in the general direction of a solution - even if I'm wrong. Sometime it's just about knowing where to pick at the edges.
Totally. Experience don't teach you how to do something, but what can be the issue and where to search for the solution.


It's experience - and you're just starting out. Don't sweat it.
Agreed here too.

Few weeks ago I tested a well handled poker game made by someone that few years ago could barely make the difference between True and "True".

So, OP, don't despair, it will come with the time.


(and in the case of Anne, python for a lot longer than that)...
Honestly, not even. I knew Python and its basis, but never really had to use it before I started to mess with Ren'Py.
It's just that my own career path made me have to deal with something like two dozens different languages (variation included). So, here too it's just a question of experience. The syntax, and keywords are different, but there's a logic behind all this. At the end of the day for loop will always be the same, whatever how you write it and how it's handled by the language.

After, the fact that I'm stubborn also help. I have a logic that come to my mind, and if it's not directly possible with the language I'll search how to make it possible :giggle: