繁體中文
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 變數在主題中有對應項,主題的鍵同樣是內建樣式的簡寫。