Professional Documents
Culture Documents
Renpy Cookbook PDF
Renpy Cookbook PDF
1
Renpy Cookbook
Contenido
Springy movement ............................................................................................................ 5
General information ...................................................................................................... 5
Avoiding singularities .............................................................................................. 5
What ρ does .............................................................................................................. 6
What μ does .............................................................................................................. 6
Simple form .................................................................................................................. 6
Optimizing ................................................................................................................ 7
Complex form ............................................................................................................... 7
Showing and Hiding the Window .................................................................................... 9
Fullscreen game run Setting ........................................................................................... 10
Preloader Image .............................................................................................................. 11
Splashscreen Effect......................................................................................................... 12
Simple Flash Effect ........................................................................................................ 14
Double Vision Effect ...................................................................................................... 16
Examples .................................................................................................................... 18
Lip Flap .......................................................................................................................... 19
Example ...................................................................................................................... 19
Blink And Lip Flap ..................................................................................................... 20
Showing layered sprites with different emotions ....................................................... 22
Particle Burst .................................................................................................................. 23
Realistic Snowfall Effect ................................................................................................ 27
Example ...................................................................................................................... 30
Adding a simple and somewhat functioning Analog Clock ....................................... 31
Menu Buttons ................................................................................................................. 35
Examples .................................................................................................................... 35
Creating Your Own Buttons ....................................................................................... 36
Centered Window ........................................................................................................... 38
Censoring for wider-audience ........................................................................................ 39
Name Above the Text Window ...................................................................................... 40
Menu Positions ............................................................................................................... 42
RPG frame ...................................................................................................................... 43
In the init section ........................................................................................................ 43
In the game script ....................................................................................................... 43
Auto-read Setting............................................................................................................ 45
Default text speed Setting ............................................................................................... 46
The tile engine and unit engine....................................................................................... 47
2
Renpy Cookbook
3
Renpy Cookbook
Example ...................................................................................................................... 98
Unarchiving files from rpa ........................................................................................... 100
Example .................................................................................................................... 100
Who's that? Changing character names during the game ......................................... 101
Conditional Hyperlinks ................................................................................................ 102
Timed menus ................................................................................................................ 103
UI Widgets .................................................................................................................... 105
Summary................................................................................................................... 105
Warning ................................................................................................................ 105
The parser ................................................................................................................. 105
Defining tags ............................................................................................................ 107
Usage example.......................................................................................................... 108
Compatibility considerations .................................................................................... 109
Interpolation ......................................................................................................... 109
Escape sequences .................................................................................................. 109
Space compacting ................................................................................................. 109
Multiple argument tags ......................................................................................... 110
How to add an "about" item to the main menu............................................................. 111
Additional basic move profiles ..................................................................................... 112
How to use these functions ....................................................................................... 112
Quadratic motion ...................................................................................................... 113
Exponential decay..................................................................................................... 114
Handling the extra parameter ............................................................................... 116
Optimizing ............................................................................................................ 116
Power functions ........................................................................................................ 117
Animation “bop” ...................................................................................................... 118
Konami Code ................................................................................................................ 120
In-game Splashscreen ................................................................................................... 122
Runtime init blocks................................................................................................... 123
Importing scripts from Celtx ........................................................................................ 128
Letting images appear after a while .......................................................................... 130
How to add ambient noises to your game for greater immersion ................................. 131
4
Renpy Cookbook
Springy movement
If you want to add some spring to your moves, the following code might be
what you’re looking for. It’s based on the under-damped response to a step
input, which is gradually decaying oscillations. I’m going to offer two
different ways of doing it – one simple and one more complex that offers
more options.
General information
The equation used is basically x(t) = 1 - ℯ-ρtcos(μt) (which is then divided by a
scaling factor). It takes two parameters that you can use to tweak the
motion: ρ and μ.
Avoiding singularities
You can set these two parameters to any values that you like, as long as the
following equation isn’t true:
ℯρ = cos(μ)
This is because of the scaling factor used, and it means that you have
selected values of ρ and μ that leave you back where you started when the
springing is done. You’ll know if you’ve picked this combination if you get a
divide by zero error, but the only way you can manage that is if ρ is less than
5
Renpy Cookbook
or equal to zero. So as long as you keep ρ greater than zero, you won’t have
to worry about that.
What ρ does
ρcontrols the decay rate, or how fast the bounces dissipate. The higher you
set ρ, the faster the bounciness vanishes as it settles. If you set ρ too low, it
will still be bouncing at the end of the Move, and it will seem to come to an
abrupt halt.
If you make ρ large: The motion will start more abruptly and the
bouncing will end more quickly.
If you make ρ small, but greater than zero: The motion will start more
smoothly and the bouncing will take longer to end (and might not end
before the time’s up, which will mean an abrupt finish).
If you make ρ zero or negative: The motion will start smoothly but the
bouncing will get bigger or stay the same size instead of getting
smaller. You also run the risk of a singularity.
What μ does
μcontrols the bounce frequency, or how many bounces happen during the
move.
If you make μ large (positive or negative): The motion will start more
abruptly and you will get more bounces.
If you make μ small (positive or negative) or zero: The motion will
start more smoothly, and you will get less bounces (or none at all).
Simple form
The simplest way to use the bounce formula is as a time warp function to
Move.
import math
springy_time_warp = renpy.curry(springy_time_warp_real)
6
Renpy Cookbook
Optimizing
If you have selected a ρ and μ that you like, and you don’t intend to change
them, you can simplify the equation a bit to get a speed increase. The best
way to do that is to start out with the following code:
import math
rho = 5.0
mu = 15.0
scale_factor = 1.0 - math.exp(-rho) * math.cos(mu)
def springy_time_warp(x):
return (1.0 - math.exp(-rho * x) * math.cos(mu * x)) / scale_factor
...
Use that code during development, so you can tweak ρ and μ until you’re
ready to release. Then replace ρ and μ with your chosen values, and scale_factor
with 1 – ℯ–ρcos(μ).
Complex form
Now the simple form does the job most of the time, but it has some
limitations. The nature of the motion means that it can be pretty abrupt
when it starts for certain values. You can use time warp functions to smooth
this out, but not with the simple form. Another benefit of the complex form
is that it is already optimized, and doesn’t require editing before release.
class UnderdampedOscillationInterpolater(object):
anchors = {
'top' : 0.0,
'center' : 0.5,
'bottom' : 1.0,
'left' : 0.0,
'right' : 1.0,
}
import math
7
Renpy Cookbook
if len(start) != len(end):
raise Exception("The start and end must have the same number of arguments.")
import math
rv = (b - a) * t + a
8
Renpy Cookbook
init python:
show_window_trans = MoveTransition(0.25,
enter_factory=MoveIn((None,
1.0, None, 0.0)))
hide_window_trans = MoveTransition(0.25,
leave_factory=MoveOut((None,
1.0, None, 0.0)))
def hide_window():
store._window_during_transitions = False
narrator("", interact=False)
renpy.with_statement(None)
renpy.with_statement(hide_window_trans)
def show_window():
narrator("", interact=False)
renpy.with_statement(show_window_trans)
store._window_during_transitions = True
Use it like:
$ hide_window()
$ show_window()
e "We're here!"
9
Renpy Cookbook
config.default_fullscreen = False
to
config.default_fullscreen = True
10
Renpy Cookbook
Preloader Image
To create a Preloader Image (placeholder image show while Ren'py is reading
the scripts and launching the game), make an image named presplash.png,
and put that image into the game directory.
Note: Ren'Py takes longer to load the first time after the script changes then
it does later on. Game should be run twice in a row to see how long it takes
the second time.
11
Renpy Cookbook
Splashscreen Effect
An example of a splashscreen effect can be found at the end of the
"script.rpy" file in the demo/game directory of Ren'py :
To add a text splashscreen to your game, insert code like this into anywhere
in your script file (as long as it is not itself in a block):
label splashscreen:
$ renpy.pause(0)
scene black
show text "American Bishoujo Presents..."
with dissolve
with Pause(1.0)
hide text
with dissolve
return
init:
image splash = "splash.png"
label splashscreen:
$ renpy.pause(0)
scene black
with Pause(0.5)
show splash
with dissolve
with Pause(2.0)
scene black
with dissolve
12
Renpy Cookbook
with Pause(1.0)
return
You need to declare the image in an init block (which can be anywhere in
your script file, either before or after the splashscreen code). You must also
declare another interaction before the scene transition, which is why $
renpy.pause(0) must exist as the first thing in the splashscreen label.
13
Renpy Cookbook
This effect can be used for transitions such as flashbacks, light saturation
effects and much more.
init:
image bg road = "road.jpg"
label start:
scene black # Or whatever image you're transitioning from.
scene bg road
with flash
"Wowsers!"
I first saw this effect done in a game called Moe: Moegi Iro No Machi, as a
transition between character images. I found it to be pretty cool, so I tried to
emulate it in Ren'Py. It didn't take long; it's pretty simple. This code was
tested and confirmed to work on Ren'Py versions 5.6.1 through 5.6.4, but it
should work on any version 5.0 or higher release.
This image should be placed in your game or data folder, whichever you use
for your game data.
This code will work for the demo script included with Ren'Py. It can very
easily be modified to fit your game.
init:
$ noisedissolve = ImageDissolve(im.Tile("noisetile.png"), 1.0, 1)
label start:
scene bg washington with fade
show eileen vhappy with noisedissolve
14
Renpy Cookbook
That's all there is to it. It's very simple to implement, and looks cool too.
Please note that this effect is not limited to character images; you can show
and hide any image or scene with it.
15
Renpy Cookbook
The basic idea is that you create a half-opaque version of the background,
and then show it at a random location after showing the background. That
means that unlike many effects, invoking this effect during your story
requires two statements, not just one.
init:
python hide:
import random
args = [ ]
if i == j:
continue
label start:
scene cs2
show cs2alpha at randmotion
16
Renpy Cookbook
In this code, the statements which actually produce the effect during the
story are:
scene cs2
show cs2alpha at randmotion
As you may have noticed, for every image that you want to show in double
vision, you'll have to create two images for it as well. In the code above,
these are the lines which create the images (note that like all image
statements, they must be placed in an init block):
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,
}
17
Renpy Cookbook
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
18
Renpy Cookbook
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
19
Renpy Cookbook
"..."
return
This.
init python:
# Displays speaking when the named character is speaking, and done otherwise.
def WhileSpeaking(name, speaking_d, done_d=Null()):
return DynamicDisplayable(curried_while_speaking(name, speaking_d, done_d))
if event == "show":
speaking = name
elif event == "slow_done":
speaking = None
elif event == "end":
speaking = None
20
Renpy Cookbook
init:
scene black
show girl
"Not speaking."
girl "Now I'm speaking. Blah blah blah blah blah blah blah."
girl "Now I'm speaking once again. Blah blah blah blah blah blah blah."
21
Renpy Cookbook
Then this is for you! Welcome to the world of LiveComposite and dynamic
displayables using ConditionSwitch! You'll only need to declare something
like "$ Janetmood = mad" to change the emotion on her face or the pose and
any number of fun things.
label start:
#This is how you call it during the game itself
show eileen
#This shows the LiveComposite "e.png" when e_face == None. We haven't changed it yet.
e "I'm neutral."
#Now we change the variable to happy.
$ e_face = "happy"
e "Now sprite is happy!"
#You can also declare ConditionSwitches in Character statements to
#change the side image.
22
Renpy Cookbook
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.
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)]
23
Renpy Cookbook
def predict(self):
return [self.displayable]
init:
python:
class ExplodeParticle:
if self.xPos > 1.05 or self.xPos < -1.05 or self.yPos > 1.05 or self.yPos < -1.05:
return None
Open your favourite image editor of choice (mine is Photoshop), and make a
new canvas with the size of the screen you're using for your game (let's say
800x600 is today's default). Use paint bucket to paint the canvas white, in
case you created a transparent canvas. Now, we need a function called "Add
Noise", so find it in your filter toolbar. Use monochromatic noise, and for
these images I used uniform distribution with values around 50% (changed
+/- .01 for each of the images). I got five images.
Now, as you see, each of them is a bit different than the other. Perfect! What
we need to do next, is put all these in an anim.SMAnimation function to do
us good. So let's define all five states and all the edges to make this
animation fluid and non-stop. Like so...
24
Renpy Cookbook
Now that's some typing right there! :D I wanted to do dissolves rather than
just showing images, that would look too coarse. This looks perfect, even
though it might be a tad memory consumptive. If the static changes too slow
for your taste, change the delay of the edge (the first number before "trans")
to a lower value, like .15 or .1
To use this, you just call it like this whenever you wish to show static in-
game.
show static
One last thing - remember that these images (if you followed my instructions
blindly) are opaque, so nothing below the static will be visible! To change
that, you can:
25
Renpy Cookbook
So this was my first cookbook recipe, hope you like it! Thanks goes to FIA and
PyTom for correcting the script, now everything will work if you use opacity
changes with im.Alpha (or use transparent .pngs, but as I said, that's less
flexible).
26
Renpy Cookbook
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)
# ---------------------------------------------------------------
27
Renpy Cookbook
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
# the maximum depth of the screen. Higher values lead to more varying particles size,
# but it also uses more memory. Default value is 10 and it should be okay for most
# games, since particles sizes are calculated as percentage of this value.
self.depth = kwargs.get("depth", 10)
# We expect that particles falling far from the screen will move slowly than those
# that are falling near the screen. So we change the speed of particles based on
# its depth =D
depth_speed = 1.5-depth/(self.depth+0.0)
28
Renpy Cookbook
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
# The last time when this particle was updated (used to calculate the unexpected delay
# between updates, aka lag)
self.oldst = None
29
Renpy Cookbook
# calculate lag
if self.oldst is None:
self.oldst = st
lag = st - self.oldst
self.oldst = st
# verify if the particle went out of the screen so we can destroy it.
if self.ypos > renpy.config.screen_height or\
(self.wind< 0 and self.xpos < 0) or (self.wind > 0 and self.xpos > renpy.config.screen_width):
## print "Dead"
return None
Example
init:
image snow = Snow("snowflake.png")
label start:
show snow
"Hey, look! It's snowing."
30
Renpy Cookbook
But first, copy these images below to your game directory (or wherever you
save your images)
init python:
renpy.image("clock.png") # Short Clockhand
renpy.image("clock_1.png") # Long Clockhand
renpy.image("clock_2.png") # Clockface
def Clocks():
if clock: # if False - clock is hide
ui.at(Position(xpos=150, ypos=140, xanchor="center", yanchor="center"))
ui.add("clock_2")
# xalign and yalign can be replaced by xpos and ypos - position where the center of the clock
should be
# this segment is the one responsible for the clockface
config.overlay_functions.append(Clocks)
OK, so it's not like your standard Analog Clock. It will behave like an Analog
Clock, it will rotate like an Analog Clock but you have to dictate the actual
time to be displayed...
31
Renpy Cookbook
HOW TO USE:
After putting the previous code inside your game script. You will now have to
write something like this (see below code)
init:
$ clock = False
The code above declares the clock so Ren'py can use it. The "False" tells
Ren'py "not" to show the Analog Clock by default. Why is that?
Well, you don't want the Analog Clock to show in your screen instantly do
you? Like you have a very dramatic scene and there is this Analog Clock
dangling on the top left side of the screen. What you and me want is to
"dictate" when will the clock show.
label start:
"What time is it?"
"Let's look at the clock."
$ clock = True
with dissolve
The line [ with dissolve ] tells Ren'py to "show" the clock with a "Dissolve"
transition. Pretty cool eh?... You could also replace "Dissolve" with other
transitions for maximum joy.
To hide the clock again just use [ $ clock = False ] and to hide it with
transition add the line [ with dissolve ] below it.
Of course, for this Analog Clock to be useful you have to be able to set the
time and dictate the time. But before that, please copy the code below in
your game script.
32
Renpy Cookbook
init:
$ minutes = 0
So what is this? You see, this is the code where you dictate the starting time
of the clock... in this case 0 minutes or 12:00 Midnight. If you wanted to
change the starting time to 9:00AM you will have to calculate how many
minutes have passed from 12:00 Midnight till 9:00AM... in this case it's 540
minutes so you will have to write the above code like this (see below)
init:
$ minutes = 540
Let's say you are in the middle of the game and your current time is set at
540 (9:00AM) and you wanted to add 2 minutes to the clock. You will have to
write something like this (see below)
$ minutes += 2
The code above adds additional two minutes to the current time count and
the analog clock will now display the time 9:02AM.
Here is an good example of how to use Analog Clock in actual. In this code
we assume you set your clock to 9:00AM or [$ minutes = 540]
label start:
$ clock = True
with dissolve
$ clock = False
with dissolve
33
Renpy Cookbook
So the above code will show the analog clock with dissolve at 9:00AM then it
will move by two minutes, and another two minutes, and another tow
minutes.
For your convenience, I also add a sort of guide to make adding minutes a lot
easier.
# 12:00 MN - 0
# 1:00 AM - 60
# 2:00 AM - 120
# 3:00 AM - 180
# 4:00 AM - 240
# 5:00 AM - 300
# 6:00 AM - 360
# 7:00 AM - 420
# 8:00 AM - 480
# 9:00 AM - 540
# 10:00 AM - 600
# 11:00 AM - 660
# 12:00 NN - 720
# 1:00 PM - 780
# 2:00 PM - 840
# 3:00 PM - 900
# 4:00 PM - 960
# 5:00 PM - 1020
# 6:00 PM - 1080
# 7:00 PM - 1140
# 8:00 PM - 1200
# 9:00 PM - 1260
# 10:00 PM - 1320
# 11:00 PM - 1380
34
Renpy Cookbook
Menu Buttons
This is a recipe that you can use to make buttons appear on the main screen.
These buttons can act as shortcuts to the game menu, toggles for skipping or
other user preferences, or in order to bring up an inventory menu.
init python:
def toggle_skipping():
config.skipping = not config.skipping
show_button_game_menu = True
def button_game_menu():
if show_button_game_menu:
# to save typing
ccinc = renpy.curried_call_in_new_context
config.overlay_functions.append(button_game_menu)
Examples
If you want the buttons to be shown on the screen all the time, you can
simply copy the above code into a .rpy file.
If you want to disable the buttons, such as during a minigame, you can write,
for example,
label minigame:
35
Renpy Cookbook
All of this needs to go inside a "init python:" block, which you create by
typing "init python:" into one of you script files.
init python:
# Give us some space on the right side of the screen.
style.window.right_padding = 100
To create a variable that determines whether or not the button is shown, you
need to first decide on an appropriate name. A good name would be one
that describes what the button does; for example, in the example code, the
variable was named "show_button_game_menu" because the buttons
formed shortcuts to the game menu and the variable determines whether or
not the menu should be shown. If you wanted a button that shows an
inventory when clicked, a good variable name would be
"show_inventory_button."
After you decide on the name, you have to decide whether or not you want
the button to be shown at the beginning of the game, or if you want the
button to be unlocked later. For the game menu example, it is a good idea to
show the buttons starting from the beginning of the game. However, if you
want to create a button that goes to a list of spells in the player's spellbook,
and you don't let the player learn spells until halfway through the game, it
would be a good idea to unlock the button later.
36
Renpy Cookbook
If you want to show the button from the beginning of the game, you would
write "your_variable_name = True," and if you would write
"your_variable_name = False"
init python:
# Give us some space on the right side of the screen.
style.window.right_padding = 100
init python:
# Give us some space on the right side of the screen.
style.window.right_padding = 100
def my_button_function():
if your_variable_name:
ui.vbox(xpos=0.99, ypos=0.98, xanchor='right', yanchor='bottom')
ui.textbutton("Button Name", clicked=do_something, xminimum=80)
ui.close()
init python:
# Give us some space on the right side of the screen.
style.window.right_padding = 100
def my_button_function():
if your_variable_name:
ui.vbox(xpos=0.99, ypos=0.98, xanchor='right', yanchor='bottom')
ui.textbutton("Button Name", clicked=do_something, xminimum=80)
ui.close()
config.overlay_functions.append(my_button_function)
37
Renpy Cookbook
Centered Window
The following code should be put in one of your RPY files, preferably
options.rpy. If you are not sure where to put it, place it at the very end of
your file.
This forces the main program window to be centered. Without this code, the
placement of the window is left up to the OS.
38
Renpy Cookbook
init python:
if persistent.hentai:
# Let's get it on.
else:
# Holding hands is more than enough.
39
Renpy Cookbook
The following code is PyTom's code, pulled from the Lemmasoft forum.
init:
$ e = Character("Eileen", show_two_window=True)
$ ui.saybehavior()
$ ui.interact(suppress_overlay=True, suppress_underlay=True)
return
# This is called when the quick load button is clicked, to load the
# game.
label _quick_load:
$ renpy.load('quicksave')
return
init -1:
python hide:
40
Renpy Cookbook
config.overlay_functions.append(quick_save_button)
41
Renpy Cookbook
Menu Positions
This shows how to reposition the elements of the main menu. Elements of
the game menu can be repositioned in a similar way. Use the style inspector
(shift+I with your mouse over a displayable) to figure out which styles to use.
style.mm_button["Preferences"].xpos = 500
style.mm_button["Preferences"].ypos = 500
style.mm_button["Quit"].xpos = 550
style.mm_button["Quit"].ypos = 550
42
Renpy Cookbook
RPG frame
This is a frame you can use for your RPG project if you intend to create a
hybrid project (renpy+minigames).
ui.text(name, size=20)
ui.close()
ui.close()
ui.close()
ui.close()
label fight:
python:
charmax_HP = 1000
char_HP = 1000
tigermax_HP = 2000
43
Renpy Cookbook
tiger_HP = 2000
while True:
while tiger_HP >= 1000:
tiger_HP = tiger_HP - 10
stats_frame("Tiger", 4, tiger_HP, tigermax_HP, xalign=0.75, yalign=0.0)
stats_frame("Hero", 1, 86, 86, xalign=0.0, yalign=0.0)
renpy.pause(0.05)
break
"Tiger" "Gao gao! You're strong!"
44
Renpy Cookbook
Auto-read Setting
To enable and set the delay of the auto-read mode at the first run of your
game (instead of going in the preferences menu), add to your script :
init:
if not persistent.set_afm_time:
$ persistent.set_afm_time = True
$ _preferences.afm_time = 10
This will change the setting to 10 the first time the player runs the game. It
will then store the fact that it has done so into game/saves/persistent, so if
the user changes the setting, it won't be reset.
Maximum setting is 41 for the default Menu Preference User Interface, but it
can be set higher in your script file. Minimum setting is 1 and will make your
game show all lines of text with no interruption.
45
Renpy Cookbook
config.default_text_cps = 0
Replace 0 with the number of characters that should be shown per second.
Valid numbers are in the range 1-100, with 0 being special-cased as infinite
speed. (Note that this is somewhat odd... 0 is infinitely fast, 1 is very slow,
and 100 is very fast.)
46
Renpy Cookbook
This includes v1.0 of the Tile Engine and Unit Engine, as well as four
extensive demos that show what's possible and how to do it, as well as
providing a number of sample isometric and square tiles. (Note that
the performance will be very bad if you try to use the game directory
in versions of Ren'Py earlier than 6.6.1.)
The TileEngine is a Ren'Py object you can use to represent and display a map
that's built out of tiles. It'll do the following:
Define Unit objects, which are Sprites with a bunch of extra properties.
Let the player move Units around the map by clicking or by keyboard
control.
Calculate which squares the unit can reach within its allocated
Movement or Action Points, with capability for different units having
different costs to move through different types of terrain.
Calculate the route and do the movement step by step when you or
the player move a unit to a given square. If you supply animations for
the unit moving in each of the eight directions, it'll use them.
Let the player select Actions that units can take, from a list you supply
- for example, attacking or healing other units, or perhaps
transforming the map square they're on.
47
Renpy Cookbook
The UnitEngine works either with "Movement Points" or "Action Points", and
you specify which one you want to use when you create it. In the Movement
Points (MP) system, each unit gets to move a certain number of spaces per
turn, and also gets a specified number of actions per turn (usually 1). In the
Action Points (AP) system, movement and action both come from the same
allocation of Action Points each turn.
It has a Callback system so that your own Ren'Py code can be executed
whenever the user selects a unit, moves a unit, or takes an action.
Restrictions:
Known bugs
General
48
Renpy Cookbook
I've been using this for testing purposes and it meets my needs. It may be a
useful recipe to start from for anyone doing a similar task. This is written to
follow the conventions of my project's "real" engine (KeyLimePie), so you
may want to tweak it to suit your needs. My project expects to present
options along the 8 cardinal directions ("n", "s", "nw", ...) and one "center"
direction, for a simple 3x3 grid of choices. The code below replaces the built-
in menu with a 3x3 grid menu. It expects menu choices to be prefixed by the
direction in lowercase and a colon (ex: "se: Some Choice"). It expects all
choices have a direction prefix, and it "stacks" direction choices so the
direction always shows just the first applicable option in the list of choices.
The menu styling is fairly basic and not always great, but as I said I mostly use
this just for testing. I'm also not sure the checkpoint code is correct (for
save/load) as I haven't actually needed it.
I placed this code directly in the top init block of script.rpy, but it should work
just fine in its own file (may need an "early" keyword, though).
python:
## KeyLimePie Menu Powers Activate! ##
style.create('keylimepie_menu_choice', 'default', 'KeyLimePie menu choice')
style.create('keylimepie_menu_choice_button', 'default', 'KeyLimePie menu button')
style.keylimepie_menu_choice.layout = "subtitle"
style.keylimepie_menu_choice.xmaximum = 0.3
style.keylimepie_menu_choice.size_group = None
style.keylimepie_menu_choice_button.xmaximum = 0.3
style.keylimepie_menu_choice_button.size_group = None
# Roll forward
roll_forward = renpy.roll_forward_info()
choices = {}
for label, value in items:
pieces = label.split(':', 1)
dir, label = pieces[0], pieces[1].strip()
if dir not in choices:
choices[dir] = (label, value)
renpy.ui.window(style=style.menu_window)
renpy.ui.grid(3, 3)
for dir in ('nw', 'n', 'ne', 'w', 'center', 'e', 'sw', 's', 'se'):
49
Renpy Cookbook
if dir in choices:
renpy.ui.textbutton(choices[dir][0],
# style=style.menu_choice_button,
text_style=style.keylimepie_menu_choice,
clicked=renpy.ui.returns(choices[dir][1]),
focus=None,
default=None,
)
else:
renpy.ui.text() # TODO: Disabled choices?
renpy.ui.close()
renpy.shown_window()
rv = renpy.ui.interact(mouse='menu', type='menu', roll_forward=roll_forward)
renpy.checkpoint(rv)
return rv
menu = keylimepie_menu
50
Renpy Cookbook
In-game Messages
If you want to have an in-game messages system - perhaps email, or a
collection of letters, or something like that - then the following code provides
a base to work from, or can be used as-is.
To use the system, copy and paste the following into a new .rpy file in your
game directory:
init python:
# Message Styles
style.messageWindow = Style(style.window)
style.messageColumns = Style(style.hbox)
style.messageListBox = Style(style.vbox)
style.messageListViewport = Style(style.viewport)
style.messageButton = Style(style.button)
style.messageButtonText = Style(style.button_text)
style.messageScrollBar = Style(style.vscrollbar)
style.messageBodyScrollBar = Style(style.vscrollbar)
style.messageBodyBox = Style(style.vbox)
style.messageBodyViewport = Style(style.viewport)
style.messageText = Style(style.say_dialogue)
style.messageControls = Style(style.hbox)
style.messageWindow.ymaximum = 600
style.messageColumns.spacing = 10
style.messageListViewport.xminimum = 280
style.messageListViewport.xmaximum = 280
style.messageListBox.yalign = 0.0
style.messageButton["Message"].xfill = True
style.messageButton["CurrentMessage"].xfill = True
style.messageButtonText["Message"].color="#99A"
style.messageButtonText["CurrentMessage"].color="#FFF"
style.messageBodyViewport.xminimum = 460
style.messageBodyViewport.xmaximum = 460
style.messageBodyViewport.ymaximum = 550
style.messageBodyScrollBar.ymaximum=550
style.messageControls.spacing = 10
def init_messages():
51
Renpy Cookbook
def show_messages():
message = None
def show_message_ui(currentMessage):
init_messages()
messageCount = count_messages()
ui.window(style=style.messageWindow)
ui.hbox(style=style.messageColumns) # For the three columns of controls
vp = ui.viewport(style=style.messageListViewport)
ui.window(style=style.messageListBox)
ui.vbox() # For the mail list
index = 0
for message in store.messages:
if (message[3] == None or eval(message[3]) == True):
styleIndex = "Message"
if (index == currentMessage):
styleIndex = "CurrentMessage"
ui.button(clicked=ui.returns(index),
style=style.messageButton[styleIndex])
ui.text(message[0] + " - " + message[1], style=style.messageButtonText[styleIndex])
index = index + 1
ui.bar(adjustment=vp.yadjustment, style=style.messageScrollBar)
ui.window(style=style.messageBodyBox)
ui.vbox() # For the right-hand stuff; message and buttons
ui.hbox()
vp2 = ui.viewport(style=style.messageBodyViewport)
ui.close()
52
Renpy Cookbook
ui.button(clicked=ui.returns(-1),
style=style.messageButton["Close Messages"])
ui.text("Close Messages", style = style.messageButtonText["Close Messages"])
if hasMessage:
ui.button(clicked=ui.returns(-2),
style=style.messageButton["Delete Message"])
t = ui.text("Delete Message", style = style.messageButtonText["Delete Message"])
result = ui.interact()
def count_messages():
init_messages()
return len(store.messages)
def count_visible_messages():
init_messages()
count = 0
Example Usage
init:
$ e = Character('Eileen', color="#c8ffc8")
$ show_messages()
e "See?"
53
Renpy Cookbook
$ show_messages()
$ add_message("My Holiday Photos", "Bob", "Drone drone drone drone drone drone drone " +
"drone drone drone drone drone drone drone drone drone drone drone drone drone drone " +
"drone drone drone drone drone drone drone drone drone drone drone drone drone drone " +
"drone drone drone drone drone drone drone drone drone drone drone drone drone drone " +
"drone drone drone drone drone drone drone drone drone drone drone drone drone drone " +
"drone drone drone drone drone drone drone drone drone drone drone drone drone drone " +
"drone drone drone drone drone drone drone drone drone drone drone drone drone drone " +
"drone drone drone drone drone drone drone drone drone drone drone drone drone drone " +
"drone drone drone drone drone drone drone drone drone drone drone drone drone drone " +
"drone drone drone drone drone drone drone drone drone drone drone drone drone drone " +
"drone drone drone drone drone drone drone drone drone drone drone drone drone drone...")
$ add_message("Where are you?", "Simon", "You've not been on FaceSpace for years! I mean,"
" like, {i}ten minutes{/i}, dude! Where are you?!")
$ add_message("Buy Stuff", "Mississippi", "Did you know that people who bought books in the"+
" past also bought books? How about you buy some books?\n"+
"Here are some books you might like:\n- {i}To Murder A Coot{/i}\n"+
"- {i}The Girl with the Pirate Tattoo{/i}\n- {i}9Tail Cat{/i}")
$ add_message("Tuesday", "Mum", "Did you remember you were coming to visit on Tuesday?" +
" I hope you didn't forget.")
$ add_message("Re: Tuesday", "Mum", "You haven't called. Are you still coming?"+
" I'm making pie.")
$ add_message("Re: Re: Tuesday", "Mum", "Why don't you let us know whether you're coming?" +
" Have you checked your mail? Are you OK? I hope you haven't got into a car crash or" +
" something. You see them on the news all the time. Are you really OK?" +
" Please, let us know!")
$ show_messages()
python:
spam_filter = False
#spam...
for x in range(20):
add_message("Billions of dollars!", "Prince Terence of Nigeria", "I am the" +
" custodian of the Nigerian Central Bank, and I need a foreign account to" +
" place billions of dollars in for a period of two (2) months for no apparent" +
" reason. You could earn millions in interest, just mail me your credit card" +
" and PIN today!",
condition="spam_filter == False")
$ show_messages()
$ spam_filter = True
$ show_messages()
54
Renpy Cookbook
$ message_count = count_messages()
$ visible_count = count_visible_messages()
e "And after all that, you now have %(message_count)s messages,
of which you can see %(visible_count)s."
55
Renpy Cookbook
There are two steps you need to perform to get Ren'Py to support Chinese or
Japanese language text:
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.
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.)
56
Renpy Cookbook
init -3 python:
if persistent.lang is None:
persistent.lang = "english"
lang = persistent.lang
This code will set the default language to "english". It also stores the current
language, whatever it's set to, into the lang variable, so that it can be accessed
by init code.
Language Chooser
The language chooser allows the user to select a message. It's simply a
Ren'Py menu, where each menu option sets persistent.lang to the
appropriate value. The code following the menu then causes Ren'Py to
restart, re-running init code.
label language_chooser:
scene black
menu:
"{font=DejaVuSans.ttf}English{/font}":
$ persistent.lang = "english"
"{font=mikachan.ttf}日本語{/font}":
$ persistent.lang = "japanese"
$ renpy.utter_restart()
Note that this is just normal Ren'Py code, and so it's possible to change the
scene command to display a different image.
57
Renpy Cookbook
As we'll want the user to be able to change the language from the main
menu, we add a menu option to config.main_menu.
init python:
config.main_menu.insert(3, (u'Language', "language_chooser", "True"))
Finally, we may want to automatically display the language chooser the first
time the game starts up. This can be done by conditionally jumping to
language_chooser from the splashscreen, using the following code:
label splashscreen:
if not persistent.chose_lang:
$ persistent.chose_lang = True
jump language_chooser
return
Language-Dependent Initialization
The lang variable can be used during initialization to change styles, images,
configuration variables, and other properties in accordance with the chosen
language.
init python:
if lang == "english":
style.default.font = "DejaVuSans.ttf"
e = Character("Eileen")
# ...
config.translations["Language"] = u"言語を選択"
e = Character(u"光")
# ...
58
Renpy Cookbook
init:
if lang == "english":
image bg city = "city_en.jpg"
elif lang == "japanese":
image bg city = "city_jp.jpg"
Multi-Path Translation
label start:
$ game_lang = lang
if lang == "english":
jump start_en
else:
jump start_jp
label start_en:
"It was a dark and stormy night..."
# ...
label start_jp:
"それは暗い嵐の夜だった..."
e "しかし、我々の中なので、大丈夫だ。"
# ...
Note that the multi-path translation lets you use the same characters (in this
case, the narrator) in both languages.
59
Renpy Cookbook
starting a new game. We then use an after_load hook to check that the
languages match, and report an error if they do not.
label after_load:
if lang == game_lang:
return
if lang == "english":
"The language of the save file is not the current selected language."
elif lang == "japanese":
"ファイルを保存した言語は、現在の選択された言語ではありません。"
$ renpy.full_restart()
In-line translation
init python:
en = Character(None, condition='lang == "english"')
jp = Character(None, condition='lang == "japanese"')
label start:
en "It was a dark and stormy night..."
jp "それは暗い嵐の夜だった..."
jp_e "しかし、我々の中なので、大丈夫だ。"
# ...
60
Renpy Cookbook
This procedure works around that limitation by reading a text file editable by
users.
First, make a small text file containing the name of your character and
nothing else. Make sure that the name is the only line, then save the file as
UTF-8 in the game directory (where the script would be.) Mark it clearly so
that prospective players can easily find it.
Next, insert this code into the init statement of your script:
Declare the named character like so, placing the name as the variable nameVar:
$ a = Character(nameVar, color="#c8ffc8")
If you need to insert the name into someone's dialogue, use %(nameVar)s to do
it.
Also, include simple instructions telling the player how to alter the text file.
61
Renpy Cookbook
This is a kana converter for Ren'py. There are three files: one for hiragana,
one for katakana without dashes, and another for katakana with dashes. The
code is open-source, and should be improved wherever possible.
Hiragana Version:
init:
pass
label hiragana:
python:
for letter in l: # On the first run, letter will be the first character in l. It will equal subsequent
characters as the for-loop continues.
62
Renpy Cookbook
if letter.lower() in accept: # The lowercase version of the variable letter is now checked against
the list of acceptable characters.
n += letter # Put a letter in variable n.
oneLetterHiragana = {"a": u"あ", "i": u"い", "u": u"う", "e": u"え", "o": u"お",
"1": "1", "2": "2", "3": "3", "4": "4", "5": "5", "6": "6",
"7": "7", "8": "8", "9": "9", "0": "0"}
twoLetterHiragana = {"ka": u"か", "ki": u"き", "ku": u"く", "ke": u"け", "ko": u"こ",
"sa": u"さ", "si": u"し", "su": u"す", "se": u"せ", "so": u"そ",
"ta": u"た", "ti": u"ち", "tu": u"つ", "te": u"て", "to": u"と",
"na": u"な", "ni": u"に", "nu": u"ぬ", "ne": u"ね", "no": u"の",
"ha": u"は", "hi": u"ひ", "hu": u"ふ", "he": u"へ", "ho": u"ほ",
"ma": u"ま", "mi": u"み", "mu": u"む", "me": u"め", "mo": u"も",
"ya": u"や", "yi": u"い", "yu": u"ゆ", "ye": u"いぇ", "yo": u"よ",
"ra": u"ら", "ri": u"り", "ru": u"る", "re": u"れ", "ro": u"ろ",
"wa": u"わ", "wi": u"うぃ", "wu": u"う", "we": u"うぇ", "wo": u"うぉ",
"la": u"ら", "li": u"り", "lu": u"る", "le": u"れ", "lo": u"ろ",
"fu": u"ふ",
"ga": u"が", "gi": u"ぎ", "gu": u"ぐ", "ge": u"げ", "go": u"ご",
"za": u"ざ", "zi": u"じ", "zu": u"ず", "ze": u"ぜ", "zo": u"ぞ",
"da": u"だ", "di": u"ぢ", "du": u"づ", "de": u"で", "do": u"ど",
"ba": u"ば", "bi": u"び", "bu": u"ぶ", "be": u"べ", "bo": u"ぼ",
"pa": u"ぱ", "pi": u"ぴ", "pu": u"ぷ", "pe": u"ぺ", "po": u"ぽ",
"ji": u"じ", "ju": u"じゅ", "n\'": u"ん"}
63
Renpy Cookbook
"nwa": u"んわ", "nwi": u"んうぃ", "nwu": u"んう", "nwe": u"んうぇ", "nwo": u"んうぉ",
"nga": u"んが", "ngi": u"んぎ", "ngu": u"んぐ", "nge": u"んげ", "ngo": u"んご",
"nza": u"んざ", "nzi": u"んじ", "nzu": u"んず", "nze": u"んぜ", "nzo": u"んぞ",
"nda": u"んだ", "ndi": u"んぢ", "ndu": u"んづ", "nde": u"んで", "ndo": u"んど",
"nba": u"んば", "nbi": u"んび", "nbu": u"んぶ", "nbe": u"んべ", "nbo": u"んぼ",
"mba": u"んば", "mbi": u"んび", "mbu": u"んぶ", "mbe": u"んべ", "mbo": u"んぼ",
"npa": u"んぱ", "npi": u"んぴ", "npu": u"んぷ", "npe": u"んぺ", "npo": u"んぽ",
"mpa": u"んぱ", "mpi": u"んぴ", "mpu": u"んぷ", "mpe": u"んぺ", "mpo": u"んぽ",
"kka": u"っか", "kki": u"っき", "kku": u"っく", "kke": u"っけ", "kko": u"っこ",
"ssa": u"っさ", "ssi": u"っし", "ssu": u"っす", "sse": u"っせ", "sso": u"っそ",
"tta": u"った", "tti": u"っち", "ttu": u"っつ", "tte": u"って", "tto": u"っと",
"hha": u"っは", "hhi": u"っひ", "hhu": u"っふ", "hhe": u"っへ", "hho": u"っほ",
"mma": u"んま", "mmi": u"んみ", "mmu": u"んむ", "mme": u"んめ", "mmo": u"んも",
"rra": u"っら", "rri": u"っり", "rru": u"っる", "rre": u"っれ", "rro": u"っろ",
"wwa": u"っわ", "wwi": u"っうぃ", "wwu": u"っう", "wwe": u"っうぇ", "wwo": u"っうぉ",
"nja": u"んじゃ", "nji": u"んじ", "nju": u"んじゅ", "nje": u"んじぇ", "njo": u"んじょ",
"jja": u"っじゃ", "jji": u"っじ", "jju": u"っじゅ", "jje": u"っじぇ", "jjo": u"っじょ"}
while brew != "": # The parsing will continue until the string is empty.
64
Renpy Cookbook
n += u"ん"
brew = brew[1:]
elif brew[:2].lower() in twoLetterHiragana: # Check the letters against the two-letter dictionary.
n += twoLetterHiragana[brew[:2].lower()] # Add the corresponding kana to n.
brew = brew[2:] # Remove the letters from brew.
elif brew[:4].lower() in fourLetterHiragana: # Check the letters against the four-letter dictionary.
n += fourLetterHiragana[brew[:4].lower()] # Add the corresponding kana to n.
brew = brew[4:] # Remove the letters from brew.
brew = n
# Kick the Python variable back out to Ren'Py so it could be displayed properly.
$ whatt = brew
return
init:
pass
label katakana:
65
Renpy Cookbook
python:
for letter in l: # On the first run, letter will be the first character in l. It will equal subsequent
characters as the for-loop continues.
if letter.lower() in accept: # The lowercase version of the variable letter is now checked against
the list of acceptable characters.
n += letter # Put a letter in variable n.
oneLetterKatakana = {"a": u"ア", "i": u"イ", "u": u"ウ", "e": u"エ", "o": u"オ",
"1": "1", "2": "2", "3": "3", "4": "4", "5": "5", "6": "6",
"7": "7", "8": "8", "9": "9", "0": "0"}
twoLetterKatakana = {"ka": u"カ", "ki": u"キ", "ku": u"ク", "ke": u"ケ", "ko": u"コ",
"sa": u"サ", "si": u"シ", "su": u"ス", "se": u"セ", "so": u"ソ",
"ta": u"タ", "ti": u"チ", "tu": u"ツ", "te": u"テ", "to": u"ト",
"na": u"ナ", "ni": u"ニ", "nu": u"ヌ", "ne": u"ネ", "no": u"ノ",
"ha": u"ハ", "hi": u"ヒ", "hu": u"フ", "he": u"ヘ", "ho": u"ホ",
"ma": u"マ", "mi": u"ミ", "mu": u"ム", "me": u"メ", "mo": u"モ",
"ya": u"や", "yi": u"イー", "yu": u"ユ", "ye": u"イェ", "yo": u"ヨ",
"ra": u"ラ", "ri": u"リ", "ru": u"ル", "re": u"レ", "ro": u"ロ",
"wa": u"わ", "wi": u"ウィ", "wu": u"ウ", "we": u"ウェ", "wo": u"ウォ",
"la": u"ラ", "li": u"リ", "lu": u"ル", "le": u"レ", "lo": u"ロ",
"fu": u"フ",
"ga": u"ガ", "gi": u"ギ", "gu": u"グ", "ge": u"ゲ", "go": u"ゴ",
"za": u"ザ", "zi": u"ジ", "zu": u"ズ", "ze": u"ゼ", "zo": u"ゾ",
"da": u"ダ", "di": u"ヂ", "du": u"ヅ", "de": u"デ", "do": u"ド",
66
Renpy Cookbook
"ba": u"バ", "bi": u"ビ", "bu": u"ブ", "be": u"ベ", "bo": u"ボ",
"pa": u"パ", "pi": u"ピ", "pu": u"プ", "pe": u"ペ", "po": u"ポ",
"ji": u"ジ", "ju": u"ジュ", "n\'": u"ン"}
67
Renpy Cookbook
"nbya": u"ンビャ", "nbyi": u"ンビィ", "nbyu": u"ンビュ", "nbye": u"ンビェ", "nbyo": u"ンビョ",
"npya": u"ンピャ", "npyi": u"ンピィ", "npyu": u"ンピュ", "npye": u"ンピェ", "npyo": u"ンピョ",
"nsha": u"ンシャ", "nshu": u"ンシュ", "nshe": u"ンシェ", "nsho": u"ンショ",
"ncha": u"ンチャ", "nchu": u"ンチュ", "nche": u"ンチェ", "ncho": u"ンチョ",
"sshi": u"ッシ", "cchi": u"ッチ", "ddzu": u"ッヅ",
"kkya": u"ッキャ", "kkyi": u"ッキィ", "kkyu": u"ッキュ", "kkye": u"ッキェ", "kkyo": u"ッキョ",
"ssya": u"ッシャ", "ssyi": u"ッシィ", "ssyu": u"ッシュ", "ssye": u"ッシェ", "ssyo": u"ッショ",
"ttya": u"ッチャ", "ttyi": u"ッチィ", "ttyu": u"ッチュ", "ttye": u"ッチェ", "ttyo": u"ッチョ",
"hhya": u"ッヒャ", "hhyi": u"ッヒィ", "hhyu": u"ッヒュ", "hhye": u"ッヒェ", "hhyo": u"ッヒョ",
"mmya": u"ンミャ", "mmyi": u"ンミィ", "mmyu": u"ンミュ", "mmye": u"ンミェ", "mmyo": u"ンミョ",
"rrya": u"ッリャ", "rryi": u"ッリィ", "rryu": u"ッリュ", "rrye": u"ッリェ", "rryo": u"ッリョ",
"ggya": u"ッギャ", "ggyi": u"ッギィ", "ggyu": u"ッギュ", "ggye": u"ッギェ", "ggyo": u"ッギョ",
"zzya": u"ッジャ", "zzyi": u"ッジィ", "zzyu": u"ッジュ", "zzye": u"ッジェ", "zzyo": u"ッジョ",
"ddya": u"ッヂャ", "ddyi": u"ッヂィ", "ddyu": u"ッヂュ", "ddye": u"ッヂェ", "ddyo": u"ッヂョ",
"bbya": u"ッビャ", "bbyi": u"ッビィ", "bbyu": u"ッビュ", "bbye": u"ッビェ", "bbyo": u"ッビョ",
"ppya": u"ッピャ", "ppyi": u"ッピィ", "ppyu": u"ッピュ", "ppye": u"ッピェ", "ppyo": u"ッピョ",
"ssha": u"ッシャ", "sshu": u"ッシュ", "sshe": u"ッシェ", "ssho": u"ッショ",
"ccha": u"ッチャ", "cchu": u"ッチュ", "cche": u"ッチェ", "ccho": u"ッチョ"}
while brew != "": # The parsing will continue until the string is empty.
elif brew[:2].lower() in twoLetterKatakana: # Check the letters against the two-letter dictionary.
n += twoLetterKatakana[brew[:2].lower()] # Add the corresponding kana to n.
brew = brew[2:] # Remove the letters from brew.
elif brew[:4].lower() in fourLetterKatakana: # Check the letters against the four-letter dictionary.
n += fourLetterKatakana[brew[:4].lower()] # Add the corresponding kana to n.
brew = brew[4:] # Remove the letters from brew.
brew = n
# Kick the Python variable back out to Ren'Py so it could be displayed properly.
$ whatt = brew
return
68
Renpy Cookbook
init:
pass
label dashkata:
python:
for letter in l: # On the first run, letter will be the first character in l. It will equal subsequent
characters as the for-loop continues.
if letter.lower() in accept: # The lowercase version of the variable letter is now checked against
the list of acceptable characters.
n += letter # Put a letter in variable n.
69
Renpy Cookbook
oneLetterKatakana = {"a": u"ア", "i": u"イ", "u": u"ウ", "e": u"エ", "o": u"オ",
"1": "1", "2": "2", "3": "3", "4": "4", "5": "5", "6": "6",
"7": "7", "8": "8", "9": "9", "0": "0"}
twoLetterKatakana = {"ka": u"カ", "ki": u"キ", "ku": u"ク", "ke": u"ケ", "ko": u"コ",
"sa": u"サ", "si": u"シ", "su": u"ス", "se": u"セ", "so": u"ソ",
"ta": u"タ", "ti": u"チ", "tu": u"ツ", "te": u"テ", "to": u"ト",
"na": u"ナ", "ni": u"ニ", "nu": u"ヌ", "ne": u"ネ", "no": u"ノ",
"ha": u"ハ", "hi": u"ヒ", "hu": u"フ", "he": u"ヘ", "ho": u"ホ",
"ma": u"マ", "mi": u"ミ", "mu": u"ム", "me": u"メ", "mo": u"モ",
"ya": u"や", "yi": u"イー", "yu": u"ユ", "ye": u"イェ", "yo": u"ヨ",
"ra": u"ラ", "ri": u"リ", "ru": u"ル", "re": u"レ", "ro": u"ロ",
"wa": u"わ", "wi": u"ウィ", "wu": u"ウ", "we": u"ウェ", "wo": u"ウォ",
"la": u"ラ", "li": u"リ", "lu": u"ル", "le": u"レ", "lo": u"ロ",
"fu": u"フ",
"ga": u"ガ", "gi": u"ギ", "gu": u"グ", "ge": u"ゲ", "go": u"ゴ",
"za": u"ザ", "zi": u"ジ", "zu": u"ズ", "ze": u"ゼ", "zo": u"ゾ",
"da": u"ダ", "di": u"ヂ", "du": u"ヅ", "de": u"デ", "do": u"ド",
"ba": u"バ", "bi": u"ビ", "bu": u"ブ", "be": u"ベ", "bo": u"ボ",
"pa": u"パ", "pi": u"ピ", "pu": u"プ", "pe": u"ペ", "po": u"ポ",
"ji": u"ジ", "ju": u"ジュ", "n\'": u"ン"}
70
Renpy Cookbook
"nba": u"ンバ", "nbi": u"ンビ", "nbu": u"ンブ", "nbe": u"ンベ", "nbo": u"ンボ",
"mba": u"ンバ", "mbi": u"ンビ", "mbu": u"ンブ", "mbe": u"ンベ", "mbo": u"ンボ",
"npa": u"ンパ", "npi": u"ンピ", "npu": u"ンプ", "npe": u"ンペ", "npo": u"ンポ",
"mpa": u"ンパ", "mpi": u"ンピ", "mpu": u"ンプ", "mpe": u"ンペ", "mpo": u"ンポ",
"kka": u"ッカ", "kki": u"ッキ", "kku": u"ック", "kke": u"ッケ", "kko": u"ッコ",
"ssa": u"ッサ", "ssi": u"ッシ", "ssu": u"ッす", "sse": u"ッセ", "sso": u"ッソ",
"tta": u"ッタ", "tti": u"ッチ", "ttu": u"ッつ", "tte": u"ッて", "tto": u"ット",
"hha": u"ッハ", "hhi": u"ッヒ", "hhu": u"ッフ", "hhe": u"ッヘ", "hho": u"ッホ",
"mma": u"ンマ", "mmi": u"ンミ", "mmu": u"ンム", "mme": u"ンメ", "mmo": u"ンモ",
"rra": u"ッラ", "rri": u"ッリ", "rru": u"ッル", "rre": u"ッレ", "rro": u"ッロ",
"wwa": u"ッわ", "wwi": u"ッウィ", "wwu": u"ッウ", "wwe": u"ッウェ", "wwo": u"ッウォ",
"nja": u"ンジャ", "nji": u"ンジ", "nju": u"ンジュ", "nje": u"ンジェ", "njo": u"ンジョ",
"jja": u"ッジャ", "jji": u"ッジ", "jju": u"ッジュ", "jje": u"ッジェ", "jjo": u"ッジョ"}
while brew != "": # The parsing will continue until the string is empty.
elif brew[:2].lower() in twoLetterKatakana: # Check the letters against the two-letter dictionary.
71
Renpy Cookbook
elif brew[:4].lower() in fourLetterKatakana: # Check the letters against the four-letter dictionary.
if brew[4:5].lower() == brew[3:4].lower(): # This uses the dash to demarcate double vowels.
brew = brew[:4] + brew[5:]
n += fourLetterKatakana[brew[:4].lower()] + u"ー"
brew = brew[4:] # Remove the letters from s.
else:
n += fourLetterKatakana[brew[:4].lower()] # Add the corresponding kana to n.
brew = brew[4:] # Remove the letters from s.
brew = n
# Kick the Python variable back out to Ren'Py so it could be displayed properly.
$ whatt = brew
return
72
Renpy Cookbook
How To Use
1. Download this Hangul_inputter.rpy file and put it in your project\game
directory.
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.
prompt - A prompt that is used to ask the user for the text.
default - A default for the text that this input can return.
Styles
The following styles are to customize hangul inputter's appearence.
73
Renpy Cookbook
Example
init python:
style.hi_frame.background = '#114faa70'
style.hi_frame.xalign = .5
style.hi_frame.yalign = .5
style.hi_frame.xminimum = 250
label start:
if final:
'%(name)s아%(final)s'
else:
'%(name)s야%(final)s'
74
Renpy Cookbook
Comparison of images before (left) and after (right) JCC. The original image
took 64 KB of disk space, while the compressed one takes 28 KB, for a
reduction of 56%.
Requirements
End User: The only requirement for the end user of JCC is that the game
they're playing be made with Ren'Py 6.3.3 or greater.
It's expected that the developer knows how to run a Python script on his or
her system.
75
Renpy Cookbook
Running JCC
To convert the images used in your game to use JCC, place the jcc.py script in
the game directory of your project, and run it. This will do two things:
All PNG files with filenames like filename.png underneath the game
directory will be moved to filename.png.bak, and will have
filename.png.base.jpg and filename.png.mask.jpg created to store the
JPEG-compressed data.
A script file, jcc.rpy, is added to the game directory. This file contains
code that causes Ren'Py to use the compressed files.
Note that this will compress all PNG files in the game directory, losing some
quality in the process. There are some files that you may not want
compressed, such as the frame of the text window, or SFonts. You should
restore these png files from the backups, and delete the corresponding jpg
files.
76
Renpy Cookbook
1. Download the latest version of NSIS from its web site, and install it.
2. Create README.txt and LICENSE.txt files for your game, and place
them in your game's base directory. (The directory containing the
game/ directory.)
3. Build the distributions using the Ren'Py Launcher, and then unzip the
windows distribution.
4. Download installer.nsi, and place it in the unzipped windows directory.
(The directory containing yourgame.exe.
5. Edit installer.nsi, changing values as appropriate. You'll certainly want
to change the EXE, INSTALLER_EXE, PRODUCT_NAME,
PRODUCT_VERSION, PRODUCT_WEBSITE, and PRODUCT_PUBLISHER
lines.
6. Right-click on installer.nsi, and pick "Compile NSIS Script".
7. NSIS will run, and assuming there are no errors, produce the installer.
Be sure to test it before releasing.
77
Renpy Cookbook
Music Room
This is the code for a sample music room. It displays a list of buttons
corresponding to music tracks. If a given track has been heard by the user
before, a button is displayed with the tracks name. When the button is
clicked, the track is played. Otherwise, a disabled button with "???" as the
label is shown.
The important things to customize here are the scene statement (which
controls the background used) and the various music_button calls.
init python:
def set_playing_(track):
store.playing = track
return True
set_playing = renpy.curry(set_playing_)
if store.playing == track:
role = "selected_"
else:
role = ""
if not renpy.seen_audio(track):
name = "???"
clicked = None
else:
clicked = set_playing(track)
ui.textbutton(
name,
clicked=clicked,
role=role,
size_group="music")
label music_room:
scene black
python:
_game_menu_screen = None
78
Renpy Cookbook
label music_room_loop:
if ui.interact():
jump music_room_loop
else:
return
79
Renpy Cookbook
New CG Gallery
This is a rewritten CG gallery, supporting more sophisticated forms of
unlocking. To use it, download new_gallery.rpy, and add it to your game
directory. You'll then need to create a Gallery object, and call the show()
method on it, after configuring the gallery and adding buttons. This is often
done inside a single label, which can be added to config.main_menu.
Example
init:
# Position the navigation on the right side of the screen.
$ style.gallery_nav_frame.xpos = 800 - 10
$ style.gallery_nav_frame.xanchor = 1.0
$ style.gallery_nav_frame.ypos = 12
80
Renpy Cookbook
g.button("thumb_beach.jpg")
#
# These show images, if they have been unlocked. The image name must
# have been defined using an image statement.
g.unlock_image("bg beach daytime")
g.unlock_image("bg beach nighttime")
#
# This shows a displayable...
g.display("beach_sketch.jpg")
# ... if all prior images have been show.
g.allprior()
g.button("thumb_eileen.jpg")
#
# Note that we can give image and unlock image more than one image
# at a time.
g.unlock_image("bg lighthouse day", "eileen day happy")
g.unlock_image("bg lighthouse day", "eileen day mad")
g.button("thumb_concepts.jpg")
#
# We can apply conditions to buttons as well as to images.
# The "condition" condition checks an arbitrary expression.
g.condition("persistent.beat_game")
#
g.display("concept1.jpg")
g.display("concept2.jpg")
g.display("concept3.jpg")
return
81
Renpy Cookbook
Documentation
Function: Gallery ():
Creates a new page, and adds it to the gallery. Buttons will be added to this
page.
Creates a button, adding it to the current page. Images and conditions will be
added to this button. The button is unlocked if all conditions added to it are
true, and at least one image associated with it is unlocked.
82
Renpy Cookbook
Conditions are added to this image. The image is unlocked if all all conditions
are satisfied.
Conditions are added to this image. The image is unlocked if all all conditions
are satisfied.
page the 0-based page number to show to the user. (So 0 is the first page, 1
is the second, and so on.)
Conditions
This takes as its arguments one or more image names. It is satisfied if all
named images have been shown to the user.
Satisified if all prior images associated with the current button have been
unlocked. This should only be used with images, not buttons.
83
Renpy Cookbook
Convenience
Equivalent to calling image and unlock with the same arguments. This defines an
image that is shown only if all of the named images given as argumenst have
been seen by the user.
Customization
layout - A function that takes two arguments, a 0-based button number and
the number of buttons on the current page. It's expected to return a
dictionary giving the placement properties for the numbered button.
When called, replaces layout with a function that arranges buttons in a grid.
gridsize - A (columns, rows) pair, giving the number of columns and rows in
the grid.
upperleft - A (x, y) pair, where x and y are the positions of the upper-left
corder of the grid.
offsets - A (xoffset, yoffset) pair, where xoffset is the distance between (the
upper-left corner of) buttons in the same row, and yoffset is the distance
between buttons in the same column.
navigation - A function that is called with three arguments: The name of the
current page, the 0-based number of the current page, and the total number
of pages. This function should add ui elements to the screen that cause
ui.interact to return either ("page", page_num), where page_num is the page
that the user should next see, or ("return", 0), to cause Gallery.show to
return.
The default navigation shows a frame containing buttons for each page, and
one "Return" button.
84
Renpy Cookbook
The default locked_image shows locked_background to the user, along with the
words "Image number of number in page is locked.", where number is the 1-
based number of the image.
transition - A transition that is used when showing gallery pages and gallery
images.
Styles
85
Renpy Cookbook
For example:
init:
$ config.font_replacement_map["DejaVuSans.ttf", False, True] = ("DejaVuSans-Oblique.ttf", False,
False)
The supplied font is of course not the only font you can use. Any true type
font which you can legally distribute, you can use in a Ren'Py game.
86
Renpy Cookbook
You can just copy and paste the code if you want and modify it to your
purposes, but I encourage going through it to gain a deeper understanding of
object-oriented programming.
This is a beginner example and is probably the kind of thing you're going to
want to use if you're only doing this once or twice. It also introduces some of
the concepts that we're going to expand upon.
87
Renpy Cookbook
Walkthrough of Example 1
(beginner)
init:
pass
label start:
$ coins = 0
You can keep track of money... let's call it coins... with a variable. The $ (cash
sign) means that we're using a python statement outside on an "init python:"
block.
We're going to initially set this value to 0. The "label start:" tells Ren'Py
where to begin at.
$ items = []
Here's something new. We're declaring the variable "items" as an empty set,
usually defined by brackets such as "[" and "]". Since it's empty, there's
nothing-inbetween yet.
We've added a string that lets the player know what's happening. By using
the += operator, we can add coins. We're adding 10 coins to zero.
"You have %(coins)d coins remaining. What would you like to buy?"
%(coins)d is what we say if we want to put the number of coins into a string
or dialogue of printed text. "%(coins)d" is a dynamic string that will insert the
value of that variable, whether it's text or a number.
menu:
This kind of label lets Ren'Py know to offer the user a set of choices, which
we will then define. Indentation is important. For menu labels, it is four
spaces over.
88
Renpy Cookbook
"Spaghetti":
$ coins -= 3
By using the -= operator, you can subtract coins, just like before when we
gave the player 10 coins by adding it to zero.
$ items.append("spaghetti")
If you have a list named "items", you can add things to the list by saying
items.append("thing"). It's a python statement so we also use a $ sign.
"Olives":
$ coins -= 4
$ items.append("olives")
"Chocolate":
$ coins -= 11
$ items.append("chocolate")
Then we just put the rest of what we know into practice by making the rest
of the menu options.
if "chocolate" in items:
"You have a bar of chocolate! Yummy!"
The question statement if "object" in "list" tells you whether or not the list
contains that object. When we append "chocolate" to "items" then the
brackets look like this to Ren'Py: ["chocolate"]
89
Renpy Cookbook
class Inventory:
def __init__(self, money=10):
self.money = money
self.items = []
label start:
python:
inventory = Inventory()
spaghetti = Item("Spaghetti", 3)
olives = Item("Olives", 4)
chocolate = Item("Chocolate", 11)
$ inventory.earn(10)
$ current_money = inventory.money
if inventory.buy(chocolate):
"Mmm, chocolate. I'll save that for later... "
else:
"Not enough money... "
90
Renpy Cookbook
jump preshop
jump shop2
if inventory.has_item(chocolate):
"Good thing I bought that chocolate earlier."
else:
"If only I had some chocolate..."
label preshop:
$ spaghetticost = spaghetti.cost
$ olivescost = olives.cost
$ chocolatecost = chocolate.cost
label shop2:
menu shop:
"Buy nothing.":
jump game_continues
label fallthrough:
"Not enough money..."
jump shop2
label game_continues:
"And so I left the store."
91
Renpy Cookbook
Walkthrough of Example 2
(advanced)
Here's an advanced solution using the long-term goal of classes of objects to
reduce the amount of work needed over the entire project. I hope you're
ready to put it all into practice.
You can actually read more about classes here: [in Python]
init python:
This statement lets Ren'Py know to run this at the start ("init") and that it's
python code ("python:") so none of these statements need a cash sign $ to
prefix them.
class Item:
self.name = name
self.cost = cost
"self" refers back to the original class ("Item") and the period is a break in the
hierarchy. "name" comes directly after that, so name is a property of "Item".
The same thing is true of "cost".
class Inventory:
92
Renpy Cookbook
This can be freely changed, of course. Just remember where it is, maybe
leave a comment for yourself.
self.money = money
As before, we're declaring that the property "money" of the "self" (class
Inventory) is contained within a global variable called "money".
self.items = []
This is an empty set called "items" which can be filled, just like before—but
now it's contained with the class called Inventory as an embedded property
of that group.
If the money contained with the "Inventory" is less than the "cost" variable
contained within the "Item" class then we...
You may also notice the period between self and money. That tells Ren'Py
the location of money in the hierarchy.
The >= syntax is identical to the += and -= we were using before, except that
this time we're using it only to evaluate the container within the variable and
not change it.
self.money -= item.cost
We subtract the amount of the cost away from the "Inventory" money. In
effect, the player has lost money.
self.items.append(item)
Just like in the beginner's code, we're adding the "item" into the empty set.
return True
else:
return False
93
Renpy Cookbook
self.money += amount
Again, "self" still refers to class "Inventory". So what is earned is added from
"amount" into "money."
This is a definition that checks to see if the item is in the inventory or not.
if item in self.items:
return True
else:
return False
This one is pretty self-explanatory and works like checking about adding an
item into the "items" container.
label start:
python:
inventory = Inventory()
spaghetti = Item("Spaghetti", 3)
94
Renpy Cookbook
Notice that it's taking the arguments "name" and "cost" just like we declared
before?
It costs 3 "money".
olives = Item("Olives", 4)
chocolate = Item("Chocolate", 11)
We then apply that same kind of code to two other objects. Olives cost more
than spaghetti and chocolate is obviously a luxury few can afford...
We state that 10 "money" is earned and sent to the inventory, after it goes
to amount.
$ current_money = inventory.money
This is a hack to make the field a global so we can use it as a dynamic string
below.
Like in Example 1, this string changes depending on the amount declared for
the variable "current_money".
if inventory.buy(chocolate):
If you give the player 11 instead of 10 coins under the class Inventory, he can
buy it.
The "else" function just handles what happens if it's not possible to buy it. By
the default option, the player can't afford it.
jump preshop
jump shop2
95
Renpy Cookbook
if inventory.has_item(chocolate):
If the "Item" "chocolate" is contained within the "Inventory" item set... then
the statement below is printed:
label preshop:
$ spaghetticost = spaghetti.cost
$ olivescost = olives.cost
$ chocolatecost = chocolate.cost
label shop2:
menu shop:
By not including a colon, Ren'Py will display this dialogue at the same time as
the menu.
This is just like we've done before, and here we're using a dynamic string for
the cost of the spaghetti: that means you can change it in the future or
maybe even lower it.
96
Renpy Cookbook
jump game_continues
"Buy nothing.":
jump game_continues
Always remember to give the player a neutral option when shopping. With a
user interface, this would probably be a cancel imagebutton.
label fallthrough:
"Not enough money..."
jump shop2
label game_continues:
"And so I left the store."
97
Renpy Cookbook
Tips menu
With this script you are able to create new menu with buttons to jump in
different part of game script.
Example
init:
# Add the TIPS menu to the main menu.
$ config.main_menu.insert(2, ("TIPS", "tips", "True"))
python:
def tip(text, target, condition):
if eval(condition):
clicked = ui.jumps(target)
else:
clicked = None
label tips:
# Show the tips background.
scene bg tips_background
# Show each tip, using the tip function. The three arguments are:
# - The text of a tip.
# - The label that we jump to if the tip is picked.
# - A string containing an expression, that determines if the tip is
# unlocked.
$ tip("Tip #1", "tip1", "persistent.unlock_tip_1")
$ tip("Tip #2", "tip2", "persistent.unlock_tip_2")
label end_tips:
return
label tip_1:
"Perhaps you should have checked to see if that was a carton of oatmeal,
or a carton of rat poison."
jump tips
label tip_2:
98
Renpy Cookbook
"She's a TRAP!"
jump tips
To unlock the first tip in this example, you would write the following in your
game script at the point you want the tip to be unlocked:
$ persistent.unlock_tip_1 = True
99
Renpy Cookbook
Example
init python:
def unarchive(original_filename, new_filename):
# original_filename is the name the file has when stored in the archive, including any
# leading paths.
# new_filename is the name the file will have when extracted, relative to the base directory.
import os
import os.path
if not os.path.exists(dirname):
os.makedirs(dirname)
orig = renpy.file(original_filename)
new = file(new_filename, "wb")
new.close()
orig.close()
label start:
# We have Track01.mp3 in archive and images/photo.png in archive too
100
Renpy Cookbook
init:
$ millie = DynamicCharacter("maid_name")
# we've chosen to call the variable "maid_name", but it doesn't exist yet.
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.
101
Renpy Cookbook
Conditional Hyperlinks
Maybe you want to be able to turn hyperlinks on or off for some reason.
Putting this code into a Python init block will show hyperlinks only when
persistent.hyperlinks_on is True:
import re
expr = re.compile(r'{a=.*?}|{/a}')
def remove_hyperlinks(input):
global expr
if persistent.hyperlinks_on:
return input
else:
return re.sub(expr, "", input)
config.say_menu_text_filter = remove_hyperlinks
expr = re.compile(r'{a=opt_.*?}|{/a}')
102
Renpy Cookbook
Timed menus
Now, with ui.timer being implemented, we finally could make visual novels
little more time-sensitive. For more fun and thrill. This recipe lets you make a
new option in menus -- choose nothing, just by waiting some time. Basic
concept is: You set a timer before menu, and if it expires, jump to "user
didn't choose anything" branch. Note that you must inform player about
time left for him to choose.
$ ui.timer(10.0, ui.jumps("menu1_slow"))
menu:
"Choice 1":
hide countdown
e "You chosed 'Choice 1'"
jump menu1_end
"Choice 2":
hide countdown
e "You chosed 'Choice 2'"
jump menu1_end
label menu1_slow:
hide countdown
e "You didn't choose anything."
label menu1_end:
e "Anyway, let's do something else."
More elaborate use of this is dynamic timed menus. This lets you "change"
menu choices presented to user over time. Like this:
$ ui.timer(10.0, ui.jumps("menu2_v2"))
menu:
"Choice 1 fast":
hide countdown2
e "You chosed 'Choice 1' fast"
jump menu2_end
"Choice 2 fast":
hide countdown2
e "You chosed 'Choice 2' fast"
jump menu2_end
label menu2_v2:
$ ui.timer(10.0, ui.jumps("menu2_slow"))
hide countdown2
show countdown at Position (xalign = 0.5, yalign = 0.1)
menu:
"Choice 1 slow":
hide countdown
e "You chosed 'Choice 1', but was slow"
jump menu2_end
103
Renpy Cookbook
"Choice 2":
hide countdown
e "You chosed 'Choice 2', but was slow"
jump menu2_end
label menu2_slow:
hide countdown
e "You was really slow and didn't choose anything."
label menu2_end:
e "Anyway, let's do something else."
And lastly, I admit that whole concept was shamelessly ripped off from the
great VN/TRPG series "Sakura Taisen"
104
Renpy Cookbook
UI Widgets
Summary
This code allows using any arbitrary text tag within say and menu statements
and, with some extra coding, UI widgets. Due to its nature, you'll need some
programming skills to make use of it, since you have to define the behavior
for the tags you want to create. Basic knowledge of python should be enough
(you don't need to understand the parser's code, but you should be able to
understand the examples before trying to define you own tags).
Warning
The parser
You should include this code as is within your game, either pasting it on an
existing script file or creating a new file for it. It defines the function that
parses the tags and the variable to hold all the needed information for the
custom tags; and tells Ren'py to call this parser function for each menu and
say statement.
init python:
# This dictionary holds all the custom text tags to be processed. Each element is a tuple in the form
# tag_name => (reqs_close, function) where:
# tag_name (the dictionary key) is the name for the tag, exactly as it should appear when used
(string).
# reqs_close is a boolean defining whether the tag requires a matching closing tag or not.
# function is the actual function to be called. It will always be called with two positional arguments:
# The first argument 'arg' is the argument provided in the form {tag=arg} within the tag, or None if
there was no argument.
# The second argument 'text' is the text enclosed in the form {tag}text{/tag} by the tag, or None for
empty tags.
# Note that for non-empty tags that enclose no text (like in "{b}{/b}"), an empty string ("") is
passed, rather than None.
custom_text_tags = {}
# This function is the heart of the module: it takes a string argument and returns that string with all
the custom tags
105
Renpy Cookbook
# already parsed. It can be easily called from any python block; from Ren'py code there is a Ren'py
mechanism to get it
# called for ALL say and menu statements: just assign it to the proper config variable and Ren'py
will do everything else:
# $ config.say_menu_text_filter = cttfilter
def cttfilter(what): # stands for Custom Text-Tags filter
""" A custom filter for say/menu statements that allows custom text tags in an extensible way.
Note that this only enables text-manipulation tags (ie: tags that transform the text into some
other text).
It is posible for a tag's implementation to rely on other tags (like the money relying on color).
"""
# Interpolation should take place before any text tag is processed.
# We will not make custom text tags an exception to this rule, so let's start by interpolating:
what = what%globals() # That will do the entire interpolation work, but there is a subtle point:
# If a %% was included to represent a literal '%', we've already replaced it, and will be later
# missinterpreted by renpy itself; so we have to fix that:
what = what.replace("%", "%%")
# Ok, now let's go to the juicy part: find and process the text tags.
# Rather than reinventing the wheel, we'll rely on renpy's own tokenizer to tokenize the text.
# However, before doing that, we should make sure we don't mess up with built-in tags, so we'll
need to identify them:
# We'll add them to the list of custom tags, having None as their function:
global custom_text_tags
for k, v in renpy.display.text.text_tags.iteritems():
# (Believe me, I know Ren'py has the list of text tags there :P )
custom_text_tags[k] = (v, None)
# This also makes sure none of the built-in tags is overriden.
# Note that we cannot call None and expect it to magically reconstruct the tag.
# Rather than that, we'll check for that special value to avoid messing with these tags, one by
one.
# This one will be used for better readability:
def Split(s): # Splits a tag token into a tuple that makes sense for us
tag, arg, closing = None, None, False
if s[0]=="/":
closing, s = True, s[1:]
if s.find("=") != -1:
if closing:
raise Exception("A closing tag cannot provide arguments. Tag given: \"{/%s}\"."%s)
else:
tag, arg = s.split("=", 1)
else:
tag = s
return (tag, arg, closing)
# We will need to keep track of what we are doing:
# tagstack, textstack, argstack, current_text = [], [], [], ""
# stack is a list (stack) of tuples in the form (tag, arg, preceeding text)
stack, current_text = [], ""
for token in renpy.display.text.text_tokenizer(what, None):
if token[0] == 'tag':
tag, arg, closing = Split(token[1])
if closing: # closing tag
if len(stack)==0:
raise Exception("Closing tag {/%s} was found without any tag currently open. (Did you
define the {%s} tag as empty?)"%(tag, tag))
stag, sarg, stext = stack.pop()
if tag==stag: # good nesting, no need to crash yet
106
Renpy Cookbook
That's all about the parser itself. You don't need to understand how it works
to use it.
Defining tags
Custom tags are expected to produce text, optionally relying on argument
and/or content values. They cannot just produce arbitrary displayables, but
they can rely on built-in tags to achieve formating. Each tag is defined by a
function that must:
107
Renpy Cookbook
Once the function for a tag is defined, the parser must be made aware of it.
This is done by including an entry in the custom_text_tags dictionary. The key
used must be the text that identifies the tag (for example, for a tag
"{myTag}", the key would be "myTag"). The value is a tuple with two fields:
the first one is a boolean indicating whether the tag requires a closing tag
(like {/myTag}) (True) or not (False); the second field is the name of the
function that should be used to process this tag.
Usage example
The following code defines two tags, feeds them to the parser's dictionary,
and makes some use of them in a say statement to allow testing them. To
test this example, put this code in a .rpy script file and the parser code above
into another file, or just paste the parser code followed by the example's
code in a single file.
init:
image bg black = Solid("#000")
# Declare characters used by this game.
$ test = Character('The Tester', color="#ffc")
python:
def tab(arg, text): # This function will handle the custom {tab=something} tag
return " " * int(arg)
def money(arg, text):
total = int(arg)
c = total % 100
s = int(total/100) % 100
g = int(total/10000)
if g>0:
return "%d{color=#fc0}g{/color}%d{color=#999}s{/color}%d{color=#f93}c{/color}"%(g, s, c)
elif s>0:
return "%d{color=#999}s{/color}%d{color=#f93}c{/color}"%(s, c)
else:
return "%d{color=#f93}c{/color}"%(c)
custom_text_tags["tab"] = (False, tab) # "Registers" the function, pairing it with the "{tab}" tag
custom_text_tags["money"] = (False, money) # idem, for the {money} tag
108
Renpy Cookbook
label start:
scene bg black
$ indent, cash = renpy.random.randint(0, 8), renpy.random.randint(0, 100000000)
test "{tab=%(indent)d}(%(indent)d space
indentation)\n{tab=4}{b}%(cash)d{color=#f93}c{/color}{/b}={money=%(cash)d}"
jump start
The example combines the two custom tags with some built-in tags, and
makes heavy use of interpolation, to demonstrate that the parser behaves as
should be expected. So much markup and interpolation in a single statement
doesn't reflect normal usage.
Compatibility considerations
As mentioned above, forward compatibility with future versions of Ren'py
shouldn't be asumed, because the code relies on elements that might change
without any warning. In addition, there are some points worth mentioning.
Interpolation
To makes sure that interpolation is handled before (custom) text tag parsing,
the parser function simply takes care of the interpolation task itself, before
starting the actual parsing task.
Escape sequences
Space compacting
109
Renpy Cookbook
While multiple argument tags are not explicitly supported, they can be easily
emulated using python's split() function to break the argument into chunks
of separate data.
110
Renpy Cookbook
init:
# Adding about button to main menu
$ config.main_menu.insert(3, ('About', _intra_jumps("about", "main_game_transition"), "True"))
# About box
label about:
python hide:
renpy.transition(config.intra_transition)
ui.window(style=style.gm_root)
ui.frame(ymargin=10, xmargin=10, style=style.menu_frame)
ui.side(['t', 'c', 'r', 'b'], spacing=2)
vp = ui.viewport(mousewheel=True)
# This is where the text will go. You can use all the usual tags.
ui.text("This is my first visual novel, be gentle, thank you.. :)")
ui.bar(adjustment=vp.yadjustment, style='vscrollbar')
In case you don't want a label remove layout.label( ) and change ui.side( ):
The 't' (top) part must be removed, or else Ren'Py will throw an "error:
Invalid resolution for Surface".
111
Renpy Cookbook
Here are a few additional basic movement profiles that you can use to add
some variation to your game.
init:
python hide:
def bop_time_warp(x):
return -23.0 * x**5 + 57.5 * x**4 - 55 * x**3 + 25 * x**2 - 3.5 * x
start:
# Move the ball from the left side of the screen to the right with a “bop” profile
show ball at Move((0.25, 0.5), (0.75, 0,5), 1.0, time_warp=bop_time_warp, xanchor="center",
yanchor="center")
112
Renpy Cookbook
init:
python hide:
def quad_time_warp(x):
return -2.0 * x**3 + 3.0 * x**2
def quad_in_time_warp(x):
return 1.0 - (1.0 - x)**2
def quad_out_time_warp(x):
return x**2
start:
show eileen happy with quadinleft
Quadratic motion
Quadratic motion serves pretty much the same purpose as the standard,
cosine-based “ease” motions, but they’re a little more efficient. They’re also
a better model for natural accelerations and decelerations, because most
natural accelerations (and decelerations) are pretty well modelled as
constant acceleration motion, which is quadratic. You can see from the
picture that they’re also a little “gentler” - with a less aggressive
acceleration/deceleration profile (except the double-ended version, which is
a best fit polynomial to a quadratic acceleration followed by deceleration). It
may look a bit more organic than the built-in “ease” motion.
113
Renpy Cookbook
def quad_time_warp(x):
return -2.0 * x**3 + 3.0 * x**2
def quad_in_time_warp(x):
return 1.0 - (1.0 - x)**2
def quad_out_time_warp(x):
return x**2
Exponential decay
Besides quadratic (constant acceleration) motion, the other most common
(non-oscillatory) motion you see naturally is exponentially decaying motion.
This is the kind of motion you see when something is being slowed down by
friction. They’re much less efficient than the quadratic or standard ease
functions, but they also allow you much more control because you can adjust
the decay constant k. Larger k values mean the decay is faster (which means a
more aggressive acceleration/deceleration profile).
114
Renpy Cookbook
import math
decay_time_warp = renpy.curry(decay_time_warp_real)
decay_in_time_warp = renpy.curry(decay_in_time_warp_real)
decay_out_time_warp = renpy.curry(decay_out_time_warp_real)
115
Renpy Cookbook
Because of the extra k parameter, you have to use these functions a little
differently:
# Or...
Optimizing
Now, if you pick a k-value and like it, it’s kind of a waste to do all of these
calculations every time. You can simplify the equations like this:
import math
def decay_fixed_time_warp(x):
return AAA / (1.0 + math.exp(-k * (x - 0.5))) - BBB
def decay_fixed_in_time_warp(x):
return CCC - DDD * math.exp(1.0 - k * x)
def decay_fixed_out_time_warp_real(x):
return EEE * math.exp(k * x) - EEE
Where:
CCC:
AAA: DDD:
EEE:
BBB:
116
Renpy Cookbook
Power functions
If you want more control over the acceleration profile – like if you want a
more or less aggressive acceleration profile – and you don’t want to pay the
price for decay functions, power functions are a cheap substitute.
Note: i made incoming and outgoing versions, but not double-ended ones,
because i don’t know a polynomial sigmoid function off the top of my head.
A Taylor series expansion of a sigmoid function would work. i thought about
the Taylor series expansion of the Error function, but if anyone knows a
Taylor series expansion of the Heaviside (step) function between −½ and ½ –
not a Fourier series! – that would be perfect.
117
Renpy Cookbook
power_in_time_warp = renpy.curry(power_in_time_warp_real)
power_out_time_warp = renpy.curry(power_out_time_warp_real)
Tip: If you want a really cheap deceleration profile, a quick and dirty hack is
to pass a value greater than zero but less than one to power_out_time_warp. It
won’t give you a very pretty profile, but it will give you a very efficient one.
Animation “bop”
This next profile is not so much about modelling reality as it is fun. Next time
you watch a kid’s cartoon – Disney stuff is a good example – watch carefully
how characters move. If a character is looking down, and then someone calls
her, she doesn’t just lift her head. What she does is kind of pull down, then
look up. They do this for pretty much every motion they make – they kinda
pull back, as if they’re storing up energy to spring, then make the motion.
Just watch here, and you’ll see – for example, at about 1:00, Goofy is lying
down floating in mid-air, and twice he kinda squeezes in before doing
something; at 1:07, it’s a big squeeze, before that is a little one. It’s a silly
little thing, but it adds some life to animations.
Each of these “bop” motions overshoots by around 15%. They make your
objects either go a bit in the wrong direction before moving in the right
direction, or go past where they were supposed to stop then pull back, or
both. There are five different profiles. Three match the standard in (“bop
in”), out (“bop out”) and normal (“bop move”) motions. And then there are
two extra motions that either accelerate at the beginning (“to bop”) or
decelerate at the end smoothly (“bop to”). You could use this to, for
example, have a character at left move from left to centre to get close to a
character at right... they accelerate naturally, but then skid to a stop, realize
they’re too close and pull back very quickly.
118
Renpy Cookbook
def bop_time_warp(x):
return -23.0 * x**5 + 57.5 * x**4 - 55 * x**3 + 25 * x**2 - 3.5 * x
def bop_in_time_warp(x):
return -2.15 * x**2 + 3.15 * x
def bop_out_time_warp(x):
return 2.15 * x**2 - 1.15 * x
def bop_to_time_warp(x):
return -5 * x**5 + 5 * x**4 + x**2
def to_bop_time_warp(x):
return -5 * x**5 + 20 * x**4 - 30 * x**3 + 19 * x**2 - 3 * x
119
Renpy Cookbook
Konami Code
This executes the konami_code label when the Konami Code is entered on the
keyboard. The code consists of the following keys in sequence:
up
up
down
down
left
right
left
right
b
a
It should be fairly easy to change the code and the label branched to.
# This lets you easily add the Konami code to your Ren'Py game. When
# the Konami code (up up down down left right left right a b) has been
# entered, this calls the konami_code label (in a new context, so that
# the current game state isn't lost.
class KonamiListener(renpy.Displayable):
renpy.Displayable.__init__(self)
import pygame
120
Renpy Cookbook
pygame.K_RIGHT,
pygame.K_b,
pygame.K_a,
]
# If it's not the key we want, go back to the start of the statem
# machine.
if ev.key != self.code[self.state]:
self.state = 0
return
# If we are at the end of the code, then call the target label in
# the new context. (After we reset the state machine.)
if self.state == len(self.code):
self.state = 0
renpy.call_in_new_context(self.target)
return
config.overlay_functions.append(konami_overlay)
"While they might not be of much use in a dating sim, good for you!"
return
121
Renpy Cookbook
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)
122
Renpy Cookbook
scene hello
with Pan((0, 0), (800, 300), 1.5)
with fade
with Pause(2.0)
scene black
"Hello world ('_')/~"
init python:
def init_variables():
initvarlabels = [label for label in renpy.get_all_labels() if label.endswith('__init_variables') ]
for l in initvarlabels:
renpy.call_in_new_context(l)
123
Renpy Cookbook
To use this file, simply add a call to the init_variables function to the start and
after_load labels of your game:
label start:
$ init_variables()
# your normal code here
label after_load:
$ init_variables()
# your normal code here
return
label __init_variables:
python:
if not hasattr(renpy.store,'a_list'): #important!
a_list = []
if not hasattr(renpy.store,'stats_dict'): # important!
stats_dict = {
'v':{
'lvl':1,
'maxhp':4,
'hp':3,
},
'l':{
'lvl':3,
'maxhp':6,
'hp':5,
},
}
return
init -500:
image aliceblue = Solid("#f0f8ff")
image antiquewhite = Solid("#faebd7")
image aqua = Solid("#00ffff")
image aquamarine = Solid("#7fffd4")
image azure = Solid("#f0ffff")
image beige = Solid("#f5f5dc")
image bisque = Solid("#ffe4c4")
image black = Solid("#000000")
image blanchedalmond = Solid("#ffebcd")
image blue = Solid("#0000ff")
124
Renpy Cookbook
125
Renpy Cookbook
126
Renpy Cookbook
Either place this init block into an existing script or in its own rpy file. Now
you can show lightgoldenrodyellow or scene mediumspringgreen etc. if you
are so inclined.
127
Renpy Cookbook
init:
#gfx initializer
python:
import os
for fname in os.listdir(config.gamedir + '/gfx'):
if fname.endswith(('.jpg', '.png')):
tag = fname[:-4]
fname = 'gfx/' + fname
renpy.image(tag, fname)
You'll need argparse and beautifulsoup, both of which you can install with
easy_install or pip install. Then from a command line you can run the
celtx2renpy.py converter with your screenplay script.
The input file that it expects is not the .celtx file, but the .html screenplay
script file embedded in the .celtx file, which is a normal .zip archive and you
should be able to open in any archive program. (If you are working with
version control, an automation tool called musdex) for version-controlling
such zip archives as bundles of files, which can handle the extraction for you.)
The converter follows a few simple script conventions, that are meant look
meaningfully well in Celtx and are relatively close to their screenplay
intentions/counterparts:
Scene Heading
Starts a new "label" named from the lower-cased text up to the first
double dash. Ex: "OPENING -- THE BEGINNING" starts a new label
called "opening".
Action
Actions become character-less (narrator) dialog.
128
Renpy Cookbook
129
Renpy Cookbook
You can let the image fade in over the course of 3 seconds, starting 2 seconds
after the text appeared:
130
Renpy Cookbook
# This is used a the beginning of label, as the most logical place for ambient noises to begin.. :P
$ ambient(("sounds/ambient02.ogg","sounds/ambient06.ogg","sounds/ambient09.ogg"), 4)
131