简体中文
English 简体中文 繁體中文
Methodox 首页

样式

样式是一种命名的外观:一组用于文本和容器的属性,例如字体、颜色、背景和内边距,并带有一个父级,凡是它自身未设置的属性都从父级继承。Novella 游戏的每个部分都用某个样式绘制:对话窗口及其文本、名字标签、选项、快捷菜单、存档位,以及游戏自有界面上的按钮和框架。故事只需定义一个同名样式,就能为其中任何一个重新设定样式;也可以为自己的界面、角色和台词中的个别词语创建专属外观。

样式的工作方式与 Ren'Py 中相同,因此 Ren'Py 作者会觉得很熟悉;两者的差异列在本页末尾。主题设置仍然是设置游戏外观最快捷的方式,因为每个主题设置都是某个内置样式中某个属性的简写;样式则用于主题键无法表达的一切。

style gold_button is button:
    background "gui/gold_idle.png"
    hover_background "gui/gold_hover.png"
    borders (24, 24, 24, 24)
    padding (32, 12)

style gold_button_text is button_text:
    font "Ma Shan Zheng"
    size 40
    color "#e8dcc6"
    hover_color "#ffd27a"
    insensitive_color "#7d7568"

style say_dialogue:
    outline_width 2
    outline_color "#1a0f0a"

style ghost_window is say_window:
    background "#10202cd0"

style shout:
    color "#ff6050"
    bold true

character ghost "Ghost" window_style "ghost_window" what_size 40

定义样式

style name [is parent]: 位于脚本的顶层,其下的块中每行一个属性。多个属性可以写在同一行(padding (32, 12) text_align center)。

  • 父级是当本样式未设置某个属性时,该属性的来源样式。style gold_button is button: 从 button 已有的一切出发,只修改它列出的内容。没有父级的样式继承自 default,即所有样式的根,它保存界面的字体、字号和颜色。
  • 第二次定义是对第一次的补充。之后在同一文件或另一个文件中再次写 style title:,会为同一个样式设置更多属性;两次都设置了同一属性时,以后者为准。带 is 的后续定义会更改父级;不带的则保留原父级。
  • 与内置样式同名的样式会重新设定游戏中对应部分的样式。style say_dialogue: 并不是一个新样式,而是叠加在 Novella 自身 say_dialogue 之上的更多属性,而每一行对话都是用它绘制的(参见内置样式)。

值的写法与脚本中一致:颜色写作 "#rrggbb" 或 "#rrggbbaa"(也可写作 "#rgb"),还有数字、true 和 false、用于字体和文件的带引号文本、center、bold 和 none 等裸词,以及写在括号中的边距。尺寸以 1080 像素高的游戏为基准,以像素为单位,并像主题中的尺寸一样随游戏缩放。边距写作 (left, top, right, bottom);两个数字分别表示左右和上下,一个数字表示四边相同。

样式是定义而不是游戏状态:它们不会被存档,因此从旧存档加载的游戏会使用脚本当前定义的样式绘制。开启开发者模式时,按 Shift+R 会重新加载脚本,并立即应用已更改的样式。

状态

元素处于一个或多个状态,可以通过给属性名加前缀来为某个状态指定该属性:

前缀 元素处于
idle_ 既不在指针下也不是不可用状态:静止。
hover_ 在指针下,或拥有键盘或手柄焦点。
selected_ 被选中:菜单的当前标签页、处于开启状态的选项、已获得的成就。
insensitive_ 不可按下,例如 sensitive 条件为 false 的按钮。
visited_ 之前选过:在先前的游玩中选择过的菜单选项。
selected_idle_、selected_hover_、selected_insensitive_ 被选中,同时处于静止、在指针下或不可用状态:selected_hover_color 是指针下的当前标签页的颜色,且仅用于这种情况。

hover_color "#ffd27a" 为指针下的按钮文本着色,hover_background "gui/gold_hover.png" 则替换其图片。任何属性都可以加任何前缀。hover_sound 并不是 hover 状态下的 sound,而是一个独立的属性:指针移到按钮上时播放的声音。

如何查找属性

查找属性时,先在样式本身中查找,然后依次在其父级中查找。在每个样式中:

  1. 与元素状态相符的带状态前缀的属性优先,最具体的在前,排序方式与 Ren'Py 相同:被选中元素的复合前缀(selected_hover_),然后是 selected_,然后是 hover_ 或 insensitive_,然后是 visited_,最后是 idle_;
  2. 否则使用不带前缀的属性;
  3. 只有当样式两者都没有时,才会询问其父级。

