* Automatically generate side images (which appear next to the speaker)
* Crop those side images into a circle
It'll help if you know a bit about images, side images and even layered images - but I'll walk you through it all from scratch.
Side Image Basics
Side images are great: they can be used to automatically show a picture of the speaking character next to the dialog window. Like a little portrait of the speaker. There's a bit of set up to do - but after that, the side image will appear magically when needed, and will use the same tags as the main speaker, so expressions and outfits and stuff will all sync.
Usually, side images are created as separate images. You'd create a main sprite called "sylvie smile.webp", and a smaller side image called "sylvie smile side.webp". Ren'Py will find this side image and show it next to the speaker for you.
Now, the thing is, I love laziness and I hate duplication. So I don't want to manually create two versions of every character pose/expression/outfit. I want to just define one sprite set and use Ren'Py's excellent functionality to automate the generation of side images. And guess what - I can!
So, I'm going to use transforms, and later layered images, to reduce the amount of manual effort I have to put into my code.
Transforming Side Images
In my game, I want to show a side image in the bottom left of the screen, slightly offset from it's default position. Something like this:
To get this positioning, I've actually had to tweak the say statement in screens.rpy. You may or may not need to do this in your project. Here's my say statement code - I've literally only changed the xoffset down on the last line:
Code: Select all
screen say(who, what):
style_prefix "say"
window:
id "window"
if who is not None:
window:
id "namebox"
style "namebox"
text who id "who"
text what id "what"
## If there's a side image, display it above the text. Do not display on the
## phone variant - there's no room.
if not renpy.variant("small"):
add SideImage() xoffset 60 yalign 1.0 #< ----- I've edited here
Code: Select all
define s = Character("Sylvie", image="sylvie")
image side sylvie normal = Transform("sylvie normal", crop=(80,0,160,180), zoom=1.2)
image side sylvie smile = Transform("sylvie smile", crop=(80,0,160,180), zoom=1.2)
label start:
scene bg uni
show sylvie normal
s "I'm so happy!"
show sylvie smile
s "I have a circular side image!"
jump start
This is mostly standard Ren'Py such as code you'll find in the side images docs. I've defined a character and associated an image with it. I've defined side images with different tags. I've used show and say statements to display my game.
The magic is that I've used a Transform in my image statement. The transform will take whichever image I'm pointing at - in this case, my full-sized image of sylvie - and, well, transform it. In this case, I'm zooming it up and cropping it down to only show the character's face. Ren'Py supports many transforms - you've probably used a few. Here's a full list in docs.
The Crop part is a little complicate. It takes a tuple with 4 values: (x, y, width, height). Imagine the crop as a rectangular area drawn inside the original image. The x and y represent the top-left corner of that rectangle. Width and height then define how long and tall the crop area should be.
Working out your crop square isn't easy. I know it's tempting but don't try and guess it. Load the image up into an image editor and use that to work out the bounds of the crop. I use the GIMP to draw a rectangular selection, and read the right values back out from there.
Radial Crops
In my game, I wanted a radial crop of the side image. I.e, I want to see a circle of the face, not a square.
Ren'Py doesn't support a radial crop transform. But Unsleppen[url], my new personal hero, wrote me one! H ... all layers plugin.
Here's a really simple layered image:
Code: Select all
layeredimage eileen:
group everything:
attribute happy "eileen happy"
attribute concerned "eileen concerned"
Code: Select all
image side eileen = LayeredImageProxy("eileen",
[
Transform(
crop=(50, 20, 220, 220),
xoffset=-20
),
Transform(
shader="circle_crop",
mesh=True
)
]
)
Like before, I'm using two transforms which makes life a bit easier. I've also put an offset on my transform here to alter it's position in the side image - this is straight up a hack for the tutorial.
And that's it! Once I've set up my side images to proxy and crop my main sprite images, everything in the game just works. Any changes via a show statement are instantly and automatically reflected in both the main sprite and the side image, for a more immersive experience all around.