Layered Images

When a sprite-set gets to a certain level of complexity, defining every possible combination may become unwieldy. For example, a character with 4 outfits, 4 hairstyles, and 6 emotions already has 96 possible combinations. Creating static images for each possible combination would consume a lot of disk space and programmer time.

To address this use case, Ren'Py introduces a way of defining an image consisting of multiple sprites, organized in layers. (For the purpose of this, consider layers to be the layers in a paint program like Photoshop or the GIMP, and not the layers used elsewhere in Ren'Py.) Elements of these layers can be selected by attributes provided to the image, or by conditions that are evaluated at runtime.

These images can be declared using the layeredimage statement, using a specific language. The LayeredImage() object is its Python alternative, it's not a displayable but can be assigned to an image statement and used like one.

The bottom of this page contains advice and examples of use.

Defining Layered Images

The language used to define layered images consists of only a few statements, to introduce the layers. Here is an example which, while not making much practical sense, is technically correct and outlines the layeredimage syntax:

layeredimage augustina:
    zoom 1.4
    at recolor_transform

    always:
        "augustina_base"

    attribute base2 default

    group outfit:
        attribute dress default:
            "augustina_dress"
        attribute uniform

    group face auto:
        pos (100, 100)
        attribute neutral default

label start:
    show augustina # displaying dress and neutral
    aug "I like this dress."

    show augustina happy # auto-defined in the auto group
    aug "But what I like even more..."

    show augustina uniform -happy # uniform replaces dress, neutral replaces happy
    aug "Is this uniform !"

Layeredimage

The layeredimage statements opens the show. The statement is part of the Ren'Py script language, and runs at init time. Like the Image Statement with ATL Block, it takes an image name and opens a block, although what's in the block differs greatly. The image name may contain spaces, just like any other image name in Ren'Py.

Inside the block will fit the statements described further down, as well as the following optional properties.

image_format

When a given image is a string, and this is supplied, the image name is interpolated into image_format to make an image file. For example, "sprites/eileen/{image}.png" will look for the image in a subdirectory of sprites. (This is not used by auto groups, which look for defined images and not for image files.)

format_function

A function that is used instead of layeredimage.format_function() to format the image information into a displayable, during the image definition at init time.

attribute_function

A function or callable that is used to tweak what attributes end up being displayed. It is called with a set of attributes supplied to the image, and should return the set of attributes that should be used to select layers. It can be used to express complex dependencies between attributes, or to select attributes at random. See Selecting attributes to display for more information about when and how this is called.

at

A transform or list of transforms that are applied to the layered image.

transform properties

If given, these are used to construct a transform that is applied to the displayable.

offer_screen

If this is True, the layeredimage will place its children, and size its children with variable size, like it was given an area matching the whole screen of the game. If it is False, the said behaviors will be done while taking into account the available area, which for example will be smaller in an hbox containing other elements, and the display of the layeredimage will not be consistent every time it is shown.

If None, the default, falls back to config.layeredimage_offer_screen, which defaults to True.

Always

The always statement declares an image that is always shown inside the layeredimage, and which will not be attached to an attribute. It must be supplied a displayable, and can also take properties. Both can be placed on the same line or inside a block.

The always statement takes the following properties:

if_all

A string or list of strings giving the names of attributes. If this is given, this layer is only displayed if all of the named attributes are present.

if_any

A string or list of strings giving the names of attributes. If this is given, this layer is only displayed if any of the named attributes are present.

if_not

A string or list of strings giving the names of attributes. If this is given, this layer is only displayed if none of the named attributes are present.

transform properties

If given, these are used to construct a transform that is applied to the displayable.

at

A transform or list of transforms that are applied to the provided displayable.

If

The if statement (or more fully the if-elif-else statement) allows you to supply one or more conditions that are evaluated at runtime. Each condition is associated with a displayable, with the first true condition being the one that is shown. If no condition is true, the else layer is shown if given.

A more complete example of an if statement might look like:

if glasses == "evil":
    "augustina_glasses_evil"
elif glasses == "normal":
    "augustina_glasses"
elif glasses == "funky":
    "augustina_glasses_clown"
else:
    "augustina_nose_mark"

Each clause must be given a displayable. It can also be given these properties:

if_all

A string or list of strings giving the names of attributes. If this is given, this condition is only considered if all of the named attributes are present.

if_any

A string or list of strings giving the names of attributes. If this is given, this condition is only considered if any of the named attributes are present.

if_not

A string or list of strings giving the names of attributes. If this is given, this condition is only considered if none of the named attributes are present.

transform properties

If present, these are used to construct a transform that is applied to the displayable.

at

A transform or list of transforms that are applied to the displayable.

The if statement is transformed to a ConditionSwitch() when the layeredimage statement runs.

layeredimage.predict_all = None

Sets the value of predict_all for the ConditionSwitches produced by layeredimages' if statements.

When predict_all is not true, changing the condition of the if statement should be avoided while the layered image is shown or about to be shown, as it would lead to an unpredicted image load. It's intended for use for character customization options that don't change often.

Attribute

The attribute statement adds a displayable that is part of the resulting image when the given attribute is used to display it. For example, using the previous example, calling show augustina dress will cause the "augustina_dress" to be shown as part of the "augustina" image.

An attribute clause takes an attribute name, which is one word. It can also take two keywords. The default keyword indicates that the attribute should be present by default unless an attribute in the same group is called. The null keyword prevents this clause from getting attached a displayable, which can be useful for bookkeeping and to build conditional display conditions using if_all, if_any, if_not, attribute_function, config.adjust_attributes or config.default_attributes.

The same attribute name can be used in multiple attribute clauses (and in auto-defined attributes as part of auto groups, more about that later), with all the corresponding displayables being shown at the same time (the if_all, if_any, and if_not properties can tweak this).

If the displayable is not explicitly given, it will be computed from the name of the layeredimage, the group (if any), the group's variant (if any), and the attribute. See the pattern section for more details.

The attribute statement takes the following properties:

if_all

A string or list of strings giving the names of attributes. If this is present, this layer is only displayed if all of the named attributes are present.

if_any

A string or list of strings giving the names of attributes. If this is present, this layer is only displayed if any of the named attributes are present.

if_not

A string or list of strings giving the names of attributes. If this is present, this layer is only displayed if none of the named attributes are present.

transform properties

If present, these are used to construct a transform that is applied to the layer.

at

A transform or list of transforms that are applied to the layer.

The if_* clauses' test is based upon the list of attributes of the resulting image, as explained here, but it does not change that list.

layeredimage eileen:
    attribute a
    attribute b default if_not "a"
    attribute c default if_not "b"

In this example, the b and c attributes are always part of the attributes list (because of their default clause). When calling show eileen a, the a attribute will be displayed as requested, and the b attribute will not, due to its if_not property. But even if not displayed, the b attribute will still be part of the attributes list, which means the c attribute will still not display.

Group

The group statement groups attributes together, making them mutually exclusive. Unless the group is multiple, when attributes a and b are in the same group, it is an error to include both of the attributes at the same time, with show eileen a b for example. In the same example, calling attribute a will hide attribute b, and vice versa. However, note that it's fine for several attribute clauses to be passed the same name, even within the same group. In that case, they will be considered as one attribute containing several sprites - more about that at the end of this section.

The group statement takes a name. The name isn't used for very much, except to generate the default names of attributes inside the group. That is not the case for multiple groups in which the name doesn't have any use or impact.

The name may be followed by the auto keyword. If it's present, after any attributes in the group have been declared, Ren'Py will scan its list of images for those that match the group's pattern (see below), with the specificity that in that case, a multiple group's name is part if the pattern, and that the format_function passed to the layeredimage is ignored. Any images that are found, except those corresponding to explicitly declared attributes, are then added to the group as if declared using the attribute statement inside the group's block. See the Examples section for a practical demo.