因此,设置了某个不带前缀属性的样式会覆盖该属性的所有状态,经由它无法到达父级的状态变体:

style base_text is text:
    color "#111111"
    hover_color "#222222"
    selected_color "#333333"

style plain_text is base_text:
    color "#444444"

在指针下时,base_text 为 #222222;被选中且在指针下时为 #333333,因为 selected_ 先于 hover_ 尝试;selected_hover_color 会在这一种情况下胜过两者。plain_text 在所有状态下都是 #444444:它自己设置了 color,因此查找在到达其父级的 hover_color 之前就停止了。它没有在任何地方设置 text_align,因此该属性来自更上层的 text。这就是 Ren'Py 的规则,也正是设置颜色的样式所预期的行为。

属性

分组 属性 值
文本 font 字体族,在一个字符串中以逗号分隔;由第一个包含该字符的字体绘制它,与主题的字体列表相同。
size 文本的字号。
color 文本的颜色。
bold true 或 false。
weight normal、medium、semibold 或 bold。
line_spacing 行高,以文本字号的倍数表示。
text_align left、center 或 right。
outline_width、outline_color 文本周围的描边,绘制在字母后面。
shadow、shadow_color、shadow_offset 柔和阴影、阴影颜色及其偏移距离(未设置时随文本字号而定)。
ruby_scale 注音文本(ruby)相对于其所注释文本的大小。
容器 background 容器的绘制底面:一种颜色或 solid("#203040c0");一个图像名称、一个文件或一个图像函数,例如 tint("gui/panel.png", "#a0ffa0"),按 borders 以九宫格方式拉伸;一个 frame("gui/panel.png", 24, 24),按其自身的边框拉伸;或 none。
borders 背景图像的九宫格边角,(left, top, right, bottom),以图像自身的像素计:边角保持不变,边和中间部分拉伸(参见九宫格图像)。
border_color、border_width 以颜色绘制的容器的轮廓线及其宽度。
corner_radius 以颜色绘制的容器的圆角。
accent_color 小标记:选中标签页下方的横条、滑块的填充、文本框的光标、点击继续箭头。
image 元素绘制的图片,例如点击继续标记(ctc)。
布局 padding 容器的边缘与其所含内容之间的空间。
margin 容器周围的空间,即容器与屏幕边缘之间的空间。
xsize、ysize 容器的宽度和高度。
spacing 容器中依次排列的内容之间的空间。
xoffset、yoffset 相对于容器原本位置向右和向下的偏移。
声音 hover_sound 指针移到按钮上时播放。
activate_sound 按下按钮时播放。

背景或图像可以是任何以函数形式书写、不带引号的图像源,就像 Ren'Py 书写 displayable 那样:

style tab_button is button:
    background frame("gui/tab.png", 24, 24)
    selected_hover_background tint("gui/tab.png", "#ffe0a0")

style tooltip:
    background Solid("#101820e0")
    corner_radius 6

frame(picture, left, top[, right, bottom]) 即 Ren'Py 的 Frame:图片按其自身的边框拉伸,给出两个数字时分别表示横向和纵向的边框。其他任何函数(tint、zoom、crop、composite 等)都作为图片绘制,并按样式的 borders 拉伸。solid(colour) 是一种颜色,因此 corner_radius 和 border_color 仍然适用于它。Frame 和 Solid 可以按 Ren'Py 的方式拼写。背景中的图片按其自身大小绘制,不会先适配舞台尺寸,因此边框始终以图片自身的像素计。

并非每个属性对每个元素都有意义:text 没有背景,vbox 没有字体。元素不使用的属性只是不会被读取;而根本不是属性的名称会由验证器报告。

描边绘制在字母后面,其全部宽度都在字母之外,形成光晕:带深色描边的细浅色文字仍保持自身的颜色和形状。主题的 text_outline_width 和 {outlinecolor} 文本标签也是如此,后者绘制至少两个像素的描边。

内置样式

Novella 自己绘制的一切都使用其自身的某个样式绘制,而这些样式是由主题设置构建的。因此,define theme.text_color = "#fff4d0" 与

style say_dialogue:
    color "#fff4d0"

表达的是同一件事。主题键是某个内置样式中某个属性的简写;故事中同名的样式会叠加在主题构建的样式之上,因此它是细化而非替换。带有新 background 的 style say_window: 会保留主题中窗口的高度、外边距和内边距,带有描边的 style say_dialogue: 会保留对话的字体、字号和颜色。未定义任何样式的游戏看起来与样式出现之前完全一样:在这一改动前后,Novella 对 25 个内置界面的逐帧巡检以及 The Red Thread 的完整游玩绘制出的像素完全相同。

