Skip to content

快速开始

许兴逸 edited this page Aug 20, 2022 · 25 revisions

基本文法

场景

一组剧本有多个 场景 组成,一个场景可以用于表示一段剧本或者一幕UI,以下例子定义了一个名为main的场景:

- scene "main"

同一个文件可以定义多个场景,例如:

- scene "main"
- scene "abc"
- scene "def"

对于-开头的定义,被称作 顶级定义

注释

YukimiScript可以使用#符号来表示行注释,如以下例子:

# 这是一段注释
- scene "main" # 这里定义了场景main
# 注释

文本

在场景下可以定义一段文本,以下例子定义了一个名为main的场景,里面有一段旁白:

- scene "main"
这里是一段旁白。

如果对话特别长的话,可以使用\符号换行:

- scene "main"
这是一段特别特别长的文本,\
特别特别长~~~~~~~~~\
然后到这里就结束了。

如果需要表示一个角色,则可以编写这样的一段对话:

- scene "main"
今天也是金砂镇和平的一天呢。
杰西卡:早上好啊!苏紫。
苏紫:早上好啊,杰西卡。
说时迟,那时快,只见杰西卡将燧发枪抵在了苏紫的脑袋上。

如果你需要使用半角“:”符号,那么你可以使用空的角色名称来表示这是一段旁白:

- scene "main"
:这里是关于:符号的一个例子。
:正如你所看到的,可以使用一个“空角色”来表示对话角色,之后便可以自由使用“:”符号了。
你也可以不使用前导冒号,但是这样的话对话中就不能出现前导冒号了。
一般情况下我们可以省略前导冒号的“空角色”语法,但如果需要使用半角冒号,\
那么就应该前导冒号表示旁白,以和后面的冒号作出区分。

文本必须写在场景定义的下方,否则我们不知道这条文本到底属于哪个场景,比如以下的例子是错误的:

# 错误的例子
今天也是金砂镇核平的一天呢。

这段例子并没有定义场景,因此编译器会报错“在第1行找到了悬浮操作”。

命令

@开头可以调用一个命令,比如以下例子:

- scene "main"
@textwindow.hide		# 调用了textwindow.hide命令
@textwindow.show		# 调用了textwindow.show命令

有的命令可能会具有参数,比如以下例子:

- scene "main"
# 调用命令并传入file参数和effect参数
@bg.play --file "bg/background.png" --effect fade

有的参数名字可能会非常长,因此可以省略参数名,没有参数名的参数将会按顺序传入:

- scene "main"
@bg.play "bg/background.png" fade

或者也可以一部分参数传入参数名,另一部分不使用参数名,在这种情况下,不带参数名的参数必须放在左边:

- scene "main"
@bg.play "bg/background.png" --effect fade

有的命令的某些参数可能具有默认值,当省略时将会采用默认值传入:

- scene "main"
@bg.play "bg/background.png"	# 此命令的effect参数默认值是fade的话,可以不传入
@bg.play --file "bg/background.png"

例如以下例子,是错误的,因为带参数名的参数被放到了不带参数名的参数左边:

- scene "main"
# 错误的例子
@bg.play --file "bg/background.png" fade

与文本相同,命令也必须写在场景定义的下面。

参数的类型

参数具有以下几种类型:

  • 字符串(需要使用半角双引号包住,例如"a""super"等)
  • 实数(其中一定含有一个点号,例如1.0-12.5等)
  • 整数(一定不带点号,例如1-12等)
  • 符号(一个不被半角双引号包住的串,例如truefalseSJ等)
  • 特别地,在宏中可使用类似$"a={a}"的字符串格式化语法

如果你需要关于类型的详细信息,或者需要进行类型检查,请参见“类型检查器”一节。

文本内命令

可以在文本内嵌入命令,以在文本中执行,文本中嵌入的命令使用[]包裹:

- scene "main"
A:惊![wait --time 5]你们在做什么!
E:快把枪放下![wait 5]你们不要打了啦!

标记一段文本

可以用<>包裹一段被某个符号标记的文本,比如我们有一个符号bold表示粗体,那么我们可以作如下标记:

- scene "main"
这一段文本中,<bold 这几个字>是粗体。

标记可以嵌套,比如现在我们还有斜体标记ita,就可以写出这样的代码:

- scene "main"
这一段文本中,<bold 这几个字>是粗体,<ita 这几个字>是斜体,<bold <ita 这几个字>>是粗体斜体。

文本命令

以上文本命令将会被编译为 文本命令 ,可以通过手动调用文本命令来实现某些功能。
文本命令已经被预定义为系统外部定义,参见本章“外部命令”的部分。
关于文本命令如何使用,参见“编译”中“展开文本命令”的部分。

场景状态继承

如果一个场景,它的初始状态继承于另一个场景(比如从某个场景跳转到这里但是背景、音乐等状态不发生改变的时候),则使用scene声明后跟inherit来声明状态继承。
inherit声明仅用于编辑器创建实时预览,编译器和剧情图生成器并不关心inherit声明。


# 一些外部命令的声明
- extern select.add text target
- extern select.do
- extern goto

- macro select.add text target    # 重载select.add外部方法以在图生成器中创建连接
@__diagram_link_to target         # 指示剧情图生成器生成对应的图
@select.add text target           # 调用外部命令select.add

- macro goto target               # 同上
@__diagram_link_to target
@goto target

- scene "场景A 起始"
@select.add "选择支1" "场景A 分支1"
@select.add "选择支2" "场景A 分支2"
@select.do

- scene "场景A 分支1" inherit "场景A 起始"    # 指示编辑器,此场景起始状态为"场景A 起始"的终末状态
@goto "场景A 结束"

