Ren'Py Cookbook

You might also like

Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 29

Ren’Py Cookbook

Shake Effect
You can already use the default vpunch and hpunch transitions to shake the screen. But what
about a shake that goes in every directions randomly, Phoenix Wright style?

init:

python:

import math

class Shaker(object):

anchors = {
'top' : 0.0,
'center' : 0.5,
'bottom' : 1.0,
'left' : 0.0,
'right' : 1.0,
}

def __init__(self, start, child, dist):


if start is None:
start = child.get_placement()
#
self.start = [ self.anchors.get(i, i) for i in start ] #
central position
self.dist = dist # maximum distance, in pixels, from the
starting point
self.child = child

def __call__(self, t, sizes):


# Float to integer... turns floating point numbers to
# integers.
def fti(x, r):
if x is None:
x = 0
if isinstance(x, float):
return int(x * r)
else:
return x

xpos, ypos, xanchor, yanchor = [ fti(a, b) for a, b in


zip(self.start, sizes) ]

xpos = xpos - xanchor


ypos = ypos - yanchor

nx = xpos + (1.0-t) * self.dist * (renpy.random.random()*2-


1)
ny = ypos + (1.0-t) * self.dist * (renpy.random.random()*2-
1)

return (int(nx), int(ny), 0, 0)

def _Shake(start, time, child=None, dist=100.0, **properties):

move = Shaker(start, child, dist=dist)

return renpy.display.layout.Motion(move,
time,
child,
add_sizes=True,
**properties)

Shake = renpy.curry(_Shake)
#

You can now use it inline or create a few transitions :

init:
$ sshake = Shake((0, 0, 0, 0), 1.0, dist=15)

Syntax : Shake(position, duration, maximum distance) with 'position' being a tuple of 4


values : x-position, y-position, xanchor, yanchor.

Examples
show phoenix think with dissolve
ph "I think this game lacks a little something... Right! Some..."
ph "Objection!" with sshake # shaking the whole screen with the
previously defined 'sshake'
ph "Eheh... Uh? What the?! It's reverberating!"
show phoenix at Shake((0.5, 1.0, 0.5, 1.0), 1.0, dist=5)
with None
# shaking the sprite by placing it at the center (where it already was)
ph "Ng!..."
show phoenix at center, Shake(None, 1.0, dist=5)
with None
# exactly the same thing but it shows how you can first give a predefined
position and,
# giving None as a position for Shake, let it take 'center' as the shaking
position.
ph "AAgh! Stop it already!"
ph "...... Is it over?"
with Shake((0, 0, 0, 0), 3.0, dist=30)
# some serious shaking of the screen for 3 seconds
# try not to abuse high values of 'dist' since it can make things hard on
the eyes
Lip Flap
Sometimes, you want to synchronize a character's lip movements to her dialogue. That's what
Lip Flap is for.

First, download lip_flap.rpy, and add it to your game directory. This file contains the
definition of the LipFlap function. This function returns an object that can be used in a show
statement to produce lip flap.

Function: LipFlap (prefix, default="", suffix=".png", combine=...):

prefix - The prefix of filenames that are used to produce lip flap.

default - The default lip that is used when no parameters is given.

suffix - A suffix that is used.

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

# Show Ayaki with her default lip, A.


show ayaki shy

"..."

# Show Ayaki with varying lips.


show ayaki shy "A .15 B .20 C .15 A .15 C .15 A"
"Ayaki" "Whatsoever, things are true."

return
SnowBlossom
Function: SnowBlossom (image, count=10, border=50, xspeed=(20, 50), yspeed=(100, 200),
start=0, horizontal=False):

This implements the snowblossom effect, which is a simple linear motion up or down, left, or
right. This effect can be used for falling cherry blossoms, falling snow, rising bubbles, and
drifting dandelions, along with other things.

image - The image that is to be used for the particles. This can actually be any displayable, so
it's okay to use an Animation as an argument to this parameter.

count - The number of particles to maintain at any given time. (Realize that not all of the
particles may be on the screen at once.)

border - How many pixels off the screen to maintain a particle for. This is used to ensure that
a particle is not displayed on the screen when it is created, and that it is completely off the
screen when it is destroyed.

xspeed - The horizontal speed of the particles, in pixels per second. This may be a single
integer, or it may be a tuple of two integers. In the latter case, the two numbers are used as a
range from which to pick the horizontal speed for each particle. The numbers can be positive
or negative, as long as the second is larger then the first.

