Documentation
How Ren'Py works
Labels, characters, art, choices and jumps — and where each lives in the editor.
Ren'Py is a scripting language for visual novels. Netwrck runs Ren'Py 8.5 in the browser. You do not install a desktop SDK: the editor is the IDE, and Build & run is the compiler.
A game is a folder of .rpy files plus assets. The engine starts at label start in game/script.rpy. Everything else is optional files you jump to, call, or init in.
Open a starter project in the editor and keep game/script.rpy on the left while you read this. The examples below are that file, cut down.
A script is indented text
Statements live in blocks. A block is a line ending in : plus indented lines under it. Indent with four spaces, same as Python. A missing colon or mixed tabs will fail the build; the problem list under the editor buffer names the file and line.
label, menu, if and style all open blocks. Dialogue does not.
label start:
"This line is narration."
m "This line is spoken by character m."
return
Characters
define binds a short id to a Character. The id is what you type before a spoken line. The name is what the player sees in the nametag. Colour is the nametag colour.
Put define at the top of game/script.rpy, or in its own file such as game/characters.rpy. Either is loaded; Ren'Py concatenates every .rpy at compile time.
define m = Character("Mara", color="#f0a9b6")
define i = Character("Ivo", color="#f2b477")
m "You heard it too, didn't you?"
i "It is not a distress call."
A quoted line with no character id is the narrator. Use that for scene description.
Labels are rooms in the story
A label is a named location. Play begins at label start. jump observatory moves there and does not come back. call observatory runs that label then returns, like a subroutine.
The Go to label menu in the editor lists every label in the open project and seeks the preview to it. Routes (F2) draws the jump graph.
Every path must eventually return or jump somewhere that does. A label that falls off the end of the file ends the game.
label start:
scene bg platform with fade
m "The last train had gone."
jump observatory
label observatory:
scene bg observatory with dissolve
i "The signal returns every sixteen minutes."
return
Art and audio are files plus names
image bg platform = "images/platform.webp" declares a displayable. The path is relative to game/, so the file on disk is game/images/platform.webp. scene bg platform clears the layer and shows it. show mara at left adds a sprite. hide mara removes it.
play music "music/last-signal.ogg" plays a loop from game/music/ or game/audio/ depending on where you put the file. stop music fadeout 2.0 ends it.
play movie "movies/opening.webm" plays a generated film over the stage. Without loop it is a cutscene: dialogue waits until the file ends or the player clicks. play movie "movies/avatars/mara.webm" loop is a talking-character overlay. $ renpy.movie_cutscene("movies/opening.webm") is the same cutscene path. Video generator writes into game/movies/. Netwrck animated avatars import a talking video there when the pack has one.
In the editor: click an image in the tree to preview it, or generate one with Art generator (writes into game/images/) and point image / scene at the new filename. Music generator writes into game/audio/. Metaphorize prompt turns a scene beat into a music-gen prompt. After adding a file, Build & run so the player can see it.
image bg platform = "images/platform.webp"
image mara = "images/mara.webp"
label start:
play music "music/last-signal.ogg" fadein 1.5
play movie "movies/opening.webm"
scene bg platform with fade
play movie "movies/avatars/mara.webm" loop
show mara at left
m "You heard it too, didn't you?"
stop movie
return
If Build succeeds but the stage is empty, the image path is wrong. Scan assets in the problem list checks that every scene / show / play file exists. Movie() displayables still do not render; use play movie.
Choices are a menu
menu: then quoted options, each with an indented block. That block usually sets a flag and jumps. Options the player did not pick are skipped.
Flags are default variables. default signal_found = False at the top, then $ signal_found = True inside a choice. $ means a Python statement. Later, if signal_found: branches the dialogue.
default signal_found = False
label start:
menu:
"Admit you followed the signal.":
$ signal_found = True
jump observatory
"Say you only came for the view.":
jump observatory
label observatory:
if signal_found:
i "You matched the final note."
else:
i "Mara matched the final note."
return
Keep choice text on one line. A second quoted string on the same option is a hint, not a second choice.
Where to put new scenes
Small games stay in game/script.rpy. When that file gets long, New file → game/act2.rpy and put more labels there. You can jump to a label in another file; the name must be unique across the project.
game/gui.rpy is sizes and colours. game/styles.rpy is the look of the say window, menus and buttons. You rarely need to touch them to write a story. Do not put plot in those two files.
The writing agent (bottom of the right pane, ctrl-k) can draft or rewrite a label. It writes into the project files; read the diff it proposes before you keep it. Ghost (tab to accept) completes at the cursor and is a paid call.
Build, then play
Ren'Py does not run the .rpy text directly in the player. Build & run compiles it. Until you build, the preview is the last good compile — or empty on a brand new project.
A successful build updates /visualnovel/play/<id>. Share link is the same game without the editor chrome. The browser player documents keys, saves and rollback.
Live AI replies inside a scene use the ai statement. That is a paid call per line; see AI characters.
Netwrck