位置由界面决定;外观由样式决定。用于放置某物的主题键,例如 choice_center_y 或 main_menu_xalign,背后没有对应的样式。

位置 样式
对话窗口 say_window(窗口:背景、margin、ysize、padding)、say_dialogue(台词)、say_label(说话者的名字)、namebox(名字标签:背景、padding、xoffset、yoffset)、side_image(xsize)、ctc(点击继续标记:image、accent_color)、centered_text、nvl_window、nvl_dialogue、nvl_label。
游戏中 choice_button 和 choice_button_text(xsize、ysize、选项之间的 spacing)、quick_button 和 quick_button_text、tooltip 和 tooltip_text、skip_indicator 和 skip_indicator_text、input_frame 和 input_prompt、notify_frame、notify_title 和 notify_text。
菜单 menu_button 和 menu_button_text、option_button 和 option_button_text、page_button 和 page_button_text、main_menu、main_menu_title、main_menu_version、game_menu、game_menu_navigation、game_menu_title、save_slot、save_slot_text、save_slot_note、save_slot_empty、save_slot_label、pref_label、slider、gallery_frame、gallery_locked、gallery_title、history_dialogue、history_choice、history_label、history_empty、achievement_frame、achievement_title、achievement_text(已获得的成就处于选中状态)、about_title、about_text、help_key、help_action、confirm_frame、confirm_text、message_frame、message_title、message_text。
游戏自有界面 default(所有样式的根)、text、button 和 button_text、frame、vbox、hbox、grid、fixed、viewport、bar、input 和 input_text。

一些内置样式继承自其他样式:centered_text 和 nvl_dialogue 继承自 say_dialogue,nvl_label 继承自 say_label,history_dialogue 继承自 say_dialogue,quick_button_text、menu_button_text、option_button_text 和 page_button_text 继承自 button_text。因此,为 say_dialogue 设置的 outline_width 也会为 NVL 页面和历史记录加上描边,为 button_text 设置的 hover_color 会作用于每个菜单以及游戏自有界面上的按钮。在 Divooka 中,Built-in Style Names 节点会列出所有内置样式。

style say_window:
    background "#10141ce8"
    border_color "#7fb3c8"
    corner_radius 4

style say_label:
    weight bold

style choice_button:
    hover_background "#2a4a5ef0"
    hover_border_color "#ffd98a"

style choice_button_text:
    visited_color "#8a8478"

在界面上

界面中的每个元素都用一个样式绘制,按以下顺序选择:

  1. 元素用 style "name" 指定的样式;对于按钮的标签,若故事中存在 name_text,则使用它;
  2. 否则,若故事中存在该样式,则使用其容器的前缀下该元素种类的样式:在带有 style_prefix "gold" 的界面或容器中,按钮用 gold_button 绘制,其标签用 gold_button_text 绘制,text 用 gold_text 绘制,frame 用 gold_frame 绘制;
  3. 否则使用该元素种类自身的样式:text、button(其标签用 button_text)、frame、vbox、hbox、grid、fixed、viewport、bar、input(配合 input_text)。

style_prefix 写在界面顶部,与 dim 和 modal 并列;也可以写在容器上,作用于该容器所含的内容。任何样式属性也可以直接写在元素上,放在其他子句之后,并仅针对该元素叠加在其样式之上。在按钮上,带 text_ 前缀的属性用于设置其标签的样式:text_color "#ffffff"、text_size 28。容器在自身未设置内边距、间距和尺寸时,会从其样式中获取这些值。

style gold_button is button:
    background "#3a2a10"
    hover_background "#6a4a18"
    border_color "#c8a05080"
    hover_border_color "#ffd27a"
    corner_radius 12

style gold_button_text is button_text:
    color "#e8dcc6"
    hover_color "#ffd27a"

style card is frame:
    background "#14202ae8"
    padding (40, 30)

screen stall:
    style_prefix "gold"
    vbox style "card" align (0.5, 0.5) spacing 16:
        text "The night market" size 44 outline_width 2
        button "Buy a lantern" size (420, 64) return "lantern"
        button "Buy a fan" size (420, 64) text_color "#ffffff" return "fan"
        button "Leave" size (420, 64) close

vbox 用 card 绘制,按钮因界面的前缀而用 gold_button 绘制;第二个按钮的标签无论处于何种状态都是白色,因为直接写在元素上的属性会叠加在其样式的所有状态之上。

