The set is simple : you have a character called Phoenix, which is created with a layeredimage because that's a really awesome and powerful thing.
Your character is fairly simple, with two outfits, 8 faces, a blush overlay and a sweat/embarassed overlay.
Let's say you want to let the user customize some aspect of the character, typically its gender.
This could be asked to the user directly via a prompt or binary choice, or more indirectly via some questionnaire (like in todo:today)... doesn't really matter, the point is, it's decided based upon the state of a python variable.
If it were like having the first wristband or the second, you could implement it like :
Code: Select all
layeredimage phoenix:
if phoenix_wristband==1:
"phoenix_wristband_green.png"
elif phoenix_wristband==2:
"phoenix_wristband_yellow.png"
So, how would you make such conditional ?
First of all, a disclaimer :
It is (almost) impossible to make a layeredimage react in real time when the underlying variable changes. That's not covered in this tutorial.
One possible answer would be a (not so) clever use of LayeredImageProxy. Little is known (and for good reasons) about its capacity to handle square-brackets string substitution.
So, you would define two layeredimages, mphoenix and fphoenix, with the same exact structure and taking the same exact attributes, a control variable, and a layeredimage making the switch between the two based upon the state of the control variable.
This would look like that :
Code: Select all
layeredimage fphoenix:
zoom .5
group base auto:
attribute default default
attribute blush
group face auto:
attribute smile default
attribute sweat
layeredimage mphoenix:
zoom .5
group base auto:
attribute default default
attribute blush
group face auto:
attribute smile default
attribute sweat
default phoenixgender = "m"
image phoenixproxy = LayeredImageProxy("[phoenixgender]phoenix")
As you can see, each layeredimage has to have the same structure, dimensions and all because when our code will call it, it won't know which of mphoenix or fphoenix will appear, so treatment of both need to be as identical as possible. For that reason, their attributes need to be exactly the same, or it will cause issues in renpy.
This could seem fair and square, but two issues arise :
- The square-brackets substitution feature of LayeredImageProxy is not documented, so it may break unexpectedly between a version of renpy and the other, breaking your game along the way.
- This "works" with one variable having two possible values, but what about managing both gender, body shape, and, say, favorite outfit like in Arcade Spirits 2, each parameter having repercussions on several sprites ? Will you define genders*shapes*outfits different layeredimages, each time repeating the attribute which are in common between versions ?
And here comes the adjust_attribute part.
Adjust_attributes allows you (in the case of layeredimages) to compute, add, filter, change, what attributes a layeredimage will be called with, when the show instruction is executed.
This means you can communicate to your layeredimage (we'll show exactly how later) whether you want the male version or the female version, based upon the control variable, each time phoenix is called to be displayed !
How is that communicated ? Using attributes. The adjust_attributes function, when calling the phoenix (layered)image, will add, say, a m attribute or a f attribute, based upon the state of the control variable (or computed in any other way really). We'll see what the phoenix layeredimage does with this later.
So, you need to define your adjust_attributes function :
Code: Select all
init python:
def phoenix_adjust_attributes(name):
names = set(name[1:])
if phoenix_is_male:
names.add("m")
else:
names.add("f")
return (name[0],)+tuple(names)
default phoenix_is_male = False
define config.adjust_attributes["phoenix"] = phoenix_adjust_attributes
The details of how the function works :
The name parameter, unlike what it seems, is a tuple of strings. If the image is called with show phoenix a b c, name will be ("phoenix", "a", "b", "c").
Name[0] is not interesting since it will always be "phoenix". The order is also non-important, because renpy doesn't care about it. Doubles are not important in the case of layeredimages.
Don't care about doubles ? No order ? Let's make it a set ! And to avoid mixing our two variables, let's call it names.
The next part is simple, if phoenix is male, tell the layeredimage to use the "m" attribute, otherwise tell it to use the "f" one. That's how we insert our message to the layeredimage. We could also have used the old phoenixgender variable and added it instead... doesn't really matter, there are several ways to do it.
Then, we reformat it as a tuple, while readding name[0] at the beginning, because we need to return the same kind of data we received.
So, now that it's done, what's left ? The layeredimage itself of course !
This time, we're going to use only one layeredimage, and no layeredimageproxy. That means our sprites will need to be renamed, as to all start with "phoenix" - and not "fphoenix" or "mphoenix", that wouldn't work (well actually it could work but it would break the auto behavior of layeredimages, wich would be a pita, so let's not do that).
We will rename them as phoenix_[group]_[f/m]_[attribute] - you'll see exactly why later.
So, let's sum up. We have different sets of sprite files, which match a more limited set of attributes. How do you change the image name while respecting the auto feature and not changing the attribute name ? Variants !
Here is the actual code for the layeredimage :
Code: Select all
layeredimage phoenix:
zoom .5
group gender:
attribute m null default
attribute f null
group base auto variant "m" if_any "m":
attribute default default
group base auto variant "f" if_any "f":
attribute default default
group face auto variant "m" if_any "m":
attribute smile default
group face auto variant "f" if_any "f":
attribute smile default
group m multiple variant "m" if_any "m":
attribute blush
attribute sweat
group f multiple variant "f" if_any "f":
attribute blush
attribute sweat
If we had other parameters, like "shape" (for body shape) for example, then we would have added another group with all-null attributes, and if_all/if_any conditions based upon the name of those attributes as well.
Each group now has two versions. Each group will use the files named after its variant - and that's why the f/m was placed as it is in the filenames. But we want a group to display only when it's supposed to be, a.k.a when the attribute of the gender of the variant is enabled ! So we add an if_any (but it could be an if_all as well, since there's only ever one of such control attributes to check) to the group, as a condition.
The last part, with the multiple groups, is an trick. Naively, you would have written something like
Code: Select all
attribute blush if_any "f":
"...f_bplush.png"
attribute blush if_any "m":
...
attribute sweat if_any "f":
...
attribute sweat if_any "m":
...
You could name the attributes f_blush, m_blush and so on, and make the switch in adjust_attributes, but it would be painful.
It's more simple to use multiple groups, which don't call the name of the group as a part of the image name in the auto behavior - hence why the filename is "phoenix_f_blush.png" and not "phoenix_f_f_blush.png". Multiple groups can have several of their attributes called to display at the same time, and they are actually a sneaky way to add a variant and/or an if_any/if_all clause to one or several attributes at once. So that's basically what we're doing here.
Voilà ! I think you're ready to use dynamic-ish (see my disclaimer above) layeredimages in renpy !
If you have any question about my explainations or if it's unclear, feel free to ask for them in replies ! If I saved your project, you can drop my name in the credits <3 but don't feel compelled to.
You can use BoysLaugh+'s sprites included here to test the tutorial, but, as said above, you need to ask for the boyz' permission to do anything other than local tests with them, such as including them in games, or distributing them.