yspeed - The vertical speed of the particles, in pixels per second. This may be a single integer,
but it should almost certainly be a pair of integers which are used as a range from which to
pick the vertical speed of each particle. (Using a single number will lead to every particle
being used in a wave... not what is wanted.) The second number in the tuple should be larger
then the first.

start - This is the number of seconds it will take to start all particles moving. Setting this to a
non-zero number prevents an initial wave of particles from overwhelming the screen. Each
particle will start in a random amount of time less than this number of seconds.

fast - If true, then all particles will be started at once, and they will be started at random places
on the screen, rather then on the top or bottom.

horizontal - If true, the particles start on the left or right edge of the screen. If false, they start
along the top or bottom edge.

Example
init:
image blossoms = SnowBlossom(Animation("sakura1.png", 0.15,
"sakura2.png", 0.15))

show blossoms

"Finally, there's now a particle motion system in


Ren'Py."

"It can be used to animate things like falling cherry


blossoms, falling snow, and rising bubbles."

"While there's convenient support for things rising and


falling in straight lines, it's also possible to define
your own motions."

"The sky's the limit."

"Or the ground, in the case of these cherry blossoms."

hide blossoms with dissolve

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.

This is the factory class that creates the particle burst

init:
python:

class ExplodeFactory: # the factory that makes the particles

def __init__(self, theDisplayable, explodeTime=0,


numParticles=20):
self.displayable = theDisplayable
self.time = explodeTime
self.particleMax = numParticles

theDisplayable The displayable or image name you want to use for your particles

explodeTime The time for the burst to keep emitting particles. A value of zero is no time limit.
numParticles The limit for the number of particle on screen.

Here is an example of how to use it.

Put the following into your init block.

image boom = Particles(ExplodeFactory("star.png", numParticles=80,


explodeTime = 1.0))

Then you can just "show boom" to show the particle burst.

Here is the rest of the code

def create(self, partList, timePassed):


newParticles = None
if partList == None or len(partList) < self.particleMax:
if timePassed < self.time or self.time == 0:
newParticles = self.createParticles()
return newParticles

def createParticles(self):
timeDelay = renpy.random.random() * 0.6
return [ExplodeParticle(self.displayable, timeDelay)]

def predict(self):
return [self.displayable]

init:
python:
class ExplodeParticle:

def __init__(self, theDisplayable, timeDelay):


self.displayable = theDisplayable
self.delay = timeDelay
self.xSpeed = (renpy.random.random() - 0.5) * 0.02
self.ySpeed = (renpy.random.random() - 0.5) * 0.02
self.xPos = 0.5
self.yPos = 0.5

def update(self, theTime):

if (theTime > self.delay):


self.xPos += self.xSpeed
self.yPos += self.ySpeed

if self.xPos > 1.05 or self.xPos < -1.05 or self.yPos >


1.05 or self.yPos < -1.05:
return None

return (self.xPos, self.yPos, theTime, self.displayable)


Creating a more realistic snow effect
This recipe lets you implement a more realistic snowfall effect than the one you can get using
SnowBlossom. Code is pretty much commented, and should work with Ren'Py 6.9.0+ (it'll
work in early versions of Ren'Py if you change line #116 to use im.FactorZoom instead of
im.FactorScale)

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

# initialize random numbers


random.seed()

#################################################################
# 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)

@parm {image} image:


The image used as the snowflakes. This should always be a image
file or an im object,
since we'll apply im transformations in it.

@parm {int} max_particles:


The maximum number of particles at once in the screen.

@parm {float} speed:


The base vertical speed of the particles. The higher the value,
the faster particles will fall.
Values below 1 will be changed to 1

@parm {float} wind:


The max wind force that'll be applyed to the particles.

@parm {Tuple ({int} min, {int} max)} xborder:


The horizontal border range. A random value between those two
will be applyed when creating particles.

@parm {Tuple ({int} min, {int} max)} yborder:


