MarkVenture 創作者指南

Version: 2.1 Date: 2026-05-04 Language: zh-TW

想像你的玩家打開故事,發現自己置身在一間陌生的密室。四面牆壁,一個上了鎖的保險箱,書架上藏著某個線索。他們開始探索、翻東西、嘗試每一把鑰匙,最後當保險箱彈開的瞬間,興奮地把連結傳給朋友。

這個體驗,你只需要 Markdown 就可以做到。

MarkVenture 語言以 Markdown 為基礎,在它上面加了一套腳本語法。場景、物件、玩家看到的文字,用 Markdown 寫;玩家做了某件事之後會發生什麼,用腳本語法寫。兩件事合在同一個純文字檔案裡,不需要寫程式。

還不熟悉 Markdown?網路上有很多五分鐘入門,搜尋「Markdown 教學」就找得到。MarkVenture 只用到最基本的幾個語法,跟著範例走很快就上手了。


目錄

入門

核心語法

進階功能

參考


1. 五分鐘上手

先從最小的開始

複製下面這段,貼到編輯器——你的第一個互動故事就完成了:

---
title: 神秘房間
language: zh-TW
---

## 房間

你站在一個陌生的小房間裡。牆上掛著一幅[畫](painting)。

thing painting "一幅風景畫":
  [仔細看] show "畫的背面有一行字:「勇氣是唯一的鑰匙。」"

@here:
  [離開] -> 走廊

## 走廊

你走出了房間,迎面是長長的走廊。

end

就這樣。 這個故事有兩個場景、一個可以互動的事物、一個結局。

四個核心概念:

寫法 作用
## 場景名稱 定義一個場景
@here: 這個場景裡玩家可以做的事(後面縮排填選項)
[文字] -> 目標場景 跳到另一個場景
thing id "描述": 定義可互動的事物

加入更多機制

掌握基礎之後,看看加入道具、鎖、多個場景之後能做到什麼。把下面這段貼到編輯器試試:

---
title: 密室逃脫:書房
language: zh-TW
---

## 書房

你醒來,發現自己在一間陌生的書房。房門緊閉,空氣有點沉。

桌上放著一個[木盒子](box),書架上有一本[奇怪的書](book)。角落有扇[木門](door)。

@here:
  [把書架推開] -> 書架後面

thing box "一個上鎖的木盒子":
  fallback "這東西打不開。"
  use rusty-key:
    remove rusty-key
    give door-key "銀色的小鑰匙"
    show "盒子打開了,裡面有一把銀色的小鑰匙!"

thing book "封面上有奇怪的圖案,書背沾著一點鏽跡。":
  [翻開書] show "扉頁上寫著:『鑰匙就在眼前,只是被藏起來了。』"

item door-key "銀色的小鑰匙"

thing door "一扇厚重的木門,裝著一把鎖。":
  fallback "門是鎖著的。"
  [嘗試推開] show "門紋絲不動。"
  use door-key:
    show "鎖打開了!你推開門,走出了書房。"
    end

### 書架後面

你把書架推開,後面露出一個凹槽,裡面躺著一把[生鏽的鑰匙](rusty-key)。

item rusty-key "一把有點生鏽的鑰匙"

@here:
  [返回書房] -> 書房

這個範例多用了幾個機制:item 宣告讓道具可以撿進背包、use 讓道具能觸發事件、fallback 設定預設反應。後面的章節會逐一說明。

基本結構

