项目说明

该项目正在准备使用 Rust 重写。

希望我们最后不要弃坑!

故事脚本

故事脚本的文法完全基于 Markdown,因此您不需要对您的编辑器进行任何配置就可以开始编写您的故事。

标题与段落

使用标题来说明当前说话的人物,使用 --- 分隔符来清空当前说话的人物。

每一段独立文本之间使用两个回车分割。

# 纳西妲

「你好呀」

# 我

「……」

「……你好」

---

突然有个羽毛球上前向我搭话,我莫名感觉到有些慌乱

定睛一看,才发现是个少女

上面的样例中,所有位于 # 我--- 之间的文本都会被当作 说的。

背景

若要添加/更换背景,可以使用 ![bg <transition>](url "<position> / <size> <animation>") 的格式进行声明。

  • 关于位置 <position> 和大小 <size> 的有关内容,请参考大小与位置章节。
  • 关于转场 <transition> 和动画 <animation> 的有关内容,请参考转场与动画章节。

人物立绘

人物立绘使用 ![fig <transition>](url "<name> <position> / <size> <animation>") 的方式来进行初始化声明。

  • <name> 用于唯一确定立绘的人物名称,以便后续操作的时候方便区分目标。
  • 关于位置 <position> 和大小 <size> 的有关内容,请参考大小与位置章节。
  • 关于转场 <transition> 和动画 <animation> 的有关内容,请参考转场与动画章节。

注意:人物名称是必选项,其他都是可选。当需要对人物立绘进行更新的时候,只需要给予相同的人物名称即可。

当需要移除立绘的时候,使用 ![fig remove ...](...) 操作,引擎会在动画完成之后移除相应的动画对象。

背景音乐

背景音乐使用 ![bgm](url) 的语法进行声明。

引擎会先淡出当前正在播放的背景音乐,之后开始播放当前的音乐。

![bgm](./bgm/boundless-bliss.mp3)