The vertical border range. A random value between those two
will be applyed when creating particles.
The higher the values, the fartest from the screen they will be
created.
"""
return Particles(SnowFactory(image, max_particles, speed, wind,
xborder, yborder, **kwargs))

# ---------------------------------------------------------------
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 particle's speed


self.speed = speed

# the wind's speed


self.wind = wind

# the horizontal/vertical range to create particles


self.xborder = xborder
self.yborder = yborder

# 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)

# initialize the images


self.image = self.image_init(image)

def create(self, particles, st):


"""
This is internally called every frame by the Particles object
to create new particles.
We'll just create new particles if the number of particles on
the screen is
lower than the max number of particles we can have.
"""
# if we can create a new particle...
if particles is None or len(particles) < self.max_particles:

# generate a random depth for the particle


depth = random.randint(1, self.depth)

# 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)

return [ SnowParticle(self.image[depth-1], # the image


used by the particle
random.uniform(-self.wind,
self.wind)*depth_speed, # wind's force
self.speed*depth_speed, # the
vertical speed of the particle
random.randint(self.xborder[0],
self.xborder[1]), # horizontal border
random.randint(self.yborder[0],
self.yborder[1]), # vertical border
) ]

def image_init(self, image):


"""
This is called internally to initialize the images.
will create a list of images with different sizes, so we
can predict them all and use the cached versions to make it
more memory efficient.
"""
rv = [ ]

# generate the array of images for each possible depth value.


for depth in range(self.depth):
# Resize and adjust the alpha value based on the depth of
the image
p = 1.1 - depth/(self.depth+0.0)
if p > 1:
p = 1.0

rv.append( im.FactorScale( im.Alpha(image, p), p ) )

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.
"""

# The image used by this particle


self.image = image

# 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

# the horizontal/vertical positions of this particle


self.xpos = random.uniform(0-xborder,
renpy.config.screen_width+xborder)
self.ypos = -yborder

def update(self, st):


"""
Called internally in every frame to update the particle.
"""

# calculate lag
if self.oldst is None:
self.oldst = st

lag = st - self.oldst
self.oldst = st

# update the position


self.xpos += lag * self.wind
self.ypos += lag * self.speed

# 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

# returns the particle as a Tuple (xpos, ypos, time, image)


# since it expects horizontal and vertical positions to be
integers, we have to convert
# it (internal positions use float for smooth movements =D)
return int(self.xpos), int(self.ypos), st, self.image

Example
init:
image snow = Snow("snowflake.png")

label start:
show snow
"Hey, look! It's snowing."
Information Screen
This is a screen that can be used to display information ranging from stats to affection points
to clues to whatever you like, really. For this example, we will show affection points of two
love interests.

In the Init Section


You need to define your variables here.

init:
$ bob_points = 0 # this is a variable for bob's affection points
throughout your game
$ larry_points = 0 # this is a variable for larry's affection points
throughout your game
$ bob_max = 10 # this variable should be set to bob's maximum affection
points
$ larry_max = 10 # this variable should be set to larry's maximum
affection points
$ variable = False # when false, the affection screen button doesn't
appear on the screen

In Screens.rpy
You can actually define this screen anywhere, but it might be easiest to group it with your
other screens.

screen button:
vbox xalign 0.1 yalign 0.1:
textbutton "Show affection points" action
ui.callsinnewcontext("aff_screen_label")
# you can also use an image button:
imagebutton:
idle "button_idle.png"
hover "button_hover.png"
action ui.callsinnewcontext("aff_screen_label")

The above code is for a button that will open up the stats/affection points screen when clicked.
Remember to change the image filenames and xalign/yalign coordinates for your own code. If
you don't want the button to show up all the time, you can do something like this:

screen button:
if variable:
vbox xalign 0.1 yalign 0.1:
textbutton "Show affection points" action
ui.callsinnewcontext("aff_screen_label")
# you can also use an image button:
imagebutton:
idle "button_idle.png"
hover "button_hover.png"
action ui.callsinnewcontext("aff_screen_label")
This makes it so that the button only shows up if "variable" is set to True. As for the actual
content of the stats/affection points screen:

screen aff_screen:
frame:
has vbox
text "Bob: [bob_points] points"
text "Larry: [larry_points] points"
textbutton "Return" action Return()

label aff_screen_label:
call screen aff_screen
return

This would show up as "Bob: ### points" and "Larry: ### points", with ### being whatever
number the variables are set to at that point in time. If you'd like to use bars instead of text
(useful for stats/affection points), you can use bars like so:

screen aff_screen:
frame:
has vbox
hbox:
label "Bob: " xminimum 100
bar range bob_max value bob_points xmaximum 400
hbox:
label "Larry: " xminimum 100
bar range larry_max value larry_points xmaximum 400
textbutton "Return" action Return()

label aff_screen_label:
call screen aff_screen
return

In the game script


