Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow masking, foregrounds, and backgrounds #4804

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

shawna-p
Copy link
Contributor

@shawna-p shawna-p commented Jul 1, 2023

This PR is intended primarily to add AlphaMask functionality to LayeredImageProxy. This is useful to show character "cut-ins", where a character is speaking but not physically present in a scene or immediately visible (e.g. over phone call, hologram, from behind the POV character). As an example, consider the following:

image

While the AlphaMask is the feature I am most interested in including, I have also included foreground and background arguments to LayeredImageProxy. When using character cut-ins, it is common to include one or both of a foreground or background image to visually separate the character from the scene. In the above image, you can see that there is a darkened background behind the character, and a white border around them.

While this aesthetic can be achieved in other ways, adding properties directly to the LayeredImageProxy provides the most flexibility with the least hassle, as it means the sprite cut-in is always shown with the needed foreground/background at the right location, so users do not have to worry about showing them separately, or including them in the layered image itself to conditionally apply based on an arbitrary null attribute.

I'd be happy to assist with adjusting the Layered Image documentation to explain more about this feature, if my implementation proves to be agreeable.

@renpytom
Copy link
Member

renpytom commented Jul 1, 2023

Let me think about this, but my initial inclination is that this may be too specific for LayeredImageProxy. What may make more sense is a way to allow AlphaMask and Window to pass attributes to children, so that things can be composed in multiple different ways. (I think it may make sense to have tons of displayables support this passing - Fixed, VBox/Hbox, Side, etc.)

@mal
Copy link
Member

mal commented Jul 1, 2023

FWIW, this is currently possible using the Layer displayable, to which an AlphaMask (or shader, or any other transform) can be applied and things inside the cut-in posed on a detached layer.

I didn't mention it previously as I could see the merit of having a more straight forward approach to this for LayeredImages, but with the scope potentially broadening to a wider variety of displayables, I think leveraging the Layer displayable and detached layers, and providing some documentation for more common uses such as this could perhaps be a more flexible (especially WRT transitions) approach without the need for changes to core displayables.

@shawna-p
Copy link
Contributor Author

shawna-p commented Jul 1, 2023

I would welcome the flexibility! LayeredImageProxy was my initial inclination due to it being the only way currently to pass along layered image attributes without requiring a new image for every combination. If I understood correctly, adding this functionality to existing displayables would mean my example for a cut-in with a foreground, background, and mask might look like:

image eileen cutin = Window(AlphaMask("eileen", "mask.png"), foreground="cutin_fg.png", background="cutin_bg.png")

and, presuming I have layeredimage eileen with attributes happy_eyes and happy_mouth, I could then do show eileen cutin happy_eyes happy_mouth for example. Is that what you're describing?

My main thoughts about such an approach would be:

  • Ensure this works properly with config.adjust_attributes to pass along adjusted attributes to the final "child"
  • Ensure this works with Transform as well, if we're not using the LayeredImageProxy version, so the AlphaMask child can be cropped to be the same size as the mask, for example, and have tints applied to it