指针位于第二个按钮上的 stall 界面:gold_button 的 hover 背景和边框,以及该按钮自身的白色标签

角色

角色可以为其台词的每个部分设置专属外观。这些部分是 window(对话窗口)、namebox(名字标签)、who(名字)和 what(台词)。在 character 行上:

  • window_style、namebox_style、who_style 和 what_style 为该部分指定一个样式,在该角色的台词中分别代替 say_window、namebox、say_label 和 say_dialogue。为此创建的样式通常继承自对应的内置样式,因此只修改它列出的内容。
  • 部分名称与任何样式属性连写,即可仅为该角色在该部分上设置这一属性,叠加在该部分的样式之上:what_size 40、what_outline_width 2、window_background "#200000"、who_color "#ffff00"、namebox_corner_radius 0。
style ghost_window is say_window:
    background "#10202cd0"
    border_color "#a8d8ff60"

style ghost_name is say_label:
    color "#a8d8ff"

character ghost "Ghost" window_style "ghost_window" who_style "ghost_name" what_size 40 what_outline_width 2
character living "Living"

幽灵在自己的窗口中说话,名字使用自己的颜色,台词更大并带有描边;living 则保留 Novella 的默认窗口。

幽灵的台词显示在其专属的蓝色边框窗口中,名字为浅蓝色,并有一段使用 shout 样式的片段 较早的角色选项仍然有效,并且在其有所设置时仍然优先:color 为名字着色,除非 who 样式设置了自己的颜色;size、font 和 text_color(也可拼写为 what_color)会覆盖样式,设置台词的字号、字体和颜色。

台词片段

{style=name}...{/style} 为台词的一部分设置样式:

style shout:
    color "#ff6050"
    bold true

label start:
    ghost "Boo. {style=shout}BOO.{/style}"

片段只采用其样式所设置的内容:当样式或除 default 以外的某个父级设置了 font、size、color、bold 或 weight、描边和阴影时,采用这些设置。只设置颜色的样式会保留周围台词的字号和字体,因此 shout 在幽灵 40 像素的台词和旁白 34 像素的台词中同样适用。片段内部或外围的其他文本标签会叠加在它之上,因此 {style=shout}{size=+10}BOO{/size}{/style} 会更大。

在故事构建器中

故事构建器写出的样式与脚本中的相同。StoryBuilder.Style(name, s => ...) 使用 StyleBuilder 定义一个样式:

story.Style("gold_button", s => s
    .Is("button")
    .Background("#3a2a10")
    .When(StyleState.Hover, hover => hover.Background("#6a4a18").BorderColor("#ffd27a"))
    .BorderColor("#c8a05080")
    .CornerRadius(12));
story.Style("gold_button_text", s => s
    .Is("button_text")
    .Color("#e8dcc6")
    .When(StyleState.Hover, hover => hover.Color("#ffd27a")));
story.Style("say_dialogue", s => s.Outline(2, "#1a0f0a"));
story.Style("shout", s => s.Color("#ff6050").Bold());
StyleBuilder 设置
Is(parent) 父级。
Set(property, value) 按脚本名称设置任意属性:Set("line_spacing", 1.4)、Set("padding", Value.FromList([32, 12]))。带状态前缀的名称(hover_color)按原样采用。
When(state, s => ...) 某一状态的属性:When(StyleState.Hover, h => h.Color("#ffd27a")) 设置 hover_color。状态有 StyleState.Idle、Hover、Selected、Insensitive 和 Visited。
WhenSelected(state, s => ...) 被选中元素在另一状态下的属性:WhenSelected(StyleState.Hover, h => h.Color("#ffffff")) 设置 selected_hover_color。
Font(families)、Size(size)、Color(color)、Bold(bold)、TextAlign(alignment)、Outline(width, color)、Shadow(shadow, color) 文本属性。
Background(colorOrImage) 或 Background(imageSource)(例如 ImageSources.Frame("gui/tab.png", 24, 24))、Borders(left, top, right, bottom)、BorderColor(color)、CornerRadius(radius)、Padding(left, top, right, bottom)、BoxSize(width, height)、Spacing(spacing) 容器属性。

属性名称在设置时即会检查,因此拼错的名称(Set("colour", ...))会立即抛出异常,而不是什么都不绘制。

角色的各部分在角色定义之后通过 StoryBuilder.CharacterStyle(id, part, style, properties) 设置:

story.Character("ghost", "Ghost")
    .CharacterStyle("ghost", "window", "ghost_window")
    .CharacterStyle("ghost", "who", properties: who => who.Color("#a8d8ff"))
    .CharacterStyle("ghost", "what", properties: what => what.Size(40).Outline(2, "#000000"));

在 ScreenBuilder 上,StylePrefix(prefix) 即界面的 style_prefix;Style(name, prefix) 为最后添加的元素指定样式,并为容器指定作用于其所含内容的前缀;StyleProperty(property, value) 直接在最后添加的元素上设置属性,text_ 开头的名称用于设置按钮标签的样式:

story.Screen("stall", s => s
    .StylePrefix("gold")
    .VBox(v => v
        .Text("The night market", size: 44)
        .StyleProperty("outline_width", 2)
        .Button("Buy a lantern", ScreenActions.Return("lantern"))
        .Button("Buy a fan", ScreenActions.Return("fan"))
        .StyleProperty("text_color", "#ffffff")
        .Button("Leave", ScreenActions.Hide()), spacing: 16)
    .Align(0.5, 0.5)
    .Style("card"));

ScriptWriter.WriteStyle 将样式输出为其脚本块;用构建器编写的故事输出后再读回,得到的是相同的脚本。

在 Divooka 中

Novella 工具箱的 Styles 类别每次用一个节点逐步构建样式,就像其他函数式节点构建故事一样:

Start Style(gold_button,父级 button)→ Set Style Property(background,"#3a2a10")→ Set Style Property(background,"#6a4a18",状态 hover)→ Set Style Property(corner_radius,12)→ Add Style 添加到故事中

值的输入方式与脚本中的写法相同:颜色写作带引号的 "#ffd27a",尺寸写作 40,边距写作 (24, 12),还有 true、center。状态可以是复合状态,例如 selected_hover。Set Style Background 则从 Images 节点获取背景:Image File、Filtered Image、Composite Image,或 Frame Image(按图片自身的边框拉伸图片)。Style To Script 以脚本形式显示样式,Built-in Style Names 列出故事可以重新设定样式的内置样式。在界面上,Set Screen Style Prefix、Style Last Element(指定样式,对于容器还可指定前缀)和 Set Last Element Style Property 的作用分别等同于脚本中的 style_prefix、style 以及直接写在元素上的属性。在角色上,Character Part Style 为某个部分指定样式,Set Character Style Property 为某个部分设置属性,两者都位于 Characters 类别中。每个节点都会在设置时检查属性名称。

验证器报告的内容

lint(NovellaPlayer lint、控制台的 lint、Check Story 和 Check Game)会读取每个样式以及每个引用样式名称的位置:

  • 不是样式(既非内置样式也非故事自有样式)的父级;
  • 父级形成循环(loop_a is loop_b、loop_b is loop_a),作为错误报告;
  • 样式、角色的部分或界面的元素上 Novella 不认识的属性;
  • 类型错误的值:未写作 "#rrggbb" 或 "#rrggbbaa" 的颜色、本应是数字却写了单词、不是一个、两个或四个数字的边距、left、center 或 right 以外的 text_align、四种取值以外的 weight、不是常量的值;
  • 既不是颜色、none、故事定义的图像,也不是游戏文件的背景或图像;引用了游戏中不存在的文件的图像函数;以及不在游戏文件中的声音(根本无法读取的图像函数,例如拼错的 fram(...),会在其书写位置报告为错误);
  • 被引用但未定义的样式:由界面的 style、角色的 window_style 等,或由 {style=name} 片段引用。

最后一项最为重要,因为拼错的样式名称在其他情况下不会有任何提示:元素只是以默认外观绘制。

与 Ren'Py 的差异

Novella 的样式遵循 Ren'Py 的模型,包括父级、状态前缀和 style_prefix,并新增了 visited_ 状态,用于在先前游玩中选过的选项。以下内容不受支持:

  • Frame 的 tile 选项,以及带动画的背景;
  • 在游戏运行时更改样式(Python 中的 style.button.background = ...、style.rebuild());样式是定义,在游戏启动或脚本重新加载时读取;
  • 片段的 {=style} 写法;请写作 {style=name}。

属性集是 Novella 自有的,已在上文列出:Ren'Py 的 outlines 列表对应 outline_width 和 outline_color,文本的 xalign 式数值对应 text_align left|center|right,xpadding 或 left_padding 对应 padding 的各边。没有 Novella 对应项的 Ren'Py 属性会被验证器报告为未知属性。Ren'Py 的 gui 变量在主题中有对应项,主题的键同样是内置样式的简写。