This can be followed by the multiple keyword. If present, no incompatibility is applied to the attributes declared inside the block. This is useful to have a group auto-define multiple attributes that are not exclusive, or to apply the same properties to a set of attributes at once. This conflicts with the default keyword being given to one of the attributes. Note that multiple groups are very different from other, normal groups, and that most of what's true about groups doesn't apply to them.

After these optional keywords, properties can then be declared on the first line of the group, and it can take a block containing properties and attributes.

The group statement takes the properties attribute does - such as if_any, at and so on. Properties supplied to the group are passed to the attributes inside the group, unless overridden by the same property of the attribute itself. In addition, there are two properties which are specific to groups:

variant

If given, this should be a string. If present, it adds an element that becomes part of automatically-generated image names, and of the pattern used to search for images when automatically defining attributes in auto groups.

prefix

If given, this is a prefix that is concatenated using an underscore with the manually or automatically defined attribute names. So if prefix is "leftarm", and the attribute name "hip" is encountered, show eileen leftarm_hip will display it.

An attribute may also be part of several groups, in which case the attribute is incompatible with every other attribute in every group it's part of. This can be useful for example for a dress attribute, to make it hide both any top and any pants that may be showing when it gets displayed:

layeredimage eileen:
    attribute base default
    group bottom:
        attribute jeans default
        attribute dress null
    group top:
        attribute shirt default
        attribute dress

When several group blocks with the same name are defined in the same layeredimage, they are considered to be different parts of a single group. For example:

layeredimage eileen sitting:
    attribute base default
    group arms variant "behind":
        attribute on_hips
        attribute on_knees
        attribute mixed
    attribute table default
    group arms variant "infront":
        attribute on_table default
        attribute holding_margarita
        attribute mixed

In this example, eileen_sitting_arms_behind_mixed.png will contain her left arm behind the table, and eileen_sitting_arms_infront_mixed.png will contain her right arm on the table. When calling show eileen sitting mixed, the two images will be shown at the same time, respectively behind and in front of the table. In this example, the on_hips attribute is incompatible with the on_table attribute, because even though they are not declared in the same block, they are both in the same group.

Pattern and format function

The pattern, used to find images for attributes when they are not explicitly given one, consists of:

  • The name of the layeredimage, with spaces replaced with underscores.

  • The name of the group, if any and if the group is not multiple.

  • The name of the variant, if there is one.

  • The name of the attribute.

all combined with underscores. For example, if we have a layered image with the name "augustina work", and the group "eyes", this will match images that match the pattern augustina_work_eyes_attribute. With a variant of blue, it would match the pattern augustina_work_eyes_blue_attribute. In the following example:

layeredimage augustina work:
    group eyes variant "blue":
        attribute closed

The attribute is linked to the image "augustina_work_eyes_blue_closed". That can resolve to an image file named augustina_work_eyes_blue_closed.png, but it can also be defined explicitly using the Image Statement for example.

If you want a multiple group's name to be included in the pattern, you can use the following syntax:

group addons multiple variant "addons"

All of the pattern behavior can be changed using a format_function: layeredimage.format_function() is the function used under the hood to implement the behavior described above. You can see what arguments it takes, in case you want to supply your own format_function to replace it.

layeredimage.format_function(what, name, group, variant, attribute, image, image_format, **kwargs)

This is called to format the information about an attribute or condition into a displayable. This can be replaced by a creator, but the new function should ignore unknown kwargs.

what

A string giving a description of the thing being formatted, which is used to create better error messages.

name

The name of the layeredimage.

group

The group of an attribute, None if not supplied or if it's part of a condition.

variant

The variant argument to the group, or None if it is not supplied.

attribute

The attribute itself.

image

Either a displayable or string.

image_format

The image_format argument of the LayeredImage.

If image is None, then name, group (if not None), variant (if not None), and attribute are combined with underscores to create image, which will then be a string.