As for the use of the Layer displayable - one thing I would very much like is the ability to add on displayables like my background/foreground implementation rather than just having an easier way to use AlphaMask. I don't want the hassle of trying to show cut-in UI elements in the same location as character cut-in sprites, nor do I want to build it into the layered image as an attribute so I can avoid it getting tinted if I don't want that, for example (e.g. perhaps the sprite is tinted blue for a nighttime scene but the cut-in background/foreground shouldn't be tinted blue), and I can keep it flexible for different sprite sizes and focal points. If the Layer approach would allow for that kind of flexibility, then I'm not opposed to either solution.

@mal
Copy link
Member

mal commented Jul 1, 2023

I threw together a quick and dirty toy example to demonstrate what's involved when doing this using the Layer displayable. It uses the sylvie and eileen sprites from The Question and Tutorial respectively. I've used cutin as the layer name, but this could be anything you like. I did use a Transform with crop rather than AlphaMask for expedience, but it should work very similarly.

The main points are much more control over the inside of the cut-in, in theory you could add other character sprites, effects, and vary them as your main scene progresses, or equally just set it up once and leave it alone. 🙂

Since the inside of the layer displayable can effectively be thought of as a secondary scene, all the features you mentioned such as config.adjust_attributes and Transforms should work as expected, and you can manage as many displayables as you like behind and in-front of your character sprites just like you would if it was not a cut-in at all - I've included a basic background and foreground in the example.

Quick cut-in demo using Layer displayable
# define new detached layer
define config.detached_layers += ['cutin']

# Use a window and transform to crop the Layer displayable and apply a white border
image cutin = Window(Transform(Layer('cutin'), crop=(0.3, 0.0, 0.4, 0.6)),
                     background='#fff', padding=(10, 0, 0, 10))

label start:
    scene bg uni
    show sylvie blue normal:
        align (0.2, 1.0)
    'Hi everyone!'

    # construct the cut-in scene
    scene expression '#488' as background onlayer cutin
    show eileen concerned onlayer cutin

    # add the cut-in to the main scene
    show cutin at topright with dissolve
    show sylvie surprised
    "Eileen" 'Excuse me!'

    # adjust the contents of the cut-in throughout the scene as desired
    show expression Solid('#844c', ysize=0.5, yalign=1.0) as foreground onlayer cutin
    "Eileen" 'Adding and removing things from the cutin is as easy as writing a normal scene!'

    # Since 8.1.0 sticky layers are default which means you can adjust
    # Eileen and Ren'Py will remember she's on the cutin layer.
    show eileen happy
    show sylvie giggle
    "Eileen" 'Okay, bye!'

    # We're done with the cut-in so we can hide it.
    hide cutin with moveoutright
    show sylvie smile
    "Sylvie" "Well that was different!"

    # Despite being hidden, the cut-in scene is still available, so it
    # can be hidden and redisplayed until explicitly cleared.
    show eileen vhappy
    show cutin at topright with dissolve
    show sylvie surprised
    "Eileen" "I'm back and didn't miss a beat!"
    "Sylvie" "!!!"

    # Now we're really done and after hiding the cut-in ...
    hide cutin with moveoutright
    # ... we can choose to explicitly clear it (or leave it until we set up a new scene later).
    scene onlayer cutin

    return

@renpytom
Copy link
Member

renpytom commented Jul 1, 2023

I don't think the Layer displayable is the right answer for this, long term. It's fine, as a short term thing, but I'm currently thinking about an overhaul of how displayables work, when it comes to parameterization, passing state from displayable to displayable, and so on.

(What we have with _duplicate is a giant mess, and so I'd like to properly fix it.)

@shawna-p
Copy link
Contributor Author

shawna-p commented Jul 1, 2023

Hmm, I appreciate you going through the trouble for the example. I will say I dislike the setup and syntax of such an approach however; rather than just showing the cutin like a normal sprite, it requires managing a layer which happens to have a cut-in image on it. Thus it doesn't easily map onto the usual way of showing sprite images in Ren'Py, which is something I'd like for this feature. For flexibility, I can appreciate that this functionality exists, but in the interest of making this sort of feature as easy as possible for non-coders to grasp, it lacks ease of use.

I think most people will approach it rather like a LayeredImageProxy, where you simply specify an additional attribute on the image and it'll use the transform (or in this case, AlphaMask) to display the layered image. If more displayables passed attributes along to their children, then the setup and scripting would closely resemble the existing LayeredImageProxy approach as well, which would make it easier to set up and use. If you'd like to avoid modifying so many displayables though, I think my initial approach allows for easy setup and use, though with less flexibility. If we do go with the former, I don't mind submitting this as an issue instead to track, as I feel such a change to the engine internals would require more review.

@Gouvernathor Gouvernathor added the enhancement A proposed or requested enhancement or improvement to renpy's features. label Jul 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement A proposed or requested enhancement or improvement to renpy's features.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants