樣式
樣式是一種命名的外觀:一組用於文字和容器的屬性,例如字型、顏色、背景和內邊距,並帶有一個父級,凡是它自身未設定的屬性都從父級繼承。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,而是一個獨立的屬性:指標移到按鈕上時播放的聲音。
如何尋找屬性
尋找屬性時,先在樣式本身中尋找,然後依次在其父級中尋找。在每個樣式中:
- 與元素狀態相符的帶狀態字首的屬性優先,最具體的在前,排序方式與 Ren'Py 相同:被選中元素的複合字首(
selected_hover_),然後是selected_,然後是hover_或insensitive_,然後是visited_,最後是idle_; - 否則使用不帶字首的屬性;
- 只有當樣式兩者都沒有時,才會詢問其父級。
因此,設定了某個不帶字首屬性的樣式會覆蓋該屬性的所有狀態,經由它無法到達父級的狀態變體:
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"
在介面上
介面中的每個元素都用一個樣式繪製,按以下順序選擇:
- 元素用
style "name"指定的樣式;對於按鈕的標籤,若故事中存在name_text,則使用它; - 否則,若故事中存在該樣式,則使用其容器的字首下該元素種類的樣式:在帶有
style_prefix "gold"的介面或容器中,按鈕用gold_button繪製,其標籤用gold_button_text繪製,text用gold_text繪製,frame用gold_frame繪製; - 否則使用該元素種類自身的樣式:
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 繪製;第二個按鈕的標籤無論處於何種狀態都是白色,因為直接寫在元素上的屬性會疊加在其樣式的所有狀態之上。

角色
角色可以為其臺詞的每個部分設定專屬外觀。這些部分是 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 的預設視窗。
較早的角色選項仍然有效,並且在其有所設定時仍然優先: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 變數在主題中有對應項,主題的鍵同樣是內建樣式的簡寫。