- scene "场景A 分支2" inherit "场景A 起始"    # 指示编辑器,此场景起始状态为"场景A 起始"的终末状态
@goto "场景A 结束"

- scene "场景A 结束" inherit "场景A 分支1"    # 指示编辑器,此场景起始状态为"场景A 分支1"的终末状态



安装编译器

使用.NET(推荐)

如果你已经安装了.NET,那么可以使用这种方式安装编译器:

dotnet tool install -g YukimiScript.CommandLineTool

如果需要将其作为独立的可执行文件使用,需要自行从源码执行dotnet publish命令。

不使用.NET

如果你没有安装.NET,那么可以使用这种方式安装编译器。
Windows平台和macOS平台为AOT编译生成的可执行文件。
Linux平台为.NET单文件发布的可执行文件。

  1. Github Release下载对操作系统的包。
  2. 将其复制到PATH目录,使其可在终端使用。
  3. 确认命令ykmc可用。

除此之外,它也可以作为独立的可执行文件使用。


编译

使用以下命令即可将单个YukimiScript编译为单个Lua文件:

ykmc ./main.ykm --target-lua ./main.lua

其中./main.ykm为待编译的YukimiScript,./main.lua则为编译生成的Lua代码。

如果需要附加额外的库,可以传入-L参数和-l参数。

ykmc ./main.ykm --target-lua ./main.lua -L../my_engine/ykmlib -lmy_engine

使用-L参数添加库搜索目录,使用-l添加要链接的库。

-L../my_engine/ykmlib -lmy_engine,将会从目录../my_engine/ykmlib中搜索libmy_engine.ykm作为库导入。

另外你也可以设置环境变量YKM_LIB_PATH来添加搜索目录而不必从-L参数指定,YKM_LIB_PATH环境变量可以用;进行分割。


外部定义

YukimiScript一般需要和其目标码写成的其他代码或宿主语言代码交互(如使用Lua作为编译目标时,需要与Lua进行交互)。
可以通过“外部定义”语法,定义来自其他语言的命令:

- extern system_call

extern定义内部不能出现任何内容,其后只能出现新的顶级定义,如以下内容是错误的:

# 错误的例子
- extern system_call
@wait 5		# 错误!extern不能包含内容

如果出现顶级定义的话是没问题的:

- extern system_call
- extern system_call2
- scene "main"
这里是场景main的内容。

外部定义可以声明参数,这些参数将会被传递给与其交互的语言编写的代码:

- extern bg.play file effect    # 定义了一个bg.play命令,它含有两个参数file和effect

也可以定义一个默认值:

- extern bg.play file effect=fade # 为effect参数定义了默认值fade
- extern bg.play2 name filename=$"{name}.png"  # 默认值也可以使用字符串格式化语法

之后便可调用这个命令:

- extern bg.play file effect=fade
- scene "main"
@bg.play "bg/background.png"

关于对外部定义的调用如何传递给目标语言,参见“目标代码”部分 。

外部定义一般在lib文件夹下,或引擎提供的额外附加库中,用于实现脚本与引擎的交互。

为了支持YukimiScript语言本身,存在一些语言预定义的系统外部定义。
如果需要集成YukimiScript,则宿主语言必须实现这些系统外部定义。

关于系统外部定义的列表,参见“系统外部定义参考”。

YukimiScript编译后的目标代码只包含对外部定义的调用。


定义宏

对于重复的代码,可以使用宏来化简,可以使用这种方式来定义宏:

- macro bg.play file effect=fade		# 名称和参数的定义同外部定义一致
@system.set_bg file				# 宏的内容 
@system.set_effect effect
@system.start_bg_transform

如果在宏中需要加入文字,则需要使用“系统外部定义参考”中“文字相关”部分的命令,不可以直接使用文本语法。

对宏的调用同外部命令一致。
宏可以调用其他宏,但不能递归,也不能多个宏之间相互递归。
宏将会在编译期被展开,形式参数会在编译期被替换为传入的实际参数。

重载系统宏和外部定义

如果宏的名称与某个系统宏或外部定义一致,则视为对那个系统宏或外部定义的重载,重载版本的参数列表不必一致。 在宏内使用这个宏的名称即可调用重载前的宏,以下是对__diagram_link_to系统宏的重载:

- macro __diagram_link_to target
@__diagram_link_to target		# 这里调用的是重载前的版本


生成剧情图

可以扫描某个文件夹下所有的YukimiScript,并将其场景及其跳转关系绘制成DGML格式存储的有向图。
对于需要生成图的场景,则需要使用__diagram_link_to命令表示这个场景可能跳转到的所有目标场景。

__diagram_link_to是一个系统宏,参见“系统宏参考”。

Examples/Diagram目录下已经有写好的关于剧情图的Demo,可使用其生成一个用于测试的剧情图。

- scene "a"
@__diagram_link_to --target "b1"
@__diagram_link_to --target "b2

- scene "b1"
@__diaram_link_to --target "b2"

- scene "main"
@__diagram_link_to --target "a"

之后对编译器调用diagram子命令即可对一个文件夹中的所有YukimiScript生成图。

ykmc diagram dgml ./my_project ./my_project.dgml --lib ~/my_engine/lib1 --lib ~/my_engine/lib2

该有向图文件可以使用Visual Studio中的DGML编辑器打开,或者使用Visual Studio Code的DGML Viewer插件打开。
同样,你也可以生成Mermaid Flowchart图文件,具体参考README.md中的“Usage部分”。

如果普通编译时需要传入lib参数,则生成剧情图时也应当传入相同的lib参数。

__diagram_link_to命令一般不被直接使用,它一般是包裹在一个跳转宏中,当用户执行跳转命令时,__diagram_link_to会被自动展开到调用处。

__diagram_link_to命令会在“展开系统宏”阶段被消除,参见“编译”。