label start:
show screen button # this will make the button show up on your screen

If you're making it so that the button doesn't always show up:

label start:
show screen button
# though we said to show it, the button won't show up until the
variable is set to True
"Blah blah blah!"
$ variable = True
# and now the button appears!
"Blah blah blah~"
$ variable = False
# and the button disappears again...
Customizing Textbox Appearance
Before we can explain how to customize your dialogue box, you need to design it first. The
easiest way to do this is to render a mock of a dialogue box in your photo editor. Get it to look
exactly how you want, text included. Even if it takes a little bit more work, it's possible to
recreate most mock-ups.

There are two ways you can save your design to be incorporated into Ren'Py. You can either
create a box that will be chopped up by the Frame code, or you can plug in the dialogue box
image directly by cropping it to be the width of your game engine.

Using frames
Dialogue boxes that use Frame look for the corners of the image, and then stretch whatever's
in between to fit the proportions you give it in the code.

style.window.background = Frame("PinkBox.png", 25, 25)

This line of code is already in your options.rpy file, except with a filler file. Replace the name
and size of your corners with your image's info. Be sure to uncomment the code by removing
the # mark before it.

Of course, Ren'Py didn't automatically fix my margins or text for me! Straight out of a new
game, your new framed dialogue box will need a lot of tweaking. Most everything you need is
already there for you, but it is commented out.
Go to your options.rpy file. Find these lines of code:

## Margin is space surrounding the window, where the background


## is not drawn.

# style.window.left_margin = 6
# style.window.right_margin = 6
# style.window.top_margin = 6
# style.window.bottom_margin = 6

## Padding is space inside the window, where the background is


## drawn.

# style.window.left_padding = 6
# style.window.right_padding = 6
# style.window.top_padding = 6
# style.window.bottom_padding = 6

## This is the minimum height of the window, including the margins


## and padding.

# style.window.yminimum = 250

Again, uncomment what you use by delete the # marks.

The Margin of the box is for how far away the dialogue box is from the edges of the screen.
This is what our PinkBox looks like with large margins (padding untouched):

Padding of the box is how far away the text is from the edges of the dialogue box. Here is
what is looks like with large padding (margin back to default):

yminimum forces the box to be a certain amount of pixels high. Use it to define how tall the
box must be at all times (but it can get taller if it needs to be). This is an important property
for the pre-fitted image dialogue boxes next.
With your new frame box in Ren'Py, it is up to you to play around with padding and margin
until it looks exactly how you want it to. Trial and error is sometimes the only path here. To
make your life easier, you can take advantage of the refresh feature by hitting SHIFT+R to
reload the game to see your changes immediately.

Using pre-fitted images


The other type is easier to implement because you have already placed it how you want it to
look in your photo editor. The downside is that this isn't flexible and you will need to make
new images for every alteration you want. The upside is that it's quick, easy, and you get the
exact look you want.

To make this easy on you, do NOT crop your image to just the dialogue box in your editor.
Instead, include the spaces around the edge (up to the top of the box) so that you don't have to
alter margins within Ren'Py. This means that your resulting image should be as wide as your
game (default: 800px).

Just insert your file name into the style.window.background property already in your
options.rpy

You'll need to make sure your yminimum is set to be the exact height of your image, and that
your padding is altered so that it fights into your box (again, trial and error might be the only
way). Set all your margins to 0. When you start your game, you should see your box! That
was easy, wasn't it?

If you want a separate window for your character names, you can either save a namebox as a
separate file (recommended) or include it in your dialogue box as one big image. The problem
with the latter is that you will need to make two versions of the same image: one with the
character box, and one without. Because the narrator (when no character is speaking) will
look weird with an empty name box lying around.

Making character-specific textboxes

To make two (or more) different versions of your text box, put the one you're going to use the
most as the default:
style.window.background = "dialoguebox.png"