If images is a string, and image_format is not None, image is formatted into the string to get the final displayable.

So if name is "eileen", group is "expression", and attribute is "happy", image would be set to "eileen_expression_happy". If image_format is "images/{image}.png", the final image Ren'Py finds is "images/eileen_expression_happy.png". But note that it would have found the same image without the format argument.

Proxying Layered Images

Sometimes, it can be useful (and even necessary) to proxy a layered image, to use the same layered image in multiple places. One reason for this would be to have a transformed version of a given layeredimage, while another would be to use it as a side image.

The LayeredImageProxy() object does this, taking one layered image and duplicating it somewhere else. For example:

image dupe = LayeredImageProxy("augustina")

creates a duplicate of the image that can be displayed independently. This also takes a transform argument that makes it useful to position a side image, like this:

image side augustina = LayeredImageProxy("augustina", Transform(crop=(0, 0, 362, 362), xoffset=-80))

See the difference:

image sepia_augustina_one = Transform("augustina", matrixcolor=SepiaMatrix())
image sepia_augustina_two = LayeredImageProxy("augustina", Transform(matrixcolor=SepiaMatrix()))

sepia_augustina_one will be a sepia version of the original version of the "augustina" layeredimage, in other words what's shown when you don't provide it any attribute. On the contrary, sepia_augustina_two will take any attribute "augustina" does, and then apply the sepia effect onto the result. If you can do this:

show augustina happy eyes_blue dress

then:

show sepia_augustina_one happy eyes_blue dress
# won't work, because Transform doesn't take attributes

show sepia_augustina_two happy eyes_blue dress
# will work, and show "augustina happy eyes_blue dress" in sepia effect
class LayeredImageProxy(name, transform=None)

This is an image-like object that proxies attributes passed to it to another layered image.

name

A string giving the name of the layeredimage to proxy to.

transform

If given, a transform or list of transforms that are applied to the image after it has been proxied.

Selecting attributes to display

Several factors influence what gets displayed following a given Show Statement. To provide more clarity as to what happens in which order, this section showcases the life of a set of attributes, from the show statement to the on-screen display.

  • The show statement provides the initial set of attributes, following the image tag.

  • If a config.adjust_attributes function exists to match the image tag, it is called, and returns a potentially different set of attributes. If so, it replaces the former set, which is forgotten.

  • If a config.default_attribute_callbacks function exists and if its trigger conditions are met, it is called and potentially adds attributes to the set.

The previous stages are not specific to layeredimages, because it is only after this stage that renpy determines which image or layeredimage will be called to display. For that reason, the given set of attributes must lead to one, and only one, defined image (or layeredimage, Live2D...), using the behavior described in the show statement section.

  • Then, the provided attributes are combined with the attributes defined in the layeredimage, discarding some previously shown attributes and conserving others. This is also the point when unrecognized attributes are detected and related errors are raised. If no such error is raised, the new attributes, along with those which were not discarded, will be recognized by renpy as the set of attributes associated with that image tag. This computing takes some of the incompatibility constraints into account, but not all. For instance incompatibilities due to attributes being in the same non-multiple group will trigger at this point in time, but the if_any/if_all/if_not clauses will not. That's why an attribute called but negated by such a clause will be considered active by renpy, and will for example become visible without having to be called again, if at some point the condition of the if_x clause is no longer fulfilled.

  • If an attribute_function has been provided to the layeredimage, it is called with the set of remaining attributes. It returns a potentially different set of attributes.

  • This set is once again confronted with the incompatibility constraints of the layeredimage, this time in full. That is the final stage, and remaining attributes are called into display.

Advice

Use underscores in image filenames.

By default, Ren'Py's layered images use underscores to separate sections of image names. It might be tempting to use images with spaces between sections, but that could lead to problems later on.

Ren'Py has a rule that if you show an image with the exact name as one that's being shown, it's shown instead. This can bypass the layered image you defined and show the sprite directly on its own, which can lead to weird problems like a pair of eyes floating in space.

