Ren'Py Cookbook
Ren'Py Cookbook
Ren'Py Cookbook
Shake Effect
You can already use the default vpunch and hpunch transitions to shake the screen. But what
about a shake that goes in every directions randomly, Phoenix Wright style?
init:
python:
import math
class Shaker(object):
anchors = {
'top' : 0.0,
'center' : 0.5,
'bottom' : 1.0,
'left' : 0.0,
'right' : 1.0,
}
return renpy.display.layout.Motion(move,
time,
child,
add_sizes=True,
**properties)
Shake = renpy.curry(_Shake)
#
init:
$ sshake = Shake((0, 0, 0, 0), 1.0, dist=15)
Examples
show phoenix think with dissolve
ph "I think this game lacks a little something... Right! Some..."
ph "Objection!" with sshake # shaking the whole screen with the
previously defined 'sshake'
ph "Eheh... Uh? What the?! It's reverberating!"
show phoenix at Shake((0.5, 1.0, 0.5, 1.0), 1.0, dist=5)
with None
# shaking the sprite by placing it at the center (where it already was)
ph "Ng!..."
show phoenix at center, Shake(None, 1.0, dist=5)
with None
# exactly the same thing but it shows how you can first give a predefined
position and,
# giving None as a position for Shake, let it take 'center' as the shaking
position.
ph "AAgh! Stop it already!"
ph "...... Is it over?"
with Shake((0, 0, 0, 0), 3.0, dist=30)
# some serious shaking of the screen for 3 seconds
# try not to abuse high values of 'dist' since it can make things hard on
the eyes
Lip Flap
Sometimes, you want to synchronize a character's lip movements to her dialogue. That's what
Lip Flap is for.
First, download lip_flap.rpy, and add it to your game directory. This file contains the
definition of the LipFlap function. This function returns an object that can be used in a show
statement to produce lip flap.
prefix - The prefix of filenames that are used to produce lip flap.
combine - A function that combines its three arguments (prefix, lip, and suffix) into a
displayable. The default combine function is Image(prefix + lip + suffix). This could
be changed if you want to, say LiveComposite the lips onto a larger character image.
To use lip flap, first declare an image using LipFlap. Then show that image with a parameter
string consisting of alternating lips and delays. It will show the first lip, wait the first delay,
show the second lip, wait the second delay, and so on. If the string ends with a lip, it will
display that lip forever. If it ends in a delay, it will repeat after that delay has elapsed.
See Blink And Lip Flap for an example of combining this with character blinking.
Example
# Note that Ayaki_ShyA.png, Ayaki_ShyB.png, and Ayaki_ShyC.png all exist in
the
# game directory.
init:
image ayaki shy = LipFlap("Ayaki_Shy", "A", ".png")
label ayaki:
scene bg whitehouse
"..."
return
SnowBlossom
Function: SnowBlossom (image, count=10, border=50, xspeed=(20, 50), yspeed=(100, 200),
start=0, horizontal=False):
This implements the snowblossom effect, which is a simple linear motion up or down, left, or
right. This effect can be used for falling cherry blossoms, falling snow, rising bubbles, and
drifting dandelions, along with other things.
image - The image that is to be used for the particles. This can actually be any displayable, so
it's okay to use an Animation as an argument to this parameter.
count - The number of particles to maintain at any given time. (Realize that not all of the
particles may be on the screen at once.)
border - How many pixels off the screen to maintain a particle for. This is used to ensure that
a particle is not displayed on the screen when it is created, and that it is completely off the
screen when it is destroyed.
xspeed - The horizontal speed of the particles, in pixels per second. This may be a single
integer, or it may be a tuple of two integers. In the latter case, the two numbers are used as a
range from which to pick the horizontal speed for each particle. The numbers can be positive
or negative, as long as the second is larger then the first.
yspeed - The vertical speed of the particles, in pixels per second. This may be a single integer,
but it should almost certainly be a pair of integers which are used as a range from which to
pick the vertical speed of each particle. (Using a single number will lead to every particle
being used in a wave... not what is wanted.) The second number in the tuple should be larger
then the first.
start - This is the number of seconds it will take to start all particles moving. Setting this to a
non-zero number prevents an initial wave of particles from overwhelming the screen. Each
particle will start in a random amount of time less than this number of seconds.
fast - If true, then all particles will be started at once, and they will be started at random places
on the screen, rather then on the top or bottom.
horizontal - If true, the particles start on the left or right edge of the screen. If false, they start
along the top or bottom edge.
Example
init:
image blossoms = SnowBlossom(Animation("sakura1.png", 0.15,
"sakura2.png", 0.15))
show blossoms
Particle Burst
While SnowBlossom is useful for the common falling particles, you may want to have an
explosion of particles. Useful for when things are blowing up or simulating sparks from a
steel on steel impact.
init:
python:
theDisplayable The displayable or image name you want to use for your particles
explodeTime The time for the burst to keep emitting particles. A value of zero is no time limit.
numParticles The limit for the number of particle on screen.
Then you can just "show boom" to show the particle burst.
def createParticles(self):
timeDelay = renpy.random.random() * 0.6
return [ExplodeParticle(self.displayable, timeDelay)]
def predict(self):
return [self.displayable]
init:
python:
class ExplodeParticle:
init python:
#################################################################
# Here we use random module for some random stuffs (since we don't
# want Ren'Py saving the random number's we'll generate.
import random
#################################################################
# Snow particles
# ----------------
def Snow(image, max_particles=50, speed=150, wind=100, xborder=(0,100),
yborder=(50,400), **kwargs):
"""
This creates the snow effect. You should use this function instead
of instancing
the SnowFactory directly (we'll, doesn't matter actually, but it
saves typing if you're
using the default values =D)
# ---------------------------------------------------------------
class SnowFactory(object):
"""
The factory that creates the particles we use in the snow effect.
"""
def __init__(self, image, max_particles, speed, wind, xborder,
yborder, **kwargs):
"""
Initialize the factory. Parameters are the same as the Snow
function.
"""
# the maximum number of particles we can have on screen at once
self.max_particles = max_particles
return rv
def predict(self):
"""
This is called internally by the Particles object to predict
the images the particles
are using. It's expected to return a list of images to predict.
"""
return self.image
# ---------------------------------------------------------------
class SnowParticle(object):
"""
Represents every particle in the screen.
"""
def __init__(self, image, wind, speed, xborder, yborder):
"""
Initializes the snow particle. This is called automatically
when the object is created.
"""
# For safety (and since we don't have snow going from the floor
to the sky o.o)
# if the vertical speed of the particle is lower than 1, we use
1.
# This prevents the particles of being stuck in the screen
forever and not falling at all.
if speed <= 0:
speed = 1
# wind's speed
self.wind = wind
# particle's speed
self.speed = speed
# calculate lag
if self.oldst is None:
self.oldst = st
lag = st - self.oldst
self.oldst = st
Example
init:
image snow = Snow("snowflake.png")
label start:
show snow
"Hey, look! It's snowing."
Information Screen
This is a screen that can be used to display information ranging from stats to affection points
to clues to whatever you like, really. For this example, we will show affection points of two
love interests.
init:
$ bob_points = 0 # this is a variable for bob's affection points
throughout your game
$ larry_points = 0 # this is a variable for larry's affection points
throughout your game
$ bob_max = 10 # this variable should be set to bob's maximum affection
points
$ larry_max = 10 # this variable should be set to larry's maximum
affection points
$ variable = False # when false, the affection screen button doesn't
appear on the screen
In Screens.rpy
You can actually define this screen anywhere, but it might be easiest to group it with your
other screens.
screen button:
vbox xalign 0.1 yalign 0.1:
textbutton "Show affection points" action
ui.callsinnewcontext("aff_screen_label")
# you can also use an image button:
imagebutton:
idle "button_idle.png"
hover "button_hover.png"
action ui.callsinnewcontext("aff_screen_label")
The above code is for a button that will open up the stats/affection points screen when clicked.
Remember to change the image filenames and xalign/yalign coordinates for your own code. If
you don't want the button to show up all the time, you can do something like this:
screen button:
if variable:
vbox xalign 0.1 yalign 0.1:
textbutton "Show affection points" action
ui.callsinnewcontext("aff_screen_label")
# you can also use an image button:
imagebutton:
idle "button_idle.png"
hover "button_hover.png"
action ui.callsinnewcontext("aff_screen_label")
This makes it so that the button only shows up if "variable" is set to True. As for the actual
content of the stats/affection points screen:
screen aff_screen:
frame:
has vbox
text "Bob: [bob_points] points"
text "Larry: [larry_points] points"
textbutton "Return" action Return()
label aff_screen_label:
call screen aff_screen
return
This would show up as "Bob: ### points" and "Larry: ### points", with ### being whatever
number the variables are set to at that point in time. If you'd like to use bars instead of text
(useful for stats/affection points), you can use bars like so:
screen aff_screen:
frame:
has vbox
hbox:
label "Bob: " xminimum 100
bar range bob_max value bob_points xmaximum 400
hbox:
label "Larry: " xminimum 100
bar range larry_max value larry_points xmaximum 400
textbutton "Return" action Return()
label aff_screen_label:
call screen aff_screen
return
label start:
show screen button
# though we said to show it, the button won't show up until the
variable is set to True
"Blah blah blah!"
$ variable = True
# and now the button appears!
"Blah blah blah~"
$ variable = False
# and the button disappears again...
Customizing Textbox Appearance
Before we can explain how to customize your dialogue box, you need to design it first. The
easiest way to do this is to render a mock of a dialogue box in your photo editor. Get it to look
exactly how you want, text included. Even if it takes a little bit more work, it's possible to
recreate most mock-ups.
There are two ways you can save your design to be incorporated into Ren'Py. You can either
create a box that will be chopped up by the Frame code, or you can plug in the dialogue box
image directly by cropping it to be the width of your game engine.
Using frames
Dialogue boxes that use Frame look for the corners of the image, and then stretch whatever's
in between to fit the proportions you give it in the code.
This line of code is already in your options.rpy file, except with a filler file. Replace the name
and size of your corners with your image's info. Be sure to uncomment the code by removing
the # mark before it.
Of course, Ren'Py didn't automatically fix my margins or text for me! Straight out of a new
game, your new framed dialogue box will need a lot of tweaking. Most everything you need is
already there for you, but it is commented out.
Go to your options.rpy file. Find these lines of code:
# style.window.left_margin = 6
# style.window.right_margin = 6
# style.window.top_margin = 6
# style.window.bottom_margin = 6
# style.window.left_padding = 6
# style.window.right_padding = 6
# style.window.top_padding = 6
# style.window.bottom_padding = 6
# style.window.yminimum = 250
The Margin of the box is for how far away the dialogue box is from the edges of the screen.
This is what our PinkBox looks like with large margins (padding untouched):
Padding of the box is how far away the text is from the edges of the dialogue box. Here is
what is looks like with large padding (margin back to default):
yminimum forces the box to be a certain amount of pixels high. Use it to define how tall the
box must be at all times (but it can get taller if it needs to be). This is an important property
for the pre-fitted image dialogue boxes next.
With your new frame box in Ren'Py, it is up to you to play around with padding and margin
until it looks exactly how you want it to. Trial and error is sometimes the only path here. To
make your life easier, you can take advantage of the refresh feature by hitting SHIFT+R to
reload the game to see your changes immediately.
To make this easy on you, do NOT crop your image to just the dialogue box in your editor.
Instead, include the spaces around the edge (up to the top of the box) so that you don't have to
alter margins within Ren'Py. This means that your resulting image should be as wide as your
game (default: 800px).
Just insert your file name into the style.window.background property already in your
options.rpy
You'll need to make sure your yminimum is set to be the exact height of your image, and that
your padding is altered so that it fights into your box (again, trial and error might be the only
way). Set all your margins to 0. When you start your game, you should see your box! That
was easy, wasn't it?
If you want a separate window for your character names, you can either save a namebox as a
separate file (recommended) or include it in your dialogue box as one big image. The problem
with the latter is that you will need to make two versions of the same image: one with the
character box, and one without. Because the narrator (when no character is speaking) will
look weird with an empty name box lying around.
To make two (or more) different versions of your text box, put the one you're going to use the
most as the default:
style.window.background = "dialoguebox.png"
And then, for the characters you want to have a different box (in this case, the narrator
because it's not a character that has a name). You can use the Frame function with this, too.
Just copy whatever you did with the window.background code and put it after
window_background in your character name (changing the filename): Code:
It should retain all of the other properties you defined for the default window.
If you find you need to make specific adjustments for each character's text, you have to put
properties like these into your character calls:
$ a = Character('Name', window_top_padding=15)
$ b = Character('Name', what_xpos=50)
$ c = Character('Name', who_yalign=0.5)
The important parts of those code are the "window_", "what_" and "who_" prefixes. You can
use about any property you want, but you have to tell the character what you're editing.
"window_" is a property that edits the whole window, similar to what you did for
style.window. "what_" edits the text (what's being said) and "who_" edits the character's name
(who's saying it).
To save you keystrokes, you should format the default text style so you don't have to
copy/paste your adjustments to every character.
Styling Text
To edit how your text looks, look a little further down in your options.rpy for these lines of
code:
Uncomment those lines to change the font or size of the text. Even though it is not included in
your file, you can also change just about any aspect of the styles that you want. You can
change the color, add a drop_shadow, or an outline if you wanted. Here's an example of each:
In addition to those, you can use all of the text properties seen here.
To edit specific text, like the name of the character, you need to find out what that text's style
is called. An easy way to find styles is to launch your game and then hover your mouse over
the text you want to change. Hit the keys SHIFT+I and a black screen will come up with all
the styles and parent styles that object is using. The one that you want is the last style listed. If
you see this:
Then you want say_label. For styles you can't hover on, you can try to find in the Style
Hierarchy by hitting SHIFT+D.
Now that you have the style's name, you can plug it into this forumla:
style.STYLE_NAME.PROPERTY = VALUE
STYLE_NAME: This would be the name you found with the style inspector, like
say_label.
PROPERTY: What you want to change about that style. Again, you can find all the
kinds of properties here.
VALUE: Whatever you're setting the style property to. Numbers for sizes, strings
(things in quotes) for filenames, Hex values for colors, etc.
So if we wanted to edit say_label to be a different font, I would put this at the bottom of my
options.rpy:
style.say_label.font = "myfont.tff"
say_label
say_who_window (only if show_two_window is True)
To change the background of your namebox, you would do it exactly like how you did it for
the dialogue window, except you replace the style name with say_who_window:
Again, replace with your image and corner size, or don't even use a Frame at all!
Use positional properties like xalign or xpos and xanchor to place the name within the box
you have made.
Just link to the image's call name in quotes. If you want to animate it, build the animation
first, and then link to the animations call name. Also, you can have the CTC indicator
embedded into the text, placed right after the last word on the screen, or you can place it in a
fixed location on the screen. Here is an example of an animated CTC icon that is placed in the
corner of the screen (fixed):
You can use the new ATL language to make CTC animations, too. Just plug in the name in
the quotes. Make sure to put position properties into the image if you want it to be fixed on
the screen somewhere.
See this forum thread for an example of what it looks like all put together.
The following code must exist in one of your code files, preferably as a separate rpy file.
(X/yalign can be changed if desired...)
init python:
def time_callback():#constantly calculate the time
if (hasattr(store, 'minutes')):
if (store.minutes > 1440):
store.minutes = store.minutes - 1440
store.theweekday = store.theweekday + 1
store.theday = store.theday + 1
store.dayofyear = dayofyear + 1
if (hasattr(store, 'theweekday')):#setweekday
if store.theweekday > 7:
store.theweekday = store.theweekday - 7
if store.theweekday == 1:
store.stringweekday = "Sunday"
elif store.theweekday == 2:
store.stringweekday = "Monday"
elif store.theweekday == 3:
store.stringweekday = "Tuesday"
elif store.theweekday == 4:
store.stringweekday = "Wednesday"
elif store.theweekday == 5:
store.stringweekday = "Thursday"
elif store.theweekday == 6:
store.stringweekday = "Friday"
elif store.theweekday == 7:
store.stringweekday = "Saturday"
else:
store.stringweekday = "Error"
if (hasattr(store, 'theday')):#monthlim
if store.theday > store.daylim:
store.theday = store.theday - store.daylim
if (hasattr(store, 'themonth')):#setmonth
if store.themonth == 1:
store.stringmonth = "January"
store.daylim = 31
if store.themonth == 2:
store.stringmonth = "February"
if ((((int(store.theyear) / 4)*4) - store.theyear) == 0):
store.daylim = 29
else:
store.daylim = 28
if store.themonth == 3:
store.stringmonth = "March"
store.daylim = 31
if store.themonth == 4:
store.stringmonth = "April"
store.daylim = 30
if store.themonth == 5:
store.stringmonth = "May"
store.daylim = 31
if store.themonth == 6:
store.stringmonth = "June"
store.daylim = 30
if store.themonth == 7:
store.stringmonth = "July"
store.daylim = 31
if store.themonth == 8:
store.stringmonth = "August"
store.daylim = 31
if store.themonth == 9:
store.stringmonth = "September"
store.daylim = 30
if store.themonth == 10:
store.stringmonth = "October"
store.daylim = 31
if store.themonth == 11:
store.stringmonth = "November"
store.daylim = 30
if store.themonth == 12:
store.stringmonth = "December"
store.daylim = 31
def Calendar():
ui.frame(xfill=False, xminimum = 400, yminimum=None, xalign=1.0,
yalign = 0.805)
ui.vbox()
ui.text("(%s) - %s %d %d" % (stringweekday, stringmonth, theday,
theyear), xalign=1.0, size=20)
ui.close()
def Clocks():
ui.frame(xfill=False, xminimum = 110, yminimum=None, xalign=1.0,
yalign = 0.76)
ui.vbox()
if (minutes > 719):
if ((minutes - (int(minutes/60))*60) < 10):
if((int(minutes/60)) == 12):
ui.text("12:0%d PM" % ((minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
ui.text("%d:0%d PM" % ((int(minutes/60)-12), (minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
if((int(minutes/60)) == 12):
ui.text("12:%d PM" % ((minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
ui.text("%d:%d PM" % ((int(minutes/60)-12), (minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
if ((minutes - (int(minutes/60))*60) < 10):
if((int(minutes/60)) == 0):
ui.text("12:0%d AM" % ((minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
ui.text("%d:0%d AM" % ((int(minutes/60)), (minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
if((int(minutes/60)) == 0):
ui.text("12:%d AM" % ((minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
ui.text("%d:%d AM" % ((int(minutes/60)), (minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
ui.close()
screen say:
if(clock):
$ Calendar()
$ Clocks()
After you do this, all you have to do is add minutes on like so (in the actual game script):
1) You need to save as UTF-8 unicode. SciTE should do this by default when you save a
script, but you need to be aware of this.
2) You need to download a free Chinese or Japanese font, place it in the game directory, and
then tell Ren'Py to use it. You'll also want to set the language to "eastasian". For example, if
your font is mikachan.ttf, then you would write:
Code:
init:
$ style.default.font = "mikachan.ttf"
$ style.default.language = "eastasian"
If you do all both of these steps, then Ren'Py should support Chinese and Japanese text.
Oh, since Ren'Py handles all text rendering itself, it shouldn't matter if your Windows isn't
Chinese or Japanese. (The one issue being that renpy.input() is not supported for non-English
languages, and especially not for ones that require input method support.)
How To Use
1. Download this rpy file and put it in your project\game directory.
Function: hangulInput (prompt = 'What's your name?', default = "", length = 5):
Return two values. First one is the word inputted by user, and the other is 1 or 0, indicating if
the last word has final consonant or not(So one can use it to change postpositional particle
depending on the inputted text).
prompt - A prompt that is used to ask the user for the text.
default - A default for the text that this input can return.
allow - A string containing characters that are allowed to be typed into this input. (By default,
allow all characters.)
exclude - A string containing characters that are disallowed from being typed into this input.
(By default, "{}".)
Styles
The following styles are to customize hangul inputter's appearance.
style.input
Example
init:
$ style.input.color = '#f00'
transform a:
alpha 0
block:
linear 1.0 alpha 1
linear 1.0 alpha 0
repeat
screen hinput:
add HangulInput('이름을 적어줘!') align (.5, .5) at a
label start:
#python:
#ui.add(HangulInput(length=5))
#name, final=ui.interact()
'이름: %(name)s\n 받침유무: %(final)s'
init python:
showitems = True
def display_items_overlay():
if showitems:
inventory_show = "Inventory: "
for i in range(0, len(items)):
item_name = items[i].title()
if i > 0:
inventory_show += ", "
inventory_show += item_name
ui.frame()
ui.text(inventory_show)
config.overlay_functions.append(display_items_overlay)
##
$ items.append("stone") #when you want to add items
$ items.remove("stone")#when you want to remove items
$ showitems = False #when you don't want to show the inventory onscreen
(cutscenes and the like)
$ showitems = True #when you want to reshow the inventory after the
cutscene is over
Money/Affection
With some adaptation, we can also use this code to display the current amount of money, or
display an "Affection Meter" for a romantic interest.
init python:
showaffection= False
affection = 0
def display_affection():
if showaffection:
ui.frame() #This is optional. It adds a frame around the text.
ui.text("Affection: %d" %affection)
config.overlay_functions.append(display_affection)
###
init python:
showmoney= False
money = 0
def display_affection():
if showmoney:
ui.frame() #This is optional. It adds a frame around the text.
ui.text("$ %d" %money)
config.overlay_functions.append(display_money)
###
Fortunately it's easy to do, using DynamicCharacter. We just use a placeholder (which Ren Py
calls a "variable") and then change the meaning of this placeholder term when we want to.
label start:
# Set "maid_name" to mean something early on in the game.
# You don't have to use it right away.
$ maid_name = "Maid"
# ''now'' the variable "maid_name" exists - it's only created when you
set it.
The first three times that Millie speaks, she'll be identified as the "Maid", but after she's told
the player her name, she'll be shown as "Millie".
For this simple example it would be just as easy to use two different Characters and switch
between them after Millie has given her name. But imagine in a more complex game where
Millie only gives her name if the player decides to tip her. By using DynamicCharacter, you
don't have to think about whether the player chose to tip or not, the game will show "Millie"
or "Maid" as appropriate.
How to add an "about" item to the main menu
First, open up screens.rpy (which is included in all new projects), look for "screen
main_menu", add a line like the following in the list of all the other buttons:
screen about:
tag menu
window:
style "gm_root"
frame:
style "menu_frame"
xmargin 10
ymargin 10
has side "t c r b"
(See page history for an older version which doesn't use screen language.)
In-game Splashscreen
This special feature allows you to create a skippable set of image transitions, like an in-game
movie. This may save you from creating a MPEG flux if you don't feel like it or if you find it
too hard.
#Pause for 2 seconds, not necessary, but it's good for narration
transitions
$ renpy.pause(2.0)
#Play a tune
$ renpy.music.play('theme.ogg')
scene hello
with flash
with Pause(1.5)
scene world
with fade
with Pause(1.5)
scene hello
with Pan((0, 0), (800, 300), 1.5)
with fade
with Pause(2.0)
scene black
"Hello world ('_')/~"