脚本语言
Novella 脚本是扩展名为 .novella 的 UTF-8 文本文件。该语言的形式与 Ren'Py 相似,因此熟悉 Ren'Py 的人会感到得心应手。条件和赋值使用 Novella 自己的 表达式语言,游戏所需的其他任何功能,则是用 C# 或 Divooka 实现的 run 命令,或者在选择启用 Python 的游戏中,是一个 Python 代码块。
本页面是参考文档。概念 解释语句所作用的模型,主题设置 列出每个 config. 和 theme. 设置,样式 完整介绍 style 语句。
布局
缩进构成代码块:以冒号结尾的行开启一个块,缩进在其下的行是它的主体。制表符按四个空格计算。注释以字符串之外的 # 开头,一直到行尾。
# The garden scene.
label garden:
scene bg garden
if affection > 2: # a block under the if
q smile "Back again?"
else:
q "Oh. It's you."
在括号内、行尾反斜杠之后或三引号字符串内,一个逻辑行可以跨越换行继续。在三引号字符串中,公共缩进会被去除,单个换行会像散文自动换行一样合并为空格,而空行表示换行,因此长段落可以按段落书写:
"""
There is a lake the maps do not show. A lotus as wide as a palace
floats on it, white and faintly gold.
Every night a small boat crosses it.
"""
字符串使用单引号或双引号;\n、\t、\\、\" 和 \' 是转义序列。
多个脚本按路径顺序加载,合并为一个故事。一个标签会落入同一文件中紧随其后的下一个标签;从文件的最后一个标签末尾落出会结束游戏,或者返回到调用者。
定义
定义位于脚本的顶层,在任何标签之外。它们的顺序无关紧要:定义在游戏开始之前就已就位。
label
label start:
"The story begins."
label chapter_title(number, title="Untitled"):
centered "[number]\n[title]"
return
标签是一个具名的块。参数由 call chapter_title("One", "The Teahouse") 绑定,仅在该次调用中有效,并且可以有默认值。没有参数的标签同样可以被跳转或调用。
character
character q "Su Qinglan" color "#c0392b" image qinglan
character wenjing "[player_name]" color "#7fb3c8" image wenjing side
character letter none kind nvl
character ghost "???" text_color "#b0c4de" prefix "“" suffix "”" font "Ma Shan Zheng" size 40 beep "audio/sfx/blip.ogg"
character id "Name" [options] 定义一个说话者。名字中可以插入变量;用 none 代替名字则不显示名字;省略名字则使用 id。选项:
| 选项 | 含义 |
|---|---|
color "#hex"(或 name_color) |
名字的颜色。 |
text_color "#hex"(或 what_color) |
对话的颜色。 |
image tag |
角色的图像标记,这样 q smile "..." 就会更换其图片。 |
side |
在对话框内以侧边图像显示角色的图像。 |
kind adv\|nvl\|centered |
对话框、全屏 NVL 页面,或不带对话框的居中文本。单独的 nvl 是 kind nvl 的简写。 |
prefix "..."、suffix "..." |
加在每句台词前后的文本,例如引号。 |
font "Family"、size 30 |
该角色台词的字体族和字号。 |
voice tag |
该角色在自动语音模式(config.auto_voice)中使用的标签。 |
beep "file" |
文字逐字显示时播放的声音。 |
window_style "name"、namebox_style "name"、who_style "name"、what_style "name" |
用于角色台词某个部件的 样式,代替内置样式:依次为对话框、名字标签、名字和台词。 |
window_background "#200000"、what_size 40、who_color "#ffff00"、... |
部件名与任意 样式属性 连在一起,即为该部件设置该属性,仅对这个角色有效。 |
narrator、centered 和 nvl_narrator 是内置的。define character ... 是另一种写法。
style ghost_window is say_window:
background "#10202cd0"
character ghost "Ghost" window_style "ghost_window" what_size 40 who_color "#a8d8ff"
在较早的选项有所指定时,它们优先于部件的样式:color 为名字着色,除非 who 样式设置了自己的颜色;font、size 和 text_color 设置台词的字体、字号和颜色,覆盖 what 样式。what_color 是 text_color 的另一种写法,而不是部件属性。
image
image paper = "#f4ecd8"
image overlay dusk = "#20101880"
image bg garden night = "bg/garden.jpg"
image qinglan sepia = sepia("sprites/qinglan/smile.png")
image rain = movie("video/rain.webm")
image sign = placeholder("Shop sign")
image card = composite(800, 600, 0, 0, "gui/card.png", 40, 40, "sprites/qinglan/smile.png")
image window = switch(hour < 18, "bg/window_day.jpg", "bg/window_night.jpg")
image name words = source 定义一个图像。大多数游戏都不需要这些定义,因为 图像文件会自行命名。来源是以下之一:
| 来源 | 含义 |
|---|---|
"path/file.png" |
游戏资源中的一个文件。 |
"#rgb"、"#rrggbb"、"#rrggbbaa" |
填满屏幕的纯色。 |
movie("file"[, loop]) |
作为图像播放的视频;除非给出 false,否则循环播放。 |
placeholder("Label"[, background]) |
引擎的替代图:一个剪影,或在 background 为 true 时为渐变。 |
grayscale(s)、sepia(s) |
去色,或去色后加暖色调。 |
tint(s, "#ffd0d0") |
与某种颜色相乘。 |
flip(s[, vertical]) |
水平镜像,或在为 true 时垂直镜像。 |
zoom(s, 0.5) |
缩放。 |
crop(s, x, y, w, h) |
来源中的一个矩形区域,以其像素为单位。 |
blur(s, 4)、alpha(s, 0.5)、brightness(s, 0.2)、saturation(s, 0.5) |
经滤镜处理。 |
composite(w, h, x, y, s, x, y, s, ...) |
在指定尺寸的画布上,按偏移绘制的多个来源。 |
switch(condition, s, condition, s, ..., default) |
第一个条件成立的来源,每次绘制该图像时重新选择。 |
frame(s, left, top[, right, bottom]) |
按自身边框作为九宫格拉伸的图片,与 Ren'Py 的 Frame 相同:用于 样式的背景;在舞台上则按图片原样显示。 |
solid("#203040") |
一种颜色,与 Ren'Py 的 Solid 相同;等同于带引号的颜色。 |
滤镜可以嵌套:blur(grayscale("bg/garden.jpg"), 6)。define image ... 是另一种写法。black 和 white 作为纯色内置,无需定义。
图像也可以是一条时间线,这就是逐帧动画的做法:image name: 下面跟一个 动画 块,块中的图片行说明它显示什么、何时显示。
image candle:
"candle a"
pause 0.5
"candle b" with dissolve
pause 0.5
repeat
show candle at left 会显示闪烁的蜡烛,无论它在哪里显示、变换如何移动它,都会持续闪烁。
layeredimage
layeredimage qinglan:
size 900 1600
always "sprites/qinglan/base.png"
group outfit:
attribute dress "sprites/qinglan/dress.png" default
attribute festival "sprites/qinglan/festival.png"
group face:
attribute neutral "sprites/qinglan/neutral.png" default
attribute smile "sprites/qinglan/smile.png"
attribute sad "sprites/qinglan/sad.png"
group extras multiple:
attribute blush "sprites/qinglan/blush.png" if affection > 3
attribute tears "sprites/qinglan/tears.png"
分层图像由多个图层绘制而成:先绘制每个 always 图层,然后对每个分组绘制所请求的属性,或该分组的 default。同一分组中的属性互斥,除非该分组是 multiple。带 if 的属性只在其条件成立时绘制;位于任何分组之外的 attribute 行自成一个分组。size w h 固定画布尺寸,否则画布尺寸为第一个图层的尺寸。任何组合都可以显示,未提及的属性会被保留:在 show qinglan festival smile 之后,show qinglan sad 仍保留节日服装。
transform
transform bob(height=20):
yoffset 0
ease 1.0 yoffset (-height)
ease 1.0 yoffset 0
repeat
一条具名的动画时间线,可选择带参数,通过 show ... at bob 或 at bob(40) 使用。参见 动画。
transition
transition slow = dissolve(2.0)
transition longfade = fade(1.0, 0.5, 1.5)
transition flashback = fade(0.3, 0.0, 0.3, "#ffffff")
一个具名转场,由某个内置转场加实参构建,或由另一个具名转场构建。参见 转场。
audio
audio theme = "audio/bgm/main_theme.ogg"
define audio.bell = "audio/sfx/bell.ogg"
音频别名,这样脚本就可以写 play music theme 和 play sound bell。
channel
channel rain mixer sfx loop fadeout 1.0
channel thunder mixer sfx noloop norestore
channel name [mixer music|sfx|voice] [loop|noloop] [fadeout n] [norestore] 定义一个音频通道。混音器决定由哪个音量滑块控制它;loop 使其默认循环;fadeout 是它被替换或停止时的默认淡出时长;norestore 使它播放的内容不进入存档和回滚。内置通道:
| 通道 | 混音器 | 循环 | 默认淡出 | 读档时恢复 |
|---|---|---|---|---|
music |
music | 是 | 0.5 秒 | 是 |
sound |
sfx | 否 | 无 | 否 |
voice |
voice | 否 | 无 | 否 |
ambience |
sfx | 是 | 1.0 秒 | 是 |
audio |
sfx | 否 | 无 | 否 |
既非内置也未定义的通道名,会作为不循环的音效播放。
default 和 define
default affection = 0
default inventory = []
default persistent.endings_seen = []
define max_affection = 10
define config.title = "The Red Thread"
define theme.text_size = 40
default name = expression 是新游戏开始时变量的值;对于 persistent. 名称,只有在该持久变量尚未设置时才会设置。define name = expression 是一个常量,在游戏开始时设置,并在每次读档后再次设置,因此对它的更改也会作用于旧存档。define config.key = value 和 define theme.key = value 设置 配置和主题;它们的值只求值一次,且不使用游戏变量。
gallery、track 和 achievement
gallery guqin "Resting the Qin" "cg guqin"
gallery lanterns "Lanterns on the River" "cg river_lanterns" "cg river_lanterns night"
gallery secret "After Hours" "cg secret" if persistent.finished
track theme "Ripples"
track "audio/bgm/romance.ogg" "Lotus Pond"
achievement lanterns "Brighter than Lanterns" "Guess all three lantern riddles."
achievement dull "Correct, and Very Dull" "Give the plum grove its dullest true answer." hidden
gallery id "Title" "image name" [...] [if condition] 是一个画廊条目,在其中每个图像都被看到后(或在条件成立时)解锁;其图像按顺序显示,第一个作为缩略图。track file "Title" [if condition] 是一首音乐空间曲目,被听到后解锁;文件可以是别名。achievement id "Title" ["Description"] [hidden] 声明一个成就,用 achieve id 授予;隐藏成就只有在获得后才显示其描述。只有当故事为 Gallery、Music Room 和 Achievements 定义了条目时,这些菜单项才会出现。
screen
screen riddle(question="What has roots that nobody sees?"):
dim
text "[question]" at (0.5, 0.3) size 44
button "A mountain" at (0.35, 0.6) return "mountain"
button "A river" at (0.65, 0.6) return "river" if visited("river_bank")
button image "gui/map_village.png" hover "gui/map_village_hover.png" at (420, 300) set went_village true jump village
bar wisdom max 3 at (0.5, 0.05) size (300, 18)
timer 20 return "timeout"
screen name[(parameters)]: 定义一个界面,show screen 将其叠加在游戏之上,call screen 则等待它作答。参数的用法与标签相同,可选择带默认值,由 show screen 或 call screen 的实参绑定。界面是故事数据,因此会被 lint 检查,并像其他定义一样以脚本形式写出。游戏在 C# 中以相同名称注册的界面优先于已定义的界面,因此游戏无需改动脚本就能换上手工制作的界面。
界面每帧都会根据故事的变量和它自己的变量重新计算,因此 if 会在其条件成立的那一刻出现,for 会为列表中的每个条目生成一行,进度条会跟随其变量变化。玩家所触及的内容——按钮的悬停和焦点、输入框中的输入、视口的滚动——按元素在界面中的位置保存,因此在界面重新布局后依然保留。元素可以像上面那样逐个放置在舞台上,也可以用容器布局:
screen inventory:
modal
default sorted_view = false
frame align (0.5, 0.5) padding 30 size (700, 560):
vbox spacing 14:
text "Your bag" size 44 align (0.5, 0.0)
viewport size (640, 300) spacing 8:
for item in bag:
button "[item]" size (600, 56) set chosen item close
if not bag:
text "Nothing yet."
hbox spacing 20:
button "Sort" toggle sorted_view
button "Close" close
key "escape" close
screen hud:
hbox align (1.0, 0.0) spacing 12 padding 14 background "#00000090":
text "Trust [trust]" size 28
bar trust max 10 size (200, 18)
button "Save" open save
位于界面顶部、在其元素之前或之间的行:
| 行 | 含义 |
|---|---|
dim |
使界面后面的游戏变暗。 |
modal |
显示的界面会拦截所有点击,因此在它显示期间,下面的故事无法推进。被调用的界面总是如此。 |
background "image" |
覆盖界面元素后方舞台的图像名称或文件。 |
style_prefix "name" |
界面元素 样式 的前缀:使用 "gold" 时,如果故事中有相应样式,按钮用 gold_button 绘制,其标签用 gold_button_text 绘制。 |
default name = expression |
界面自己的变量,在界面打开时设置,在界面存在期间一直有效。界面的按钮可以设置和切换它,界面的文本和条件可以读取它。 |
元素:
| 行 | 含义 |
|---|---|
text "..." [size n] [color "#hex"] [width n] |
文本,可以插入 [variable] 和界面的参数;width 是换行前文本延伸的像素宽度。 |
image "name, file or colour" [zoom z] |
一张图片。 |
button "Text" [size (w, h)] [sensitive value] actions |
使用主题按钮框的按钮,除非给出以像素为单位的尺寸,否则按其文本确定大小。使用 sensitive 时,只有在该值成立时才能按下,否则显示为暗色。 |
button image "file" [hover "file"] actions |
可按下的图片,例如地图上的某个地点,指针悬停时显示另一张图片(或将同一张图片调亮)。 |
bar value max maximum [size (w, h)] |
显示某个值相对于最大值的进度条,例如好感度计量条;未指定尺寸时为 300×20 像素。 |
input variable [length n] [allow "characters"] [width n] |
向变量中输入内容的文本框,例如输入名字。 |
space (w, h) |
空白区域,用于在容器中隔开元素。 |
timer seconds [repeat] actions |
时间到后执行一次,就像限时选项到时一样;使用 repeat 时每次到时都执行。它不绘制任何内容。 |
key "name" actions |
在界面显示期间按下某个键时执行:escape、enter、space、left、某个字母、f1 等。它不绘制任何内容。 |
容器包含其下方块中的元素,并接受 spacing n(子元素之间的像素数)、padding n(边缘与子元素之间的像素数)、background "colour or image" 和 size (w, h)(像素;不指定时容器会适应其内容):
| 容器 | 子元素的布局方式 |
|---|---|
vbox |
上下排列。 |
hbox |
左右并排。 |
grid columns |
按指定列数分行排列。 |
frame |
位于使用主题颜色的面板上,各自位于自身位置所指定之处。 |
fixed |
各自位于自身位置所指定之处。 |
viewport |
上下排列,按其尺寸裁剪,放不下时可用鼠标滚轮滚动。 |
样式。 每个元素都用一个 样式 绘制:如果用 style "name" 指定了样式,就用该样式;否则用其类型在包含它的界面或容器的前缀下的样式(容器与界面一样,接受 style_prefix "name",作用于其所含内容);否则用其类型自己的样式:text、button 和 button_text、frame、vbox、hbox、grid、fixed、viewport、bar、input 和 input_text。任何样式属性也可以写在元素本身上、位于其其他子句之后,仅作用于该元素:text "Hi" outline_width 2、vbox background "#14202ae8" corner_radius 8:。在按钮上,带 text_ 前缀的属性设置其标签的样式:button "Buy" text_color "#ffffff" return 1。容器未自行设置内边距、间距和尺寸时,会从其样式中获取。界面上的文本测量时向上取整到下一个整像素,因此能在一行内放下的标题,不会因其自身宽度的舍入而被换成两行。
流程:
| 行 | 含义 |
|---|---|
if condition:、elif condition:、else: |
第一个条件成立的分支中的元素。 |
for name in list: |
对列表中的每个条目,将其下方的元素各生成一次,条目存放在 name 中:袋子里的每件礼物一行。 |
use other_screen(arguments) |
在此处放入另一个界面的元素,并绑定其参数,例如多个界面共用的状态栏。 |
定位。 在界面本身、fixed 或 frame 中,元素的定位方式有三种:用 at (x, y),使其以该点为中心;用 align (x, y),使元素的相同比例处对齐区域的相同比例处,因此 align (1.0, 1.0) 是右下角,align (0.0, 0.0) 是左上角;或者用 pos (x, y) 配合 anchor (x, y),分别指定两者。与 动画 中一样,带小数点的数字是比例,整数是像素:(0.5, 0.3) 是靠近顶部的中间位置,(420, 300) 是横向 420 像素、向下 300 像素。不使用其中任何一种时,元素位于中间。在 vbox、hbox 或 grid 中,由容器放置其子元素,align 只说明子元素在容器横向上的位置。任何元素都可以以 if condition 结尾,这样只在条件成立时显示。
按钮、计时器或按键的动作按顺序执行:
| 动作 | 作用 |
|---|---|
return value |
回答被调用的界面;故事以 _return 读取该值。在显示的界面上,它会关闭界面。 |
jump label |
关闭界面,并在该标签处继续故事。正在进行的调用仍在进行,因此之后的 return 仍会返回。 |
set variable value |
设置一个变量:如果界面自己有同名变量,就设置它,否则设置故事的变量。 |
toggle variable |
把为真的变量变为假,为假的变为真。 |
notify "text" |
显示一条通知。 |
play "sound" |
播放一次声音。 |
open menu |
打开游戏的某个菜单:save、load、preferences、history、gallery、music_room、achievements 或 main_menu。 |
close |
关闭界面;被调用的界面回答 none。 |
show name、hide name |
按名称显示或隐藏另一个界面,不带实参。 |
run command |
运行游戏注册的命令,不带实参;其结果放入 _return。 |
值可以是带引号的字符串、数字、名称或括号中的表达式:set gold (gold - 5),在 for 中则如 set chosen item。显示的界面只响应它自己的 key 行,因此其下的故事仍可使用方向键和 Enter。每个界面,无论是内置的还是游戏自己的,都可以用键盘和 游戏手柄 操作,因为手柄的按钮就是界面已经响应的那些键。无头游玩时,按下被调用界面的按钮所用的位置,与菜单所用的位置相同。
lint 会检查界面的跳转、它显示、隐藏和使用的界面(以及 use 传递的实参个数是否正确,或界面是否使用了自身)、它运行的命令、其表达式调用的函数和读取的未设默认值的变量、它显示的图像和其容器的背景,以及 show screen 和 call screen 传给它的实参是否多于其参数。
lint 还会报告元素所指定、但故事未定义的样式,以及元素上不是样式属性或值类型错误的属性。
界面不涵盖的是 Ren'Py 的拖放和图像映射(imagemap)的热点裁剪;需要这些功能的界面,应写成 C# 类。
style
style gold_button is button:
background "#3a2a10"
hover_background "#6a4a18"
corner_radius 12
style gold_button_text is button_text:
color "#e8dcc6"
hover_color "#ffd27a"
insensitive_color "#7d7568"
style say_dialogue:
outline_width 2
outline_color "#1a0f0a"
style name [is parent]: 定义一个样式:一种具名外观,包含用于文本和容器的属性,每行一个,未设置的属性从父样式继承。带 idle_、hover_、selected_、insensitive_ 或 visited_ 前缀的属性在相应状态下生效。同名的第二个 style 块会补充第一个;与 Novella 自有样式(say_dialogue、say_window、choice_button 等)同名的样式,会细化每个游戏界面的相应部分,而不是替换它。界面用 style 和 style_prefix 指定样式,角色用 window_style 等指定,台词用 {style=name} 指定。样式 是完整的参考:沿父样式和状态的查找、每个属性、内置样式,以及 lint 检查的内容。
init python
init python:
def title_for(level):
titles = ["stranger", "friend", "confidant"]
return titles[min(int(level), len(titles) - 1)]
在选择启用 Python 的游戏中,游戏开始时运行一次的 Python;它定义的函数可以像内置函数一样,从故事中的任何表达式调用:"You are a [title_for(trust)]."。参见 Python。
translate
translate zh-Hans pro_yuelao_01:
yuelao "天下每一个魂灵,在我的簿子上都有一行。"
translate zh-Hans tea_narr_02:
pass
translate zh-Hans strings:
old "Su Qinglan"
new "苏清岚"
old "Bow and answer with a verse"
new "作揖,以诗相答"
translate language id: 翻译一句台词:其主体是一个或多个说话语句,以段落分隔符连接,并且可以把这句台词交给另一个说话者;pass 表示在该语言中删去这句台词。translate language strings: 按原文精确匹配翻译文本:菜单选项、角色名、提示、通知、画廊和成就标题、translate("...") 调用,以及引擎自己的界面用语。参见 翻译。
语句
语句位于标签内部。
对话
"Narration: a line with no speaker."
q "Qinglan speaks."
q smile "She smiles as she speaks, and keeps smiling."
q @ surprised "Surprised for this line only."
q annoyed @ blush "Annoyed from now on, blushing for this line."
"Pedlar" "A one-off speaker, named in quotes."
q "A line with its own id." id garden_q_01
extend " And a little more, appended to the line before."
说话语句由可选的说话者、可选的图像属性和文本组成。@ 之前的属性会更改说话者的图像并保持不变;它之后的属性只在这句台词显示期间有效。台词可以以 id name 结尾,为它指定一个显式的 台词 id。extend 延续上一句台词:先前的文本保留,新文本紧接其后逐字显示,位于角色的后引号之内。台词也可以以 with transition 结尾:如果角色有图像,说话者的图像会先更换为该台词的属性,接着播放转场,然后台词才出现。
show、scene 和 hide
show qinglan smile
show qinglan sad at left with dissolve
show xiaotao grin at left, hop with easeinleft
show lantern at truecenter behind qinglan zorder 2
show qinglan smile as twin at right
show fog onlayer overlay
show qinglan:
xalign 0.0
linear 2.0 xalign 1.0
scene bg teahouse with fade
scene
scene onlayer overlay
hide qinglan with dissolve
hide fog onlayer overlay
show name [at t1, t2] [behind tag, tag] [zorder n] [as tag] [onlayer layer] [with transition][:] 显示一个图像,或更换正在显示的同标签图像。at 按顺序应用变换;behind 把它放在指定标签的下面;zorder 决定它在所在图层内的顺序;as 为它指定另一个标签,使同一个图像可以显示两次;onlayer 选择 图层;该行下方的块是一段内联动画。省略属性时,show qinglan 保留正在显示的内容,只更改各子句。
scene [onlayer layer] [name [clauses]] [with transition] 清空一个图层(默认为 master),并可选择显示一个背景;其转场同时作用于两者。hide tag [onlayer layer] [with transition] 移除一个图像;标签之后允许写属性,但会被忽略。
with
show qinglan smile at right
show xiaotao grin at left
with dissolve
单独一行的 with transition 会对自玩家上次交互以来发生变化的所有内容执行转场。with none 使变化立即生效。
pause
pause
pause 1.5
pause 3.0 hard
等待一次点击,或等待一段时间;hard 无法通过点击跳过。不带时间的暂停与一句台词一样,是一个检查点。
menu
menu:
q "Which do you choose?"
"The hairpin":
$ gift = "hairpin"
"The fan" if affection >= 2:
$ gift = "fan"
"The book of poems" id market_choice_book:
$ gift = "book"
jump bookstall

菜单首先包含一句可选的台词(与选项一起显示),然后是选项:带引号的文本、可选的 id name、可选的 if condition、一个冒号和一个块。条件为假的选项会被隐藏;没有可显示选项的菜单会被跳过。做出选择后,_choice 保存其文本,_choice_index 保存其索引,该选择会记录到历史记录中。在之前的游玩中选过的选项会显示得暗一些。数字键可以选择选项。
menu set variable: 把每个已选选项的文本记录到一个列表变量中,并隐藏其中已有的选项,这就是“每个话题只问一次”的模式:
$ asked = []
while len(asked) < 3:
menu set asked:
"Ask about the lanterns":
keeper "Three riddles, from Master Su's own brush."
"Ask about the bridge":
keeper "Lit for the festival."
"Ask about the river":
keeper "Every lantern on it is a wish."
menu 后面的单词为菜单命名,具名菜单可以在故事的任何地方像标签一样被跳转或调用;lint 接受它作为目标。菜单会像故事运行到它那样显示,做出选择后,故事从那里继续,执行该菜单在其标签中后面的内容。如果有同名标签,则以标签为目标。菜单不接受实参:带实参调用菜单是一个错误,lint 会报告它。
label market:
menu gifts:
"Which gift for Qinglan?"
"The hairpin":
$ gift = "hairpin"
"Nothing yet":
jump wander
jump teahouse
label wander:
"You walk the stalls once more."
jump gifts
jump、call 和 return
jump teahouse
jump expression "ending_" + outcome
call chapter_title("Chapter One", "The Teahouse")
call expression next_scene
return
return affection * 10
jump 在某个标签或某个 具名菜单 处继续;在被调用的标签内部,跳转仍留在该调用之内,因此之后的 return 仍会返回。call 运行一个标签,在它返回后从调用之后继续,并把实参绑定到标签的参数。return value 把值放入 _return。jump expression 和 call expression 通过计算得到标签名称;计算得出的调用不接受实参。
if、elif、else 和 while
if riddles_solved == 3:
achieve lanterns
elif riddles_solved > 0:
q "Not bad."
else:
q "Not even the moon?"
while candles > 0:
"A candle goes out."
$ candles -= 1
空块是错误;在不需要做任何事时,写 pass。循环必须执行到某句台词、菜单或暂停,否则故事会在执行一百万条语句后因错误而停止。
$ 赋值
$ affection += 1
$ player_name = trim(player_name) or "Lu Wenjing"
$ inventory += "fan"
$ inventory -= "fan"
$ persistent.endings_seen += "good"
$ score //= 2
$ name = expression 进行赋值;+=、-=、*=、/=、//= 和 %= 进行复合赋值。$ expression 为了副作用而对表达式求值。在任何游戏中,$ 行都是 Novella 自己的表达式语言,而不是 Python。
python
python:
trust += 1
gifts = sorted(set(gifts + ["fan"]))
在选择启用 Python 的游戏中,一个在故事运行到它时、针对故事变量运行的 Python 块。它所设置的、故事能够容纳的值会回到故事的变量中。参见 Python。
play、queue、stop 和 voice
play music theme fadein 3.0
play music "audio/bgm/town_day.ogg" if_changed
play ambience crowd fadein 2.0 volume 0.3
play sound ["audio/sfx/door.ogg", "audio/sfx/bell.ogg"]
queue music romance
stop music fadeout 3.0
voice "voice/qinglan_014.ogg"
q "Listen."
voice sustain
q "The voice from the line before keeps playing."
play channel file [fadein n] [fadeout n] [loop|noloop] [volume v] [if_changed] 播放一个带引号的文件、一个别名,或一个按顺序播放的方括号列表。fadeout 让正在播放的内容淡出;如果同一文件已在播放,if_changed 不做任何事。queue 在当前播放的内容之后播放。stop channel [fadeout n] 停止播放。voice file 为下一句台词设置语音,voice sustain 让当前语音在下一句台词期间继续播放。设置了 config.auto_voice(例如 voice/{id}.ogg)时,如果以台词 id 命名的语音文件存在,台词就会播放它;模式中的 {character} 是说话者的语音标签或 id。
movie
movie "video/opening.mp4"
movie "video/credits.webm" noskip keepmusic
播放全屏影片。除非给出 keepmusic,否则它会停止音乐;除非给出 noskip,否则点击即可跳过。
input
input player_name "What is your name?" default "Lu Wenjing" length 16
input code "The lock has four digits." allow "0123456789" length 4
input variable "Prompt" [default "text"] [length n] [allow "characters"] 请玩家输入文字。默认值中可以插值(default "[translate('Lu Wenjing')]");空回答会保留默认值。
window 和 nvl clear
window hide
scene bg night_sky with fade
window show with dissolve
window auto
nvl clear
window show|hide|auto [with transition] 设置对话框的可见性;auto 在有对话时显示对话框,其他时候隐藏。nvl clear 开始一个新的 NVL 页面。
run
run fortune
run riddle answer, 3
run lantern("red", 3)
$ slip = _return
run name [arguments] 运行游戏用 C# 或 Divooka 实现的命令,实参为逗号分隔的表达式;其结果放入 _return。故事用到的每个命令都必须已注册,否则故事会因错误而停止(lint 也会报告)。参见 开发 和 在 Divooka 中使用 Novella。
界面
show screen hud(gold)
hide screen hud
call screen riddle("What has a face but no mouth?")
if _return == "timeout":
q "Too slow."
show screen name(arguments) 把一个界面放在游戏之上,它会一直保留(读档时也会恢复),直到 hide screen name;故事在它下面继续进行。call screen name(arguments) 显示一个界面并等待它作答,答案放入 _return;如果玩家未作答就关闭了它(按 Escape、执行 close 动作,或 C# 界面自己的 Close),_return 为 none。该界面可以是故事用 screen 定义的界面,也可以是游戏在 C# 中注册的界面,后者优先于同名的已定义界面。带 with transition 的 show screen 和 hide screen 会在舞台上播放转场。被调用的界面不属于舞台,因此 call screen ... with transition 会在该转场的时长内让界面淡入,无论它指定的是哪种转场。
notify、achieve 和 pass
notify "Added to the gallery: Lanterns on the River"
achieve end_good
pass
notify 显示一条通知,持续几秒钟;achieve 授予一个成就(仅一次)并加以宣告;pass 不做任何事。
文本标签
对话台词接受 Ren'Py 的文本标签。样式标签由对应的 {/tag} 关闭;关闭一个标签也会关闭在其内部打开的所有标签。
| 标签 | 效果 |
|---|---|
{b}、{i}、{u}、{s} |
粗体、斜体、下划线、删除线。 |
{plain} |
关闭粗体、斜体、下划线和删除线。 |
{color=#f88} |
文字颜色。 |
{outlinecolor=#000} |
文字周围的描边,作为光晕绘制在字母后面,宽度至少两个像素。 |
{style=name} |
样式 所设置的文本属性:字体、字号、颜色、字重、描边和阴影。样式未设置的属性沿用台词本身的,因此只设置颜色的样式会保留台词的字号和字体。不支持 Ren'Py 的 {=name} 写法。 |
{size=40}、{size=+8}、{size=-4}、{size=*1.5} |
绝对字号,或相对于周围文本的字号。 |
{font=Family} |
字体族。 |
{alpha=0.5} |
不透明度。 |
{a=target} |
链接,在台词显示完毕后可点击:jump:label、label:label 或单独的标签名会跳转到该处;call:label 运行该标签,然后再次显示同一句台词;http 或 https 地址会在浏览器中打开。 |
{rb}漢字{/rb}{rt}かんじ{/rt}、{ruby=pīnyīn}字{/ruby} |
显示在基础文字上方的注音文字(振假名、拼音)。 |
{w}、{w=0.5} |
在继续逐字显示之前,等待一次点击或一段时间。 |
{p}、{p=1.0} |
换行,然后等待。 |
{nw} |
不在台词末尾等待:逐字显示完毕后立即推进。 |
{fast} |
立即显示此处之前的所有内容。 |
{cps=20}、{cps=*2} ... {/cps} |
以每秒字符数表示的显示速度,或玩家设置速度的倍数。 |
{image=gui/heart.png} |
与文本同行的内联图像。 |
{space=20} |
水平空白。 |
{#comment} |
被忽略:写给译者的备注。 |
[expression] 插入一个值:"Welcome, [player_name]."、"[len(inventory)] items"。{{ 和 [[ 分别表示字面的花括号和方括号。Novella 不认识的标签会作为文本显示,但 {vspace}、{k} 和 {clear} 除外,它们会被接受并忽略。
表达式
表达式出现在 $ 行、条件、default 和 define、实参、[interpolation] 以及 if 子句中。它们是一种小巧、安全的语言,在作者习惯使用的地方采用 Python 的语法,同时也接受 C 风格的写法。
值包括数字、字符串(单引号或双引号)、true 和 false(也可写作 True、False)、none(也可写作 None、null)以及列表([1, "fan", true])。变量就是名称;persistent.name 读取持久存储。列表和字符串从零开始索引,负索引从末尾倒数:inventory[0]、name[-1]。不存在的索引为 none。
运算符,按优先级从低到高排列:
| 运算符 | 说明 |
|---|---|
x if c else y、c ? x : y |
条件表达式。 |
or、\|\| |
返回起决定作用的操作数,因此 name or "stranger" 可用作默认值。 |
and、&& |
返回起决定作用的操作数。 |
not、! |
|
==、!=、<、<=、>、>=、in、not in |
比较运算可以链式书写:1 < x <= 5。in 测试列表成员关系或子字符串。 |
+、- |
+ 连接字符串、向列表追加元素(inventory + "fan")或连接列表;- 从列表中移除所有匹配项。 |
*、/、//、% |
/ 是精确除法,// 是向下取整除法;"ab" * 3 重复字符串。 |
一元 -、+ |
|
** |
幂运算。 |
函数
| 函数 | 结果 |
|---|---|
len(x) |
列表或字符串的长度。 |
min(...)、max(...)、sum(...) |
对各实参求值,或对唯一的列表实参中的各项求值。 |
abs(x)、round(x[, digits])、floor(x)、ceil(x)、clamp(x, low, high) |
算术。 |
int(x)、number(x)、str(x)、bool(x) |
类型转换;int 截断取整。 |
upper(s)、lower(s)、trim(s)、capitalize(s) |
文本。 |
join(list[, separator])、split(text[, separator]) |
连接默认使用 ", ",拆分默认使用空格。 |
contains(x, item)、has(x, item) |
成员关系,与 in 相同。 |
count(list, item)、append(list, item)、remove(list, item)、unique(list)、sorted(list) |
列表辅助函数;每个都返回一个新列表。 |
range(n)、range(a, b) |
从 0(或 a)开始、直到但不包括终点的整数。 |
random()、random(n)、random(a, b) |
0..1 之间的数、0..n-1 之间的整数,或 a 到 b(含两端)之间的整数。 |
chance(p) |
以概率 p 为真。 |
choice(list) 或 choice(a, b, ...) |
一个随机项。 |
defined(name)、get(name[, default]) |
某个变量(以字符串命名)是否有值;其值或默认值。 |
visits(label)、visited(label) |
本次游玩中某个标签被进入的次数,以及是否被进入过。 |
seen_label(label) |
某个标签是否在任何一次游玩中被到达过。 |
seen_image(name)、heard(file) |
某个图像是否在任何一次游玩中被显示过,或某段音频是否被播放过。 |
achieved(id) |
某个成就是否已被授予。 |
showing(tag) |
带该标签的图像是否在舞台上。 |
language() |
当前语言;如果是原始语言,则为空字符串。 |
translate(text) |
经当前语言的字符串翻译处理后的文本。 |
随机抽取来自游戏自己的随机数生成器,它是存档和回滚状态的一部分,因此回滚后再次选择会抽到相同的结果。游戏可以从 C# 用 RegisterFunction、从 Divooka 图用 Register Expression Function,或从脚本用 init python: 添加函数。
Python
init python:
def title_for(level):
titles = ["stranger", "friend", "confidant"]
return titles[min(int(level), len(titles) - 1)]
label start:
"You are a [title_for(trust)]."
python:
trust += 1
gifts = sorted(set(gifts + ["fan"]))
对于 Novella 表达式无法覆盖的内容——含有循环的辅助函数、对列表的计算——游戏可以选择启用 Python。Novella 自己的表达式仍是故事的语言,因为它们可以被验证、转换为节点并打印回脚本,而 Python 做不到这些;Python 用于其余的部分。
init python:位于脚本的顶层,在游戏开始时运行一次。它定义的函数可以像内置函数一样,从任何表达式中调用,包括条件、$行和[interpolation]。python:位于标签的语句之间,在故事运行到它时执行。运行之前,它会获得故事的变量;运行之后,它所设置的每个故事能够容纳的变量——数字、文本、布尔值、None,或由这些组成的列表——都会回到故事中,与其余变量一起存档和回滚。- 故事无法容纳的内容同样会被保留。 字典、集合、由
init python:块定义的类的对象,以及 init 块设置的值,在任何代码块更改它们时,都会一起被 pickle 到一个隐藏的故事变量_python中,因此它们会与其余内容一起存档、读档和回滚:读档或回滚之后,下一个代码块或函数调用看到的 Python 状态,与那个时间点完全一致。Ren'Py 也是这样保留它的 store 的。 - 函数和类应放在
init python:中。 函数、类和模块是代码而不是状态,因此不会被 pickle;init 块在每次游戏启动时都会重新定义它们,而正是在那里定义的类,才使存档中该类的对象得以恢复。pickle 无法写出的值(如生成器或已打开的文件)不会被保留。 - 代码完全按原样保留,包括注释和空行,Python 错误会指出它来自哪个脚本的哪一行。
- 标准库可用。 Python 通过 Parcel NExT 的
Divooka.Scripting.Python在 IronPython 3.4.2 上运行,标准库位于游戏旁的PythonLib/中,因此import random、os、json、pickle、datetime、collections等都与在任何 Python 中一样可用。在故事本身中需要随机性时,仍应在表达式中使用故事自己的random、chance和choice:它们的抽取来自游戏状态,因此回滚会重放相同的结果,而 Python 的random会重新抽取。
Python 来自 Novella.Python。从不编写 Python 的游戏无需携带它;当故事含有 Python 时,引擎会在游戏旁按名称找到它。Divooka 包和 NovellaPlayer 都携带它,C# 游戏则需引用它(参见 安装)。lint 会读取 init python: 块定义的函数和 python: 块赋值的变量,因此不会把它们报告为未知;如果故事含有 Python 而游戏没有 Python,lint 也会报告。
动画
变换的主体、show 或 scene 下的块,或者 image name: 块,都是 Ren'Py ATL 风格的时间线。每一行是以下之一:
- 属性设置,立即生效。多个设置可以写在同一行:
xalign 0.5 yalign 1.0 alpha 0.0。 - 插值:一个缓动函数(warper)、一个时长和若干属性,如
ease 1.0 xalign 1.0 alpha 1.0,从当前值动画过渡到这些值。属性也可以写在缓动函数行下方的块中。 - 暂停:
pause 0.5,或单独一个数字。 - 图片行:用引号括起的图像名称、文件或颜色,可选择带
with转场,如"eileen blink" with dissolve。从此处起,图像显示该图片,在转场的时长内交叉淡化,但不会阻塞时间线。图片行之间穿插暂停就构成逐帧动画。 repeat或repeat n:重新开始外层块,永远重复,或直到它总共运行了n次。block::嵌套的时间线,使repeat可以只循环动画的一部分。parallel::连续的多个parallel块同时运行,各自为自己的属性制作动画。choice [weight]::连续的多个choice块构成一次选择,每次时间线到达这里时,按权重(未给出时为 1)随机选取其中一个运行。time seconds:其后的内容在块开始后的指定秒数时开始,并中断仍在运行的任何内容。on show:、on replace:、on hide:,或同时指定多个,on show, replace::在图像发生相应事件时播放的时间线。变换处理hide的图像会一直留在舞台上,直到处理程序结束,因此它可以淡出或滑走。contains::在本图像内部绘制的一张图片,带有自己的时间线,放置在本图像的范围内;块的第一行是该图片。单行的contains "sparkle"是简写形式。function name:把图像交给游戏在 C# 中用RegisterTransformFunction注册的函数,持续时间由该函数决定(参见 开发)。- 另一个变换的名称,如果它接受参数则带上实参,就地引入:
left、bob(30)。
transform drift:
alpha 0.0
ease 2.5 alpha 1.0
block:
ease 3.0 yoffset -14
ease 3.0 yoffset 0
repeat
transform hop:
yoffset 0
easeout 0.12 yoffset -28
easein 0.14 yoffset 0
repeat 2
transform breathe_and_sway:
parallel:
ease 2.0 zoom 1.02
ease 2.0 zoom 1.0
repeat
parallel:
ease 3.0 xoffset 6
ease 3.0 xoffset -6
repeat
transform entrance:
on show, replace:
alpha 0.0
linear 0.5 alpha 1.0
on hide:
linear 0.5 alpha 0.0
contains:
"sparkle"
linear 2.0 alpha 0.0
image lantern:
"lantern bright"
choice 3:
pause 0.8
choice:
pause 0.2
"lantern dim" with dissolve
pause 0.15
repeat
时间线是时间的纯函数:同一图像在同一时刻显示相同的内容,随机选择也不例外,因为每个显示的图像都带有自己的种子,存档会保存它。因此,读档或回滚会精确显示当时所显示的内容。
属性:xpos、ypos、pos、xanchor、yanchor、anchor、xalign、yalign、align、xcenter、ycenter、center、xycenter、xoffset、yoffset、offset、zoom、xzoom、yzoom、alpha、rotate、blur、saturation、brightness、tint、xsize、ysize、size。成对属性接受两个数字或一个括号中的数对:pos 100 200、align (0.5, 1.0)。tint 接受一个颜色:tint "#ffd0d0"。负的 xzoom 或 yzoom 表示镜像。
位置:带小数点的数字是比例(对位置和对齐而言是屏幕的比例,对锚点而言是图像的比例),整数是像素:xpos 0.25 是横向四分之一处,xpos 320 是 320 像素。内置位置:left、right、center、midleft、midright、truecenter、top、topleft、topright、offscreenleft、offscreenright、fullscreen、default 和 reset。
缓动函数:linear、ease、easein、easeout、pause(保持不变,到结束时跳变),以及 ease_quad、ease_cubic、ease_quart、ease_quint、ease_expo、ease_circ、ease_back、ease_elastic 和 ease_bounce 系列,每个系列也有 easein_... 和 easeout_... 形式。
值:时长或值可以是数字、参数名,或括号中的表达式:ease (speed * 2) yoffset (-height)。时间线表达式可以看到变换的参数和内置函数,但看不到游戏的变量。
不支持:ATL 语句 event,解析器会将其报告为错误(图像自身的事件用 on show、on replace 和 on hide 处理);用于任何其他事件的 on;以及 Ren'Py 的极坐标、裁剪、平铺和着色器属性。
转场
转场写在 with 之后:show qinglan with dissolve、scene bg night with fade、with pixellate。内置转场:
| 名称 | 效果 | 默认值 | 实参 |
|---|---|---|---|
dissolve、crossfade |
交叉淡化。 | 0.5 秒 | (seconds[, warper]) |
fade |
淡出到某种颜色,保持,再淡入。 | 淡出 0.5 秒,淡入 0.5 秒,黑色 | (out[, hold[, in[, "#colour"]]]);只有一个实参时,淡入时长等于淡出时长 |
flash |
闪白后恢复。 | 淡出 0.1 秒,淡入 0.4 秒 | 同 fade |
pixellate |
先像素化淡出,再像素化淡入。 | 1.0 秒,5 步 | (seconds[, steps]) |
move、ease |
留在场上的图像滑行到新位置;其他图像溶解。 | 0.5 秒,缓动 | (seconds[, warper]) |
moveinleft、moveinright、moveintop、moveinbottom |
进入的图像从该侧滑入。 | 0.5 秒 | (seconds[, warper]) |
moveoutleft、moveoutright、moveouttop、moveoutbottom |
离开的图像向该侧滑出。 | 0.5 秒 | (seconds[, warper]) |
easeinleft ... easeoutbottom |
与 movein/moveout 相同,但带缓动。 |
0.5 秒 | (seconds[, warper]) |
slideleft、slideright、slideup、slidedown |
新画面滑入并覆盖旧画面。 | 1.0 秒 | (seconds[, warper]) |
slideawayleft ... slideawaydown |
旧画面滑走。 | 1.0 秒 | (seconds[, warper]) |
pushleft ... pushdown |
新画面把旧画面推出去。 | 1.0 秒 | (seconds[, warper]) |
wipeleft ... wipedown |
一条硬边扫过画面。 | 1.0 秒 | (seconds[, warper]) |
irisin、irisout |
圆形展开或收拢。 | 1.0 秒 | (seconds[, warper]) |
zoomin、zoomout |
新画面缩放。 | 0.5 秒 | (seconds[, warper]) |
vpunch、hpunch |
震动屏幕。 | 0.4 秒,10 像素 | (seconds[, pixels]) |
blinds、squares |
百叶窗,或方格网。 | 1.0 秒,8 片百叶或 12 个方格 | (seconds[, count]) |
imagedissolve |
像素按灰度遮罩的亮度顺序变化。 | 1.0 秒,渐变范围 32 | ("mask.png"[, seconds[, ramp[, reverse]]]) |
none |
立即完成。 |
具名转场(transition slow = dissolve(2.0))可以带或不带更多实参使用。
翻译
用原始语言编写游戏(config.original_language 在偏好设置中为它命名),然后为每种语言添加一个脚本。偏好设置中的语言选择器会列出故事所翻译的每种语言。

# zh_hans.novella
translate zh-Hans strings:
old "Su Qinglan"
new "苏清岚"
old "Chapter One"
new "第一章"
translate zh-Hans tea_wang_03:
wang "我已经替你相中了一位好姑娘!"
translate zh-Hans tea_narr_07:
"茶馆里一下子安静了。"
"连掌柜都停下了算盘。"
- 台词按 id 翻译。为每句台词指定显式 id(
id tea_wang_03),可以在原文被编辑后仍让译文保持关联;没有显式 id 时,id 是标签名加上说话者和文本的哈希值,文本一变它就会改变。一个块中的多个说话语句以段落分隔符连接;译文可以指定不同的说话者;pass表示在该语言中删去这句台词。 - 字符串按原文精确匹配翻译:菜单选项、角色名、提示、通知、画廊、音乐空间和成就标题,以及通过
translate("...")传入的任何内容。字符串表还可以覆盖引擎自己的界面用语,例如old "Start"/new "开篇"。 - 引擎界面(菜单、偏好设置、确认、帮助界面)已翻译成简体中文、繁体中文和日文;参见 主题设置。
- 字体:在主题的字体列表中,把 CJK 字体放在拉丁字体之后,使两种文字都能正常渲染,具体见 主题设置。
lint 会报告每种语言翻译了多少句台词。config.default_language 设置全新安装时的初始语言。