Renpy Portrait System v2.X

Version 2.4a

Overview

This is version 2.0 of my Renpy Portrait System. It takes the base premise of my original code and refines it. As there's some major design direction changes from the original, I'm presenting it on its own page here.

Originally, the portrait system had fixed portrait and eye/mouth sizes, as well as storing them in single files. I did this as I felt it would be more convenient, but it immediately became a problem when I wanted to make subtle expression changes. Thus an important difference with v2.0 is it separates portraits into separate files for base, eyes, and mouth. This version also does away with the restraints caused by height limitations and simplifies some arguments.

Features

  • Define character portraits that'll animate with a single line.
  • Text-blip effect tied to characters, refined to avoid looping sounds.
  • Ability to set custom overrides for eye/mouth frames.

Syntax Overview

image tamati neutral = Portrait("tamati", "neutral", (298, 209), (339, 277))

This is a basic example for defining a character portrait. You have your image name, the Portrait() definition, and its arguments:

  • The image directory, often doubling as the character name,
  • Base body expression/filename,
  • And then eyes and mouth co-ordinates.

By default, portraits are stored under images/char/character name/, using the syntax expression_base.png, expression_eyes.png, and expression_mouth.png. So the example above would be stored under images/char/tamati/ and the images would be prefixed with neutral.

You're not limited to using one expression with one base portrait, however.

image tamati scowl = Portrait("tamati", "neutral", (298, 209, "scowl"), (339, 277))

In this example, the eyes co-ordinates have been appended with scowl. Thus, it'll use scowl_eyes.png with a neutral base portrait.

The demo below goes over things in more detail, as well as functions like static eye expressions and custom idle mouth frames.

Installation

To add to your project, you will need _portraitsys.rpy (or the code it contains) and to make the text in your say screen into BeepyText with beepytext what id "what"

Download and Credits

Download Demo (v2.4a)

Credit should be given to myself - Aether.

Special thanks also to Bryan Tsang and Kistaro for their help in getting the code to this point.

Changelog

(2.4a) Saturday, 7th March 02026
  • Moved some variables to a configuration store at the top of the script for ease of use.
  • Fixed blink timer not re-rolling between blinks.
(2.4) Saturday, 28th February 02026
  • Complete refactoring of the Portrait class thanks to help from Kistaro.
  • Portrait is now correctly Flattened.
  • Text now requires use of a BeepyText screen element.
    • This was done to have it play a repeated sound rather than a loop, so it'll slow down if the text itself is slower, and does not play when the menu is open.
    • In the future I would like to pause the dialogue text while the menu is open, making the pause of the sound mean the text is actually paused, but this is technically a personal secondary desire for aesthetic reasons.
  • Speaker definition now a single tuple argument to create less cruft.
  • Moved all relevant code, like defining the text noise channel, to the _portraitsys.rpy script.
  • Due to the degree to which the code has been changed from the initial version in 2014, I have taken it upon myself to change credit to Tsang as special thanks.
  • Some minor updates to the demo.
  • Updated to Renpy version 8.5.2.
(2.0a) Sunday, 12th December 02021
  • Updated the demo to clarify possible obtuse explanations of speaker.
  • Updated to Renpy version 7.4.11.
(b0.9) Thursday, 21st November 02019
  • Initial release.

  • Saturday, 7th March 02026