And then, for the characters you want to have a different box (in this case, the narrator
because it's not a character that has a name). You can use the Frame function with this, too.
Just copy whatever you did with the window.background code and put it after
window_background in your character name (changing the filename): Code:

$ narrator = Character(None, window_background="dialoguebox_noname.png")

It should retain all of the other properties you defined for the default window.

If you find you need to make specific adjustments for each character's text, you have to put
properties like these into your character calls:

$ a = Character('Name', window_top_padding=15)
$ b = Character('Name', what_xpos=50)
$ c = Character('Name', who_yalign=0.5)

The important parts of those code are the "window_", "what_" and "who_" prefixes. You can
use about any property you want, but you have to tell the character what you're editing.
"window_" is a property that edits the whole window, similar to what you did for
style.window. "what_" edits the text (what's being said) and "who_" edits the character's name
(who's saying it).

To save you keystrokes, you should format the default text style so you don't have to
copy/paste your adjustments to every character.

Styling Text
To edit how your text looks, look a little further down in your options.rpy for these lines of
code:

## The file containing the default font.


# style.default.font = "DejaVuSans.ttf"

## The default size of text.


# style.default.size = 22

Uncomment those lines to change the font or size of the text. Even though it is not included in
your file, you can also change just about any aspect of the styles that you want. You can
change the color, add a drop_shadow, or an outline if you wanted. Here's an example of each:

style.default.color = "#000000" #Makes text black

style.default.drop_shadow = [(1, 1)] #Adds a shadow one pixel to the


right and one pixel down

style.default.outlines = [(4, "#ff0000", 0, 0), (2, "#fff", 0, 0)]


#Adds a white outline 4 pixels thick, and then a black outline, 2 pixels
thick
style.default.xalign = 1.0 #aligned the text completely to the right

In addition to those, you can use all of the text properties seen here.

To edit specific text, like the name of the character, you need to find out what that text's style
is called. An easy way to find styles is to launch your game and then hover your mouse over
the text you want to change. Hit the keys SHIFT+I and a black screen will come up with all
the styles and parent styles that object is using. The one that you want is the last style listed. If
you see this:

Then you want say_label. For styles you can't hover on, you can try to find in the Style
Hierarchy by hitting SHIFT+D.

Now that you have the style's name, you can plug it into this forumla:

style.STYLE_NAME.PROPERTY = VALUE

 STYLE_NAME: This would be the name you found with the style inspector, like
say_label.
 PROPERTY: What you want to change about that style. Again, you can find all the
kinds of properties here.
 VALUE: Whatever you're setting the style property to. Numbers for sizes, strings
(things in quotes) for filenames, Hex values for colors, etc.

So if we wanted to edit say_label to be a different font, I would put this at the bottom of my
options.rpy:

style.say_label.font = "myfont.tff"

Making sure my font is in the top level of my folder.

Styling the Names


Using everything we learned on this page, we can now put each of the elements where we
want and make them look how we want, with some respect to Ren'Py's limitations. Like how
to implement your images, there are two ways to show your character names: embedded into
one dialogue window, or to have the name in a separate window from the dialogue box. By
default, the name is embedded into one window. To change this, you need to insert a piece of
code into your character definitions called show_two_window. Set it to True, and do this for
every character you want to have a separate window. By default, it will inherit the same
background as your dialogue box.

The styles for the name and window are:

 say_label
 say_who_window (only if show_two_window is True)

To change the background of your namebox, you would do it exactly like how you did it for
the dialogue window, except you replace the style name with say_who_window:

style.say_who_window.background = Frame("namebox.png", 15, 15)

Again, replace with your image and corner size, or don't even use a Frame at all!

Use positional properties like xalign or xpos and xanchor to place the name within the box
you have made.

Click to Continue icon


A fun feature is to show an icon to the player when they need to click in order to advance the
story. To add this to your boxes, you need to add it to the characters you want to use it, like
show_two_window.

Just link to the image's call name in quotes. If you want to animate it, build the animation
first, and then link to the animations call name. Also, you can have the CTC indicator
embedded into the text, placed right after the last word on the screen, or you can place it in a
fixed location on the screen. Here is an example of an animated CTC icon that is placed in the
corner of the screen (fixed):

image ctc_animation = Animation("ctc01.png", 0.2, "ctc02.png", 0.2,


"ctc03.png", 0.2, "ctc04.png", 0.2, xpos=0.99, ypos=0.99, xanchor=1.0,
yanchor=1.0)

$ a = Character('Name', ctc="ctc_animation", ctc_position="fixed")

You can use the new ATL language to make CTC animations, too. Just plug in the name in
the quotes. Make sure to put position properties into the image if you want it to be fixed on
the screen somewhere.
See this forum thread for an example of what it looks like all put together.

Adding a slightly-complicated Digital Calendar


The following variables must exist in your game path/route (label start is a good choice) in
order for the Digital Calendar to function properly (actual values are simply examples):
$ minutes = 750#must be initially defined.
$ clock = True#make false to hide the calendar
$ theweekday = 3#tuesday, the number of the weekday, this automatically
changes but must be initially assigned
$ themonth = 9#september, the number of the month, this automatically
changes but must be initially assigned
$ theday = 21#this automatically changes but must be initially assigned
$ theyear = 2010#this automatically changes but must be initially
assigned
$ dayofyear = 264#you must calculate this properly, this automatically
changes
$ yearlim = 365#initially define it as 265 or 366, whichever is
correct, this gets changed automatically later
$ daylim = 30#initially define it as 28, 29, 30, or 31, whichever is
correct, this gets changed automatically later
$ stringweekday = "Tuesday"#3, the string of the weekday, this
automatically changes but must be initially assigned
$ stringmonth = "September"#9, the string of the month, this
automatically changes but must be initially assigned

The following code must exist in one of your code files, preferably as a separate rpy file.
(X/yalign can be changed if desired...)

init python:
def time_callback():#constantly calculate the time
if (hasattr(store, 'minutes')):
if (store.minutes > 1440):
store.minutes = store.minutes - 1440
store.theweekday = store.theweekday + 1
store.theday = store.theday + 1
store.dayofyear = dayofyear + 1

if (hasattr(store, 'theweekday')):#setweekday
if store.theweekday > 7:
store.theweekday = store.theweekday - 7
if store.theweekday == 1:
store.stringweekday = "Sunday"
elif store.theweekday == 2:
store.stringweekday = "Monday"
elif store.theweekday == 3:
store.stringweekday = "Tuesday"
elif store.theweekday == 4:
store.stringweekday = "Wednesday"
elif store.theweekday == 5:
store.stringweekday = "Thursday"
elif store.theweekday == 6:
store.stringweekday = "Friday"
elif store.theweekday == 7:
store.stringweekday = "Saturday"
else:
store.stringweekday = "Error"

if (hasattr(store, 'theday')):#monthlim
if store.theday > store.daylim:
store.theday = store.theday - store.daylim

if (hasattr(store, 'themonth')):#setmonth
if store.themonth == 1:
store.stringmonth = "January"
store.daylim = 31
if store.themonth == 2:
store.stringmonth = "February"
if ((((int(store.theyear) / 4)*4) - store.theyear) == 0):
store.daylim = 29
else:
store.daylim = 28
if store.themonth == 3:
store.stringmonth = "March"
store.daylim = 31
if store.themonth == 4:
store.stringmonth = "April"
store.daylim = 30
if store.themonth == 5:
store.stringmonth = "May"
store.daylim = 31
if store.themonth == 6:
store.stringmonth = "June"
store.daylim = 30
if store.themonth == 7:
store.stringmonth = "July"
store.daylim = 31
if store.themonth == 8:
store.stringmonth = "August"
store.daylim = 31
if store.themonth == 9:
store.stringmonth = "September"
store.daylim = 30
if store.themonth == 10:
store.stringmonth = "October"
store.daylim = 31
if store.themonth == 11:
store.stringmonth = "November"
store.daylim = 30
if store.themonth == 12:
store.stringmonth = "December"
store.daylim = 31

if (hasattr(store, 'dayofyear') and hasattr(store,


'yearlim')):#yearstuff
if store.dayofyear > store.yearlim:
store.dayofyear = store.dayofyear - store.yearlim
store.theyear = store.theyear + 1
if ((((int(store.theyear) / 4)*4) - store.theyear) == 0):
store.yearlim = 366
else:
store.yearlim = 365
config.python_callbacks.append(time_callback)

def Calendar():
ui.frame(xfill=False, xminimum = 400, yminimum=None, xalign=1.0,
yalign = 0.805)
ui.vbox()
ui.text("(%s) - %s %d %d" % (stringweekday, stringmonth, theday,
theyear), xalign=1.0, size=20)
ui.close()

def Clocks():
ui.frame(xfill=False, xminimum = 110, yminimum=None, xalign=1.0,
yalign = 0.76)
ui.vbox()
if (minutes > 719):
if ((minutes - (int(minutes/60))*60) < 10):
if((int(minutes/60)) == 12):
ui.text("12:0%d PM" % ((minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
ui.text("%d:0%d PM" % ((int(minutes/60)-12), (minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
if((int(minutes/60)) == 12):
ui.text("12:%d PM" % ((minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
ui.text("%d:%d PM" % ((int(minutes/60)-12), (minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
if ((minutes - (int(minutes/60))*60) < 10):
if((int(minutes/60)) == 0):
ui.text("12:0%d AM" % ((minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
ui.text("%d:0%d AM" % ((int(minutes/60)), (minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
if((int(minutes/60)) == 0):
ui.text("12:%d AM" % ((minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
else:
ui.text("%d:%d AM" % ((int(minutes/60)), (minutes -
(int(minutes/60))*60)), xalign=1.0, size=20)
ui.close()

Then make it appear. I use the following code:

screen say:
if(clock):
$ Calendar()
$ Clocks()

After you do this, all you have to do is add minutes on like so (in the actual game script):

$ minutes = minutes + 112#or whatever

Chinese and Japanese


The procedure for supporting Chinese and Japanese changed in Ren'Py 6.3.2, please ensure
you use this version.
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.

2) You need to download a free Chinese or Japanese font, place it in the game directory, and
then tell Ren'Py to use it. You'll also want to set the language to "eastasian". For example, if
your font is mikachan.ttf, then you would write:

Code:

init:
$ style.default.font = "mikachan.ttf"
$ style.default.language = "eastasian"

You'll want a font you can distribute with the game.

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.)

Hangul (Korean Alphabet) Input Class


You can't get hangul by renpy.input(6.12.1), so if you want to put and use hangul character in
your Ren'py game, you can use this code.

Before using it with your game, notice that

a) It implements Dubeolsik keyboard,


b) and it can't get alphabet characters.

How To Use
1. Download this rpy file and put it in your project\game directory.

2. And then you will be able to use this function.

Function: hangulInput (prompt = 'What's your name?', default = "", length = 5):

Return two values. First one is the word inputted by user, and the other is 1 or 0, indicating if
the last word has final consonant or not(So one can use it to change postpositional particle
depending on the inputted text).
prompt - A prompt that is used to ask the user for the text.

default - A default for the text that this input can return.

legnth - A limit to amount of text that this function will return.

allow - A string containing characters that are allowed to be typed into this input. (By default,
allow all characters.)

exclude - A string containing characters that are disallowed from being typed into this input.
(By default, "{}".)

Styles
The following styles are to customize hangul inputter's appearance.

style.input

The style applied to inputted text.

Example
init:
$ style.input.color = '#f00'
transform a:
alpha 0
block:
linear 1.0 alpha 1
linear 1.0 alpha 0
repeat

screen hinput:
add HangulInput('이름을 적어줘!') align (.5, .5) at a

label start:
#python:
#ui.add(HangulInput(length=5))
#name, final=ui.interact()

call screen hinput


$ name, final = _return

'이름: %(name)s\n 받침유무: %(final)s'

Simple Onscreen Inventory


This code adds a simple, items only inventory that will remain onscreen until you set $
showitems = False .

init python:
showitems = True

def display_items_overlay():
if showitems:
inventory_show = "Inventory: "
for i in range(0, len(items)):
item_name = items[i].title()
if i > 0:
inventory_show += ", "
inventory_show += item_name
ui.frame()
ui.text(inventory_show)
config.overlay_functions.append(display_items_overlay)

##
$ items.append("stone") #when you want to add items
$ items.remove("stone")#when you want to remove items
$ showitems = False #when you don't want to show the inventory onscreen
(cutscenes and the like)
$ showitems = True #when you want to reshow the inventory after the
cutscene is over

Money/Affection

With some adaptation, we can also use this code to display the current amount of money, or
display an "Affection Meter" for a romantic interest.

init python:
showaffection= False
affection = 0
def display_affection():
if showaffection:
ui.frame() #This is optional. It adds a frame around the text.
ui.text("Affection: %d" %affection)
config.overlay_functions.append(display_affection)

###

$ showaffection = False #Hides the Affection box thing


$ affection +=5 #This adds 5 affection to the current score
$ affection -=5 #Likewise, this subtracts 5 affection from the current
score

An onscreen money system is virtually the same.

init python:
showmoney= False
money = 0
def display_affection():
if showmoney:
ui.frame() #This is optional. It adds a frame around the text.
ui.text("$ %d" %money)
config.overlay_functions.append(display_money)

###

$ showmoney = False #Hides the Money box thing


$ money +=5 #This adds $5 to the current score
$ money -=5 #Likewise, this subtracts $5 from the current score
Who's that? Changing character names during the game
Quite often the first time a character appears in the game, the player won't know the name of
the character, just that they're the "Maid" perhaps, or the "Stranger". Yet later, when the
player has learned more about them, we want the game to refer to them differently.

Fortunately it's easy to do, using DynamicCharacter. We just use a placeholder (which Ren Py
calls a "variable") and then change the meaning of this placeholder term when we want to.

define 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.

millie "I hope you'll be comfortable here."


millie "We always do our best to make our guests comfortable."
millie "If you need anything, just ask for \"Millie\" - that's me."
$ maid_name = "Millie"
millie "The bell boy's just bringing your suitcases now."

The first three times that Millie speaks, she'll be identified as the "Maid", but after she's told
the player her name, she'll be shown as "Millie".

For this simple example it would be just as easy to use two different Characters and switch
between them after Millie has given her name. But imagine in a more complex game where
Millie only gives her name if the player decides to tip her. By using DynamicCharacter, you
don't have to think about whether the player chose to tip or not, the game will show "Millie"
or "Maid" as appropriate.
How to add an "about" item to the main menu
First, open up screens.rpy (which is included in all new projects), look for "screen
main_menu", add a line like the following in the list of all the other buttons:

textbutton _("About") action ShowMenu("about")

Then, add something like the following anywhere in your project:

screen about:
tag menu

window:
style "gm_root"

frame:
style "menu_frame"
xmargin 10
ymargin 10
has side "t c r b"

label "My visual novel" bottom_margin 10


viewport id "vp":
mousewheel True
has vbox spacing 10

text "This is my first visual novel, be gentle, thank you. :)"


text "\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean \
adipiscing tellus sit amet augue ultrices adipiscing. Suspendisse \
fermentum imperdiet lobortis. Duis non lectus at massa ornare \
tincidunt. Duis ultricies nisi ac elit rhoncus commodo. In nec \
commodo enim. Vivamus luctus rutrum est eget scelerisque. In ligula \
sapien, consectetur et faucibus at, porttitor id dui. Quisque \
aliquam augue faucibus odio ultrices sagittis. Cras odio orci, \
varius sit amet vulputate eleifend, pulvinar ac augue."

vbar value YScrollValue("vp")


textbutton "Return to menu":
action Return()
top_margin 10

See http://www.renpy.org/doc/html/screens.html for more information about screens. If you're


adding/removing elements, note that the "side" can be a bit picky; for example, if you remove
the label, you need to remove the "t" from the string "t c r b". See
http://www.renpy.org/doc/html/screens.html#side for details.

(See page history for an older version which doesn't use screen language.)
In-game Splashscreen
This special feature allows you to create a skippable set of image transitions, like an in-game
movie. This may save you from creating a MPEG flux if you don't feel like it or if you find it
too hard.

# In the usual init section, place all the images


# you will need for transitions and alpha transitions.
init:
image hello = "myimage0.png" #change here the desired image name
image world = "myimage1.png" #change here the desired image name
image black = Solid((0, 0, 0, 255))

## This here is the flash effect transition


$ flash = Fade(.25, 0, .75, color="#fff")

# I won't lecture you about how to create good "video" scenes.


# This depends entirely on your inspiration.
# It is best you kept one file for your in-game splashcreen (IGS)
# This will be better for future editing.

#The idea here is to create a 2 image IGS


#with some short music and a little flash effect.

# Let's create our IGS.


label OP:
$ mouse_visible = False #Turns off the mouse cursor

#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')

#This here is tricky: it applies the transition effect,


#plus the renpy.pause
#Your first image will appear with a flash effect
#and pause for 1.5 seconds

scene hello
with flash
with Pause(1.5)

#You can also use the pre-defined alpha


#transitions already defined in the Ren'py build.

scene world
with fade
with Pause(1.5)

#You can also use animation effects such as Pan, particles...


#You might eventually create an entire
#animation studio if you're patient enough.

scene hello
with Pan((0, 0), (800, 300), 1.5)
with fade
with Pause(2.0)

#Don't forget to stop the music or it will loop endlessly.


$ renpy.music.stop(fadeout=1.0)

#And return the mouse or you'll feel lost :)


$ mouse_visible = True

#outta the OP!


jump next_scene

# You will end here if you click or press space


label next_scene:

#This little script here is necessary because of skipping,


#the previous code might not have been read.
python:
if renpy.sound.is_playing(channel=7):
renpy.music.stop()
mouse_visible = True

scene black
"Hello world ('_')/~"

You might also like