其他操作

  • 原地等待若干时间(毫秒)

    [wait](#2000)
    

流程控制

使用链接元素进行流程的控制。

  • 跳转到其他剧情文件

    [goto](./xxx.md)
    
  • 跳转到其他剧情文件并结束游戏

    [end](./xxx.md)
    

分支选项

我们使用无序列表来迫使用户做出一次选择。

无序列表中只能包含纯文本内容。

- 好啊
- 还是算了

之后可以用有序列表来根据用户的选择执行对应的操作。

有序列表中可以包含任何可解析的标签,当用户在上一次选择某条分支之后,程序会自动执行对应分支内的代码。

1. [goto](./happy_end.md)
2. [goto](./bad_end.md)

上文中如果您之前选择 好啊,那么将会跳转到 ./happy_end.md 中的剧情,否则会跳转到 ./bad_end.md 中的剧情。

角色语音

角色语音使用 ![v](url) 的方式声明,会与当前的文本段落自动绑定。

# 纳西妲

![v](./vocal/nahida-1.mp3)
「你说的对,但是原神是……」

![v](./vocal/nahida-2.mp3)
「后面忘了」

如果语音不能与任何文本匹配,或者一个段落中出现了多个语音文件,那么会抛出一个编译错误。

音效

对于只需要播放一次的简单音效,使用 ![sfx](url) 的方式声明。

![sfx](./sfx/open_letter.mp3)

内嵌代码(未实现)

可以使用 <script></script> 标签、代码块,以及行内代码的方式进行内嵌 JavaScript 代码。

<script>
  var a = 114514;
</script>

```js
var b = 1919810;
```

# 纳西妲

`if (a > b) {`
「我觉得您说的对」
`} else {`
「我觉得您说的不对」
`}`

以上代码会被编译成

export default async function* (ctx) {
  var a = 114514;
  var b = 1919810;
  if (a > b) {
    yield /* 纳西妲:「我觉得您说的对」 */ [];
  } else {
    yield /* 纳西妲:「我觉得您说的不对」 */ [];
  }
}

关于程序运行时的上下文对象 ctx,可以查看关于 ctx 的有关文档。

定位和大小

在背景图片和人物立绘的地方都可以看到 <position><size> 的参数。

本文具体解释这两个参数的取值和行为。

定位

<position> ::=
  | <position-1>
  | <position-2>
  | <position-3>
  | <position-4>

<position-1> ::=
  | <position-x>
  | <position-y>
  | center
  | <percentage>

<position-x> ::= left | right
<position-y> ::= top | bottom
<percentage> ::= <float32> %

<position-2> ::=
  | <position-x> <position-y>
  | <position-y> <position-x>
  | ( <percentage> | center ) <position-y>
  | <position-x> ( <percentage> | center )
  | ( <percentage> | center ) ( <percentage> | center )

<position-3> ::=
  | ( <position-x> | center ) <position-y> <percentage>
  | ( <position-y> | center ) <position-x> <percentage>
  | <position-x> <percentage> ( <position-y> | center )
  | <position-y> <percentage> ( <position-x> | center )

<position-4> ::=
  | <position-x> <percentage> <position-y> <percentage>
  | <position-y> <percentage> <position-x> <percentage>
  • 水平方向的 0%left 相同,100%right 相同。
  • 垂直方向的 0%top 相同,100%bottom 相同。

注意如果图片高度与窗口高度相同,那么垂直方向的任何百分比都没有区别。

可以参考 CSS 标准中的 background-position 属性。

大小

<size> ::=
  | contain
  | cover
  | fill
  | ( <percentage> | auto )
  | ( <percentage> | auto ) ( <percentage> | auto )

如果没有指定 <size>,则默认设置为 auto

  • contain:将图片缩放,使得其保持最大不会超过屏幕大小的尺寸
  • cover:将图片缩放,使得其保持最小能覆盖整个屏幕大小的尺寸
  • fill:拉伸图片使得长宽与屏幕大小相等
  • auto:相当于 contain
  • auto auto:相当于 contain
  • <percent>:指定图片宽度与屏幕宽度的百分比,高度等比例缩放
  • <percent> auto:指定图片宽度与屏幕宽度的百分比,高度等比例缩放
  • auto <percent>:指定图片高度与屏幕高度的百分比,宽度等比例缩放
  • <percent> <percent>:指定图片宽高和屏幕宽高的百分比

可以参考 CSS 标准中的 background-size 属性,不同的是此处的百分比是相对于屏幕大小的。

转场和动画

所有的动画效果大致分为转场 <transition> 和动画 <animation> 两种。

两者的主要区别是:

  • 转场可以主动跳过,必要的时候可以通过点击直接快进到转场结束
  • 当快进的时候,所有转场会被直接跳过
  • 动画会随转场一起播放,但不可以被快进,也无法追踪播放进度
  • 动画可以设置播放次数、是否循环等,转场只能播放一次

例如,背景图片的淡出属于转场,而图片从左到右的缓慢平移属于动画。

此外,为了方便控制各种转场的时间,我们规定:位于同一个段落之内的所有转场都会同时开始播放。

也就是说,下面的样例中,展示了整个转场的时间线。

![bg fade-in 5s](./background.png)
![fig fade-in 3s](./background.png)

你说的对

时间线:

| 0s | 1s | 2s | 3s | 4s | 5s | 6s |
|------------------------| bg fade-in 5s
|--------------| fig fade-in 3s
                         |---------- text

转场

转场 <transition> 的可选内容如下:

名称说明默认值
fade-in淡入动画
fade-out淡出动画
conic-in径向淡入动画
conic-out径向淡出动画
blinds-in百叶窗淡入动画
blinds-out百叶窗淡出动画
shake抖动动画
<time>设置动画时长1s
<easing>设置动画的时间函数linear

其中除 shake 动画外,其他动画都相互冲突。

动画

动画 <animation> 的可选参数如下:

名称说明默认值
to <position> / <size>平移动画
<time>设置动画时长60s
<easing>设置动画的时间函数linear

如果没有任何动画声明,那么动画时长和动画时间函数的设置将会被忽略。

示例:

  • 背景会突然出现

    ![bg](./background/image.png)
    
  • 首先淡入图片,播放从上到下的动画。

    之后图片逐渐变成从下向上的动画。

    最后图片淡出。

    ![bg fade-in](./background/image.png "top to bottom")
    
    ![bg fade-in](./background/image.png "bottom to top")
    
    ![bg fade-out](./background/image.png "bottom to top")
    

此外,可以利用脚注之类的功能对拥有相同图片动画效果的背景图片进行统一声明,并多次引用。

![bg fade-in][bg-1]
![bg fade-in][bg-2]
![bg fade-in][bg-1]
![bg fade-in][bg-2]

[bg-1]: ./assets/background/image.png "top to bottom"
[bg-2]: ./assets/background/image.jpg "bottom to top"

动画时间线

很多时候由于剧情需要,我们需要更加精细化地控制各类背景、人物素材的转场的开始时间和结束时间。 Markdown Story 提供了一套基于段落的转场控制机制。

简单来说,位于同一段落内的所有转场都会被一起播放。

例如

  • 下面的样例中,背景的转场和人物立绘的转场会一起开始播放,并且在他们都播放结束之后才会开始显示下面的文本。

    ![bg fade-out](./background.png)
    ![fig fade-out](./figure/alice.png "alice")
    
    「你说的对,但是原神」
    
  • 下面的样例中,会先播放背景的转场。动画完成之后,人物立绘的转场和文本才会一起开始播放。

    ![bg fade-out](./background.png)
    
    ![fig fade-out](./figure/alice.png "alice")
    「你说的对,但是原神」
    

此外,同一个段落中不允许出现多次 ![bg]() 和相同人物立绘的 ![fig]() 声明。

例如,下面是一些错误的语法:

  • 出现了多次 ![bg]() 动画,由于不能同时播放两个动画于一个对象,因此会产生冲突

    ![bg fade-out](./background/old.png)
    ![bg fade-in](./background/new.png)
    
  • 出现了对同一个人物立绘的多次动画

    ![fig move](./figure/alice.png "alice contain / center")
    ![fig fade-out](./figure/alice.png "alice contain / center")
    

    由于 movefade-out 并不是冲突的动画,如果有必要请写成一条指令中。

    ![fig fade-out move](./figure/alice.png "alice contain / center")