寫法 作用
--- 包住的區塊 故事設定(標題、語言、標籤等,詳見語法速查
## 場景名稱 定義一個場景
### 場景名稱 定義子場景(小地點、靠近檢查)
@here: 這個場景裡玩家可以做的事
[文字] -> 目標場景 跳到另一個場景
[文字] show "..." 同場景動作(不跳轉)
thing id "描述": 定義可互動的場景事物
item id "描述" 定義可以撿起的道具

2. 場景與導航

每個場景由兩個部分組成:

場景描述(scene content)——玩家看到的文字:空間、氛圍、當下的狀態。用 Markdown 散文寫,就像劇本裡的場景說明。

腳本區塊(script block)——這個場景裡有什麼事物、可以做什麼、觸發什麼。用縮排結構語法寫,像劇本裡的舞台指示。

兩者最大的不同:場景描述是靜態的,玩家一進場景就看到;腳本區塊是動態的,定義玩家的行動和後果。你是創作者,每個玩家都是獨立的執行者,也是自己那場演出的唯一觀眾。

跳場景

[選項文字] -> 場景名稱 連結場景。場景名稱要完全一致:

## 走廊

黑暗的走廊通往兩個方向。

@here:
  [往左走] -> 圖書室
  [往右走] -> 廚房
  [原地等待] show "你聽到遠處傳來腳步聲。"

## 圖書室

這裡放滿了書。

## 廚房

爐子上的鍋子還在冒煙。

同場景互動

選項後面直接接指令,就是不跳轉的同場景動作:

[搖搖看] show "盒子裡有東西在滾動。"

如果需要執行多個指令,用 [文字]: 加縮排:

[仔細查看]:
  show "盒子裡有東西在滾動。"
  set box_checked = true

指令區、選項區與 then: 區塊

同一個互動區塊(@here:、entity 互動等)裡,指令、選項、then: 必須按照以下順序出現,不可混用:

指令區(set / show / discover / …)
選項區([選項] / if + [選項])
then: 區塊(共用的後續指令)
use/apply 區(道具互動,永遠最後)

指令區在選項顯示前執行,適合初始化或條件判斷。 選項區列出玩家可以點的選項。選項區的 if 裡只能有選項,不能有其他指令。 then: 是語法糖:它的指令會在每個選項的動作結尾自動附加,等同手動複製到每個選項裡。 use/apply定義道具對這個對象的效果,必須放在最後。

@here:
  show "門微微開著。"         ← 指令區:進入場景時立刻執行
  [推門進去] -> 走廊           ← 選項區
  [轉身離開] -> 大廳
  then:                        ← 每個選項執行後都會附加這段
    set door_checked = true
  use key:                     ← use/apply 區:永遠最後
    show "這裡不需要鑰匙。"

上面的範例等同於:

@here:
  show "門微微開著。"
  [推門進去] -> 走廊:
    set door_checked = true
  [轉身離開] -> 大廳:
    set door_checked = true
  use key:
    show "這裡不需要鑰匙。"

注意: 若選項區完全沒有選項,then: 無法套用,parser 會報錯。

子場景

## 是主場景,### 是子場景(圖片顯示略小,適合「靠近檢查」的感覺):

## 書房

寬敞的書房,最醒目的是靠牆的大書架。

@here:
  [靠近書架] -> 書架
  [坐到書桌前] -> 書桌

### 書架

書架上的書都被翻動過。

@here:
  [返回] -> 書房

### 書桌

桌面上散落著文件。

@here:
  [返回] -> 書房

場景文字格式

場景描述支援部分 Markdown 格式:

語法 效果
**文字** 粗體
*文字* 斜體
> 文字 引言(縮排灰字)
## 密室

**鐵門**緊緊鎖住。你環顧四周,感覺*時間正在流逝*。

> 一張紙條從門縫滑了進來。

3. 腳本區塊

這是 MarkVenture 最有趣的地方。

場景描述之後可以跟著腳本區塊(script block),定義玩家可以互動的東西。腳本區塊裡有三種類型:

類型 宣告方式 說明
場景事物(scene object) thing id "描述": 只在定義它的場景有效
道具(item) item id "描述" 可撿進背包,跨場景使用
線索(clue) clue id "描述" 可發現,跨故事記錄

定義場景事物與道具

在場景描述裡用 [顯示名稱](id) 標記事物或道具,然後在下方定義它的互動行為(interaction)。id 只能包含小寫英文字母、數字、連字號:

## 入口大廳

大廳中央有一扇[鐵門](iron-door)。

thing iron-door "一扇沉重的鐵門":
  [敲門] show "沉重的回響。沒有人回應。"
  [嘗試推開] show "紋絲不動。"

如果事物只出現在場景描述裡、不需要互動行為,可以省略冒號和區塊:

thing iron-door "一扇沉重的鐵門"

可撿起的道具

item 前綴宣告,玩家就可以把這個道具撿進背包:

地上有一把[生鏽的鑰匙](rusty-key)。

item rusty-key "一把生鏽的老鑰匙"

玩家會看到「撿起」選項;撿起後,鑰匙進入背包,可以對場景中的事物使用。

道具也可以定義自己的互動行為(撿起前後行為不同):

撿起前:「撿起」排第一,其他互動選項接在後面:

item journal "一本皮面日記":
  [翻開看] show "扉頁上有人用鉛筆寫了幾個字。"

玩家在場景裡點擊日記,會看到「撿起 日記」和「翻開看」兩個選項。

撿起後:點擊背包裡的日記,會看到「使用 日記…」和「翻開看」兩個選項。「使用 日記…」進入 use mode,玩家再點場景中的目標觸發組合互動;「翻開看」則直接執行。

沒有定義任何互動行為的道具,撿起後點擊仍然直接進入 use mode,行為與之前相同。

用道具解謎

玩家從背包選道具,點場景中的目標,觸發互動。邏輯寫在目標這邊,不是道具這邊:

thing iron-door "一扇沉重的鐵門":
  fallback "這個東西對門好像沒什麼用。"
  use rusty-key:
    show "鎖打開了!"
    [進入內部] -> 走廊

fallback 是玩家使用了「沒有對應互動的道具」時看到的訊息。不寫的話,預設顯示「沒有反應。」

消耗道具

用完的道具加上 remove 從背包移除:

thing iron-door "一扇沉重的鐵門":
  use rusty-key:
    remove rusty-key
    show "鎖打開了,鑰匙也斷在裡面。"
    [進入內部] -> 走廊

use 與 apply 的區別

use(道具對場景事物,item-on-scene-object): 邏輯寫在場景事物的互動行為裡。玩家點擊場景事物,互動面板會列出「使用 [道具]」的選項。效果只在該場景生效。

apply(道具對道具,item-on-item): 邏輯寫在 item 的互動行為裡。玩家必須先從背包選取道具(進入使用模式),再點擊目標道具才能觸發。不會顯示在選項列表,全故事生效。

這個設計是刻意的:提前顯示「可以用 A 對 B」會洩漏謎題答案。需要隱藏提示的組合,應把目標設計成 item 而非場景固定實體。

道具本身的互動行為: 若道具定義了 link 互動,玩家點擊背包裡的道具時,會開啟互動面板,顯示「使用 [道具]…」(進入 use mode)和所有定義的互動選項。適合「隨時可翻查」的道具,例如筆記本、地圖、日記。

合成道具

一個道具對另一個道具使用,可以產生新道具。邏輯寫在「目標道具」的互動行為裡:

地上有[木棍](stick)和[破布](cloth)。

item stick "一根普通的木棍"
item cloth "一塊破爛的布":
  apply stick:
    remove stick
    remove cloth
    give torch "火把"
    show "你把布纏在木棍上,做出了火把!"
item torch "用布纏繞木棍製成的火把"

give item-id "顯示名稱" 把道具直接加進背包。透過 give 取得的道具,仍然需要在某個場景的腳本區塊裡宣告 item item-id "描述",否則 parser 會報錯。若場景描述文字裡已有 [名稱](item-id) 的引用,give 後面的名稱可省略;否則建議提供,省略時 parser 會警告,背包會顯示 ID。

對自己使用道具(@self)

玩家選中道具後,點「使用」按鈕,觸發 @self 互動(效果不限場景,全故事生效):

@self:
  use potion:
    remove potion
    set health += 50
    show "你喝下藥水,感覺恢復了不少。"
  use armour:
    remove armour
    set defence += 10
    show "你穿上了盔甲。"

@self 可以分散寫在多個場景裡,引擎會自動合併。但同一道具只能在一個 @self 區塊定義,重複定義會在儲存時報錯。@self 底下只能有 use/apply 區塊(可搭配 if),選項或其他指令不會有任何效果,parser 會報錯。


4. 條件與變數

追蹤狀態

set 改變變數,用 if 根據變數控制選項:

## 走廊

@here:
  [開門]:
    set door_open = true
    show "門打開了。"
  if door_open:
    [走進去] -> 房間內部

變數名稱只能包含小寫英文字母、數字、底線(_),例如 door_openhas_keyquest_started

變數不需要預先宣告——set 會自動建立,未宣告的變數預設值是 false / 0 / ""

在 frontmatter 設定初始值(也可以讓你清楚記錄故事裡用了哪些變數):

---
title: 我的故事
variables:
  health: 100
  gold: 0
  has_invitation: false
---

數值運算

set gold += 10     ← 加
set health -= 20   ← 減
set level = 5      ← 直接設定

條件判斷

@here:
  if health > 50:
    [繼續前進] -> 深處
  else if health > 20:
    [謹慎前進] -> 謹慎探索
  else:
    [太虛弱了,先休息] -> 休息處

比較運算子: = != > < >= <=

字串比較不分大小寫("Lumos""lumos" 視為相同)。

字串包含: includes

  if player_name includes "龍"     ← 名字裡有「龍」
  if not notes includes "龜"       ← 記錄裡沒提到龜
  if event_order includes "13"     ← 順序裡 1 和 3 連續出現

不分大小寫,連續字元比對。若要判斷非連續字元包含,用多個 includes 搭配 and

  if guilty includes "1" and guilty includes "3"

邏輯連接詞: and or

  if has_sword and health > 30:
    [戰鬥] -> 戰鬥場景
  if gold >= 100 or has_invitation:
    [進入宴會廳] -> 宴會廳

道具條件

  if has rusty-key        ← 道具目前在背包裡
  if taken rusty-key      ← 道具曾經被撿起過(即使已消耗也為 true)

taken 常用來在道具被撿走後,讓場景描述不再顯示它。

場景文字裡的條件

在描述文字中直接插入條件,根據狀態顯示不同文字:

## 書房

你站在書房。{if door_open}門是開著的。{else}門是關著的。{/if}

{if not taken crystal}角落台座上有一顆[魔法水晶](crystal)。{/if}

你目前有 {gold} 金幣,血量 {health}/100。

隨機數字

@here:
  [擲骰子]:
    random dice 1 6
    show "你擲出了 {dice}!"
    if dice >= 4:
      show "成功!"
    else:
      show "失敗。"

語法:random 變數名稱 最小值 最大值(包含兩端)

顯示訊息

show 顯示一則訊息,緊接著繼續執行後續指令:

@here:
  [閱讀信件]:
    show "信上寫著:『鑰匙藏在書架後面。』"
    set read_letter = true

分段暫停(pause

如果一個動作要顯示多則訊息,可以加上 pause,讓引擎在顯示這則訊息後暫停、等讀者點「繼續」,再執行後面的指令:

@here:
  [閱讀日記]:
    show "日記的字跡潦草,看來是匆忙寫下的。第一頁寫著一個地址,後面幾頁撕掉了。" pause
    show "你翻到最後一頁。空白,只有角落蓋了一個紅色印章。"

不加 pause 的話,多則 show 會同時出現在畫面上(依序以打字機效果呈現)。

pause 也可以單獨一行使用,在不需要新增文字的情況下插入一個暫停點:

@here:
  [查看線索]:
    show "第一段訊息。"
    if resolved some-clue:
      pause
      show "因為你先前發現了某件事,這裡有額外的訊息。"

玩家輸入

prompt 有兩種用法:自由文字輸入,或選項點選介面。

自由文字輸入

@here:
  [說出你的名字]:
    prompt player_name
    [繼續] -> 歡迎

## 歡迎

「歡迎你,{player_name}!」

選項點選(單選,預設)

玩家點選一個選項,變數存入選項文字。

@here:
  [選擇陣營]:
    prompt faction [正義聯盟, 暗影公會, 中立商會]
    if faction = "正義聯盟":
      show "你加入了正義聯盟。"

複選

玩家可勾選多個選項,變數存入 1-based index 串接字串(如 "13" 代表第 1 和第 3 個)。

@here:
  [誰是共犯?]:
    prompt suspects [管家, 廚師, 園丁] multiple
    if suspects = "12":
      show "管家和廚師都有嫌疑。"
    else if suspects includes "3":
      show "園丁也在你的懷疑名單裡。"

排序

玩家依序點選,變數存入點選順序的 index 字串(如 "312" 代表先選第 3、再選第 1、最後選第 2)。

@here:
  [重建案發順序]:
    prompt timeline [聽見槍聲, 燈光熄滅, 玻璃破碎] order
    if timeline = "132":
      show "正確!"
    else:
      show "再想想。"

線索系統

線索是「拿來想的」,和道具(拿來用的)不同。每條線索有兩個階段:發現(discover)和解開(resolve)。玩家執行 discover 動作後,線索才出現在線索欄;之後需要進一步探索才能 resolve

發現線索(discover)

線索不會自動出現。創作者必須在互動動作裡明確寫 discover,才能讓玩家發現線索。

單一發現路徑:在 discover 指令裡直接定義線索(內聯定義):

thing sign "石板指示牌":
  [仔細看]:
    discover note "一張撕去一角的便條紙" (thread: culprit)

多個發現路徑:先宣告線索,再在多個地方用 discover 引用:

clue note "一張撕去一角的便條紙" (thread: culprit)

thing table "書桌":
  [仔細搜查]:
    discover note

thing chair "椅子底下":
  [往下看]:
    discover note

規則:

  • 有獨立宣告時,discover 不能再附上定義(否則報錯:重複定義)
  • 無獨立宣告時,discover 必須附上定義(否則報錯:未定義)
  • 線索 ID 全域唯一,不可在不同地方定義兩次

線索宣告語法(多路徑用)

clue clue-id "線索描述"
clue clue-id "線索描述" (thread: thread-name)
clue clue-id "線索描述" (thread: thread-name, optional)
  • thread-name:選填,將線索歸入謎題線
  • optional:標記後不影響謎題線的完成計算

謎題線進度顯示

謎題線面板的顯示行為由 engine 自動管理,創作者不需要額外設定:

狀態 觸發條件 顯示
不出現 該謎題線沒有任何線索被 discover 面板不顯示此謎題線
仍有線索未發現 至少一個 required 線索已 discover,但有 required 線索尚未 discover 謎題線標題旁顯示「仍有線索未發現」
仍有線索未解開 所有 required 線索已 discover,但有 required 線索尚未 resolve 謎題線標題旁顯示「仍有線索未解開」
已解開 所有 required 線索已 resolve 謎題線標題旁顯示「已解開」

Optional 線索不納入進度計算:標記 optional 的線索不影響上述任何狀態判斷,玩家找到是驚喜,沒找到不影響進度提示。

設計建議:required 線索的 discoverresolve 應放在不同互動點,讓玩家能經歷「仍有線索未解開」的狀態。若 discoverresolve 在同一個動作裡,玩家會直接跳過此中間狀態。

重複發現:若玩家重複觸發已發現過的線索的 discover 動作,系統不會重複記錄,但會顯示提示訊息「線索『xxx』已記錄過了。」讓玩家確認曾在此處發現線索。這個提示會夾在該互動前後的 show 訊息之間,能幫助玩家回憶線索的發現情境。

Thread 描述(frontmatter,選填)

---
title: 我的故事
threads:
  culprit: "誰是兇手"
---

解開線索(resolve)

resolve clue-id
resolve clue-id = "字串值"
resolve clue-id = true
resolve clue-id = false
resolve clue-id = 42

省略值時預設為 truefalse 也是合法的值,代表「答案是否定的」。

執行 resolve 時,引擎會自動在故事 log 插入一條「線索解開:XXX」的提示,樣式有別於 show 訊息。不需要另外用 show 提示玩家。

條件判斷

if has clue-id                      # 線索已被發現(執行過 discover)
if resolved clue-id                 # 線索已被解開(不管值為何)
if not resolved clue-id             # 線索尚未解開
if resolved clue-id = "值"          # 解開為特定值
if resolved thread-name             # 謎題線內所有 required 線索都已解開

完整範例

---
title: 我的故事
threads:
  culprit: "誰是兇手"
---

## 書房

桌上的[便條紙](note)有些皺摺。

thing table "書桌":
  [翻看桌面]:
    discover note "一張撕去一角的便條紙" (thread: culprit)
    show "字跡潦草,只看得出是左撇子寫的。"

## 審訊室

@here:
  if has note:
    [嫌疑人是左撇子]:
      resolve note = "左撇子"
    [字跡看不出端倪]:
      resolve note = false
  if resolved culprit:
    [宣布結論] -> 結局

ID 規則

  • 線索 ID 全域唯一,不可重複定義(含宣告與內聯定義)
  • 同一場景內,所有 thing/item/clue ID 也不可重複

道具數量

道具不支援堆疊——同一個 ID 在背包只能有一個。需要追蹤數量請用數值變數:

set gold += 1      ← 正確:用變數追蹤數量
give gold-coin     ← 避免:無法堆疊

5. 結束故事

end 命令標記故事結局。觸發後,玩家會看到結束畫面,並出現「重新開始」按鈕和評分介面。

故事至少需要一個 end,否則 parser 會發出警告(仍可存檔,但玩家無法看到結局畫面)。場景若沒有任何離開方式(無跳轉連結、也無 end),parser 同樣會警告。

最簡單的用法,在腳本區塊頂層寫 end,玩家到達時立即結束:

## 結局

你找到了出口,逃出了密室。陽光灑在臉上。

end

也可以把 end 放在選項的動作裡,讓玩家先看到訊息再結束:

## 最終對決

@here:
  if has_sword and has_shield:
    [迎戰魔王]:
      show "你成功擊敗了魔王,拯救了王國!"
      end
  [逃跑] -> 逃跑路線

多結局的常見寫法:

## 尾聲

@here:
  if good_deeds >= 3:
    [光明結局] -> 好結局
  else if evidence_found:
    [真相結局] -> 真相結局
  else:
    [普通結局] -> 普通結局

## 好結局

你做出了正確的選擇。世界因你而改變。

end

## 真相結局

真相終於大白,但代價不小。

end

## 普通結局

一切回到原點,彷彿什麼都沒發生過。

end

6. 跨集存檔

如果你的故事分成多集,可以讓 EP1 的選擇影響 EP2 的劇情。EP1 結束時儲存指定的變數,EP2 開場時讀取還原。

EP1:儲存變數

end 之前加 save,指定要傳到下一集的變數名稱:

## 最終結局

你做出了選擇。

@here:
  [結束]:
    save mystery-manor-ep1-save [player_choice, found_key]
    end
  • mystery-manor-ep1-save 是存檔識別碼,慣例為 {slug}-save,發佈後不要更改
  • 方括號裡列出要儲存的變數名稱(以逗號分隔)
  • 同一個故事可以有多個 save(對應不同結局),每次觸發都會新增一筆存檔

EP2:讀取前集存檔

第一個場景加上 @load 區塊,故事開始時會自動顯示讀取視窗:

---
title: Mystery Manor EP2
---

## 序章

你記得那個抉擇——或者,你對它一無所知。

@load mystery-manor-ep1-save [player_choice, found_key]:
  if _loaded:
    if player_choice:
      show "記憶湧上來。你記得自己當時的選擇。"
    else:
      show "你對那段往事的記憶模糊不清。"
  else:
    show "那段往事的記憶消散了,你只能繼續向前。"

@here:
  [繼續前行] -> 第一關
  • @load 只能放在第一個場景,故事開始時自動觸發一次,之後不再觸發
  • 方括號裡列出要從存檔讀取的變數名稱(需與 save 裡的列表一致);只有列出的變數會被載入,其餘忽略
  • 讀取完成後,_loaded 會自動設為 true(有存檔)或 false(跳過)
  • 讀取成功時,系統會自動在訊息區顯示「已讀取上集探索記錄」提示,不需要在故事裡另加 show
  • @load 底下的指令在讀取視窗關閉後執行

封鎖入場

若前集存檔是必要的,在存檔識別碼後加上 required,沒有存檔的登入玩家將在入場時被攔截:

@load mystery-manor-ep1-save [player_choice, found_key] required:
  show "記憶還在。你記得上次發生的事。"

存檔行為

已登入 未登入
save 自動儲存到帳號(最多 10 筆) 顯示存檔碼,請玩家自行複製保存
@load 顯示存檔列表供選擇 顯示輸入框,玩家貼上存檔碼

7. 場景圖片

標記需要圖片的場景

在場景標題下方加上提示詞:

## 廢棄工廠
{img: "abandoned factory, rusty machinery, dim light, realistic"}

你走進一間廢棄已久的工廠...

或用 auto 讓系統以場景名稱自動生成:

## 神秘森林
{img: auto}

同一部故事裡,如果某個場景需要不同風格,可以在 {img} 裡加 style:,會取代 frontmatter 的全局 style:

## 序章
{img: "山丘小徑,晨霧,清晨陽光", style: "寫實水彩,柔光"}

## 古宅大廳
{img: "宏偉廳堂,燭光昏黃,塵封的地毯"}

圖片比例: 16:9 為佳,上傳其他比例會自動裁切。

上傳圖片(推薦方式)

  1. 用任何工具生成圖片(ChatGPTGeminiIdeogramCanva
  2. 儲存為圖片檔案(上傳上限 10MB;寬度超過 1536px 會自動縮小,並轉存為 JPEG)
  3. 在編輯頁面的「場景圖片」區塊,點擊場景旁的「上傳圖片」

AI 自動批次生成(需 API Key)

在帳號設定頁填入 API Key 後,編輯頁面會出現「生成缺少的圖片」按鈕,可一次生成所有標記了 {img:} 的場景圖片。

服務 provider API Key 來源
Google Gemini gemini aistudio.google.com
OpenAI DALL-E 3 dall-e-3 platform.openai.com
Stability AI stable-diffusion platform.stability.ai

在 frontmatter 指定服務與風格:

---
title: 我的故事
ai_image:
  provider: gemini
  style: "soft watercolor illustration, warm light"
---

不指定 provider 時,系統依照已填入的 Key 自動選擇(順序:Gemini → OpenAI → Stability AI)。

提示詞技巧:

  • ✅ 具體:「古老的圖書館,高聳的書架,暖黃燈光,油畫風格」
  • ✅ 指定風格:「水彩插畫」「像素藝術」「電影感寫實」
  • ❌ 太抽象:「美麗的地方」「神秘的感覺」

8. 設計建議

給選項真正的意義:

@here:
  [正面迎戰] -> 戰鬥    ← 高風險高回報
  [嘗試談判] -> 談判    ← 需要滿足條件
  [轉身離開] -> 逃跑    ← 安全但放棄機會

盡量避免「假選擇」——三個選項只有一個有意義的設計,玩家很快就會感覺到。

一個場景一個焦點:

不要把所有東西塞進同一個場景。讓玩家用選項主動「靠近檢查」,而不是在一個場景裡塞滿十個物件。

加有趣的 fallback 訊息:

玩家一定會亂試道具。與其讓他們看到無聲無息的「沒有反應」,不如給每個事物一個有個性的 fallback 訊息——這本身就是敘事的一部分。

「n選1、不可回頭」的選擇:

大多數選項玩家可以重試,但有些時刻需要讓玩家做出真正的、一次性的選擇——例如內心的表態、對某件事的最終看法。這類選項可以在出現前用 show "..." pause 製造一個停頓節點:玩家主動按繼續後,才看到選項。訊息內容加上這個停頓本身,就在告訴讀者:這一刻不能隨便點選。

[你怎麼看這件事]:
  show "你站在那裡,心裡把幾個答案都轉了一遍──" pause
  [「這是對的選擇」]:
    ...
  [「我不後悔,但我知道代價」]:
    ...
  [「說不清楚。只是走到了這一步」]:
    ...

發佈前測試清單:

  • 所有場景都能抵達
  • 沒有意外的死胡同(除非故意設計)
  • 所有結局都可以達到
  • 道具 ID 和引用名稱一致
  • 試過至少兩條不同路線

9. 語法速查

Frontmatter

---
title: 故事標題
description: 故事簡介(顯示在列表頁)
language: zh-TW          # 支援:zh-TW, en, ja, ko
tags: [解謎, 恐怖, 短篇]
fallback: "這樣做沒有任何效果。"
variables:
  health: 100
  gold: 0
  has_key: false
ai_image:
  provider: gemini
  style: "watercolor illustration"
threads:
  entry-method: 入侵手法
  timeline: 時間線
---

場景定義

## 主場景名稱
### 子場景名稱

場景描述格式

**粗體文字**
*斜體文字*
> 引言文字

場景選項(@here)

@here:
  [跳到場景] -> 目標場景
  [同場景動作]:
    show "訊息文字"
  [動作加訊息] show "訊息文字"
  end
  if condition:
    [條件選項] -> 場景
  else if other_condition:
    [另一選項]:
      show "訊息文字"
  else:
    [預設選項] -> 場景

指令(選項底下縮排)

show "訊息文字"
show "訊息文字" pause          ← 顯示後暫停,等讀者點「繼續」再執行後續指令
pause                          ← 不顯示新文字,直接插入暫停點
set variable = value
set variable += 5
set variable -= 5
random var_name 1 6
prompt variable_name
prompt variable_name [選項1, 選項2, 選項3]
prompt variable_name [選項1, 選項2, 選項3] single
prompt variable_name [選項1, 選項2, 選項3] multiple
prompt variable_name [選項1, 選項2, 選項3] order
discover clue-id "描述" (thread: thread-id)
discover clue-id
resolve clue-id
resolve clue-id = value
give item-id "顯示名稱"
remove item-id
save save-key [var1, var2]
end

跨集存檔

@load save-key [var1, var2]              ← 故事開始自動觸發讀取視窗(可跳過)
@load save-key [var1, var2]:             ← 加上執行邏輯時用冒號+縮排
  if _loaded:
    show "有前集存檔時顯示"
  else:
    show "無存檔或跳過時顯示"

@load save-key [var1, var2] required     ← 無存檔的登入玩家直接被攔截

save save-key [var1, var2]               ← 儲存指定變數到跨集存檔

實體與道具

thing entity-id "描述文字"                          ← 場景事物(無互動行為)
thing entity-id "描述文字":                         ← 場景事物(有互動行為)
  [自訂動作] show "訊息"
  fallback "使用無效道具時的訊息"
  use other-item-id:                              ← item-on-scene-object
    show "互動訊息"

item entity-id "描述文字"                         ← 道具(無互動行為)
item entity-id "描述文字":                        ← 道具(有互動行為)
  apply other-item-id:                            ← item-on-item
    show "互動訊息"
    [繼續] -> 場景
    remove other-item-id

clue clue-id "描述文字"                                    ← 線索(無分類)
clue clue-id "描述文字" (thread: thread-id)               ← 線索(歸屬分類)
clue clue-id "描述文字" (thread: thread-id, optional)     ← 選填線索

@self:                                            ← 自用道具的目標
  use potion:
    remove potion
    show "效果描述"

場景文字中的條件

{if condition}顯示文字{/if}
{if condition}A{else}B{/if}
{variable_name}

條件語法

if has_key                ← 布林值為 true
if not has_key            ← 布林值為 false
if has item-id            ← 道具目前在背包
if taken item-id          ← 道具曾經被撿起(即使已消耗也為 true)
if gold >= 100            ← 數值比較
if name = "勇者"          ← 字串比較(不分大小寫)
if name includes "龍"     ← 字串包含(不分大小寫)
if a and b
if a or b
else if condition
else