By having each sprite have a different tag from the main image, this is no longer a problem.

Cropping layers isn't necessary.

Ren'Py optimizes images by cropping them to the bounding box of the non-transparent pixels before loading them into RAM. As a result, assuming the images are being predicted properly, it generally won't improve performance or image size much to crop the images yourself.

Layered images shouldn't use data that changes at runtime.

Note that with the exception of the conditions in the if statement, all expressions written in a layeredimage block are evaluated at init time, when the layered image is first defined. This is not the case for ATL transforms for example, or for anything occurring in config.adjust_attributes, config.default_attributes or attribute_function, but it is the case for format_function which is also only called at layeredimage definition.

Choosing what syntax to use

If you want a sprite to be always visible, use either the always clause or the attribute x default syntax. always will require you to provide the displayable explicitly (automatic attribution using the pattern will not be available), but attribute will spend the "x" attribute name which will always be active for that layeredimage.

If you want it to appear depending on the attributes being passed to the layeredimage at the moment of the show statement, for example show eileen happy instead of show eileen jeans, use the attribute statement, in or out of a group block (or implicitly defined in an auto group).

If you want it to appear depending on a python variable or condition, use the if statement.

If you want it to depend on both (for example for show eileen ribbon to show either a blue or red ribbon depending on a variable, but no ribbon appearing unless you ask for it with the ribbon attribute), declare all versions as attributes and use a dedicated config.adjust_attributes function.

Examples

Pattern and auto groups

From the following files in the images/ directory (or one of its subfolders) and written code:

francis_base.png
francis_face_neutral.png
francis_face_angry.png
francis_face_happy.png
francis_face_very_happy.png
francis_face annoyed.png
francis_supersad.png
layeredimage francis:
    attribute base default
    group face auto
        attribute neutral default
    attribute supersad:
        Solid("#00c3", xysize=(100, 100))

The francis layeredimage will declare the (defaulted) base attribute, and associate it the "francis_base" (auto-defined) image using the pattern : the layeredimage name ("francis"), the group name (none here), the variant name (none here) and the attribute name ("base"), separated with underscores.

Then, in the face group, the explicit neutral attribute gets associated the "francis_face_neutral" image, following the same pattern but using "face" as the group name and "neutral" as the attribute name.

After all explicit attributes receive their images, face being an auto group, existing images (auto-defined or not) are scanned for a match with the pattern. Here, three are found : "francis_face_angry", "francis_face_happy" and "francis_face_very_happy". They are associated with the angry, happy and very_happy attributes respectively, using the same pattern as before. No annoyed attribute is defined however, since the "francis_face annoyed" image contains a space where the pattern expected an underscore.

Finally, the supersad attribute is declared, but since a displayable is explicitly provided, the pattern does not look for a matching image.

The "francis_supersad" and "francis_face annoyed" images get auto-defined from the filename as part of Ren'Py's ordinary protocol, but these sprites don't find a match with any attribute or auto group, so they end up not being used in the francis layeredimage.

As you can see, using the pattern to associate images to attributes and using auto groups shrinks the code considerably. The same layeredimage would have taken 13 lines if everything was declared explicitly (try it!), and this syntax allows for geometric growth of the sprite set - adding any number of new faces wouldn't require any change to the code, for example.

Dynamism in attributes

Here is an example for defining attributes depending on variables (as mentioned in the Advice section):

layeredimage eileen:
    attribute base default
    group outfit auto
    group ribbon prefix "ribbon":
        attribute red
        attribute blue

default eileen_ribbon_color = "red"

init python:
    def eileen_adjuster(names):
        atts = set(names[1:])
        if "ribbon" in atts:
            atts.remove("ribbon")
            atts.add("ribbon_" + eileen_ribbon_color)
        return names[0], *atts

define config.adjust_attributes["eileen"] = eileen_adjuster