-
Notifications
You must be signed in to change notification settings - Fork 3
目标代码
注意:目标代码不会保证更新时向前兼容,请勿依赖于目标代码内部格式进行编程。
YukimiScript 二进制文件是一个小端序RIFF文件,关于RIFF文件详见:资源交换文件格式 (RIFF)
RIFF区块的四字符识别码为RIFF
,读取YukimiScript二进制文件后即可得到此区块,此区块结构如下:
数据 | 描述 |
---|---|
RIFF |
RIFF区块的四字符识别码 |
fileType: uint32_t |
data 大小 |
data | 区块数据 |
其中data的前四字节为四字符识别码YUKI
,后面的内容由多个子区块组成,子区块均符合普通区块的格式。
这个区块中存储了UTF8编码的字符串,每个字符串后都会跟着一个\0
表示字符串结尾。
整个YukimiScript二进制文件中仅存在一个CSTR区块。
其他区块访问CSTR区块时,只会提供一个uint32_t,在该区块data开始处向后移动uint32_t个字节即可得到目标字符串的开始地址,从此地址到下一个\0
值即为当前访问的字符串。
这个区块中存储了YukimiScript中用到的外部连接,每个外部连接都占用4字节,data大小除以4即可得到EXTR区块中的元素数量。
每个EXTR区块元素都是一个字符串指针,指向CSTR区块中的一段字符串,该字符串是YukimiScript外部定义的名称。
整个YukimiScript二进制文件中仅存在一个EXTR区块。
这个区块中存储了YukimiScript中的一个场景。
YukimiScript二进制文件中可能存在多个SCEN区块。
data的前四个字节为CSTR指针,指向CSTR区块中该场景的名称,后续为命令列表。
命令列表中存在多个命令,其中每个命令以如下方式存储:
数据 | 描述 |
---|---|
uint16_t | 所调用的命令在EXTR区块中的编号N(即EXTR区块中的data + 4 * N ) |
uint16_t | 参数数量 |
... | 多个参数紧密排列 |
而对于参数则是以下方式存储:
一个参数由4或8字节组成,前四个字节以int32_t读取,并按照以下情况进行处理:
- 如果为0,则该参数为8字节,后四个字节为int32_t型的int类型参数。
- 如果为1,则该参数为8字节,后四个字节为float32_t型的real类型参数。
- 如果大于等于2,则该参数为4字节,当前读到的值减去2即可得到指向CSTR的字符串指针,得到字符串参数。
- 如果小于等于-1,则该参数为4字节,当前读到的值加上1后取绝对值即可得到只想CSTR的字符串指针,得到Symbol参数。
对以下YukimiScript代码进行编译:
- extern Hello a b c
- extern World a b c
- scene "main"
@Hello 1 2 "World"
@World 1 2 "World"
- scene "foo"
@Hello 1.0 2.0 true
可得到以下格式的字节码:
数据 | 描述 |
---|---|
RIFF |
RIFF区块的四字符识别码 |
148: uint32_t | RIFF区块的data大小 |
YUKI |
RIFF的data部分的前四字节,为一个四字符识别码,表明为一个YukimiScript二进制文件 |
CSTR |
CSTR区块的四字符识别码 |
28: uint32_t | CSTR区块中data的大小 |
Hello\0 |
字符串Hello |
World\0 |
字符串World |
main\0 |
字符串main |
foo\0 |
字符串foo |
true\0\0\0 |
字符串true,CSTR区块到此结束,额外追加的两个\0 是需要将CSTR区块大小对齐到4字节。 |
EXTR |
EXTR区块的四字符识别码 |
4: uint32_t | EXTR区块data部分的大小 |
0: uint32_t | Hello外部定义,0为指向其名称"Hello"的CSTR指针 |
6: uint32_t | World外部定义,6为指向其名称"World"的CSTR指针 |
SCEN |
SCEN区块的四字符识别码 |
52: uint32_t | 该区块data部分的大小 |
12: uint32_t | 该区块代表的Scene的名称,即指向main 的CSTR指针 |
0: uint16_t | 调用第0个外部定义,即Hello
|
3: uint16_t | 传递了三个参数 |
0: int32_t | 第一个参数为int32_t |
1: int32_t | 参数1
|
0: int32_t | 第二个参数为int32_t |
2: int32_t | 参数2
|
8: int32_t | 第三个参数大于等于2,为字符串,减去2后得到6,即为指向CSTR的指针,得到字符串“World”。 |
1: uint16_t | 调用了第1个外部定义,即World
|
3: uint16_t | 传递了三个参数 |
0: int32_t | 第一个参数为int32_t |
1: int32_t | 参数1
|
0: int32_t | 第二个参数为int32_t |
2: int32_t | 参数2
|
8: int32_t | 第三个参数大于等于2,为字符串,减去2后得到6,即为指向CSTR的指针,得到字符串“World”。 |
SCEN |
SCEN区块的四字符识别码 |
28: uint32_t | 此SCEN区块的data大小 |
17: uint32_t | 该区块代表的Scene的名称,即指向foo 的CSTR指针 |
0: uint16_t | 调用了外部定义Hello
|
3: uint16_t | 传递了三个参数 |
1: uint32_t | 第一个参数为一个float |
1.0: float32_t | 参数1.0
|
1: uint32_t | 第二个参数为一个float |
2.0: float32_t | 参数2.0
|
-22: uint32_t | 第三个参数为一个symbol,加上1后取绝对值得21,即为指向CSTR区块中字符串“true”的指针。 |
编译生成的Lua文件,将会以UTF-8编码,具有以下基本结构:
return function(api) {
["Scene1"] = {
function() --[[操作]] end,
function() --[[操作]] end,
function() --[[操作]] end,
},
["Scene2"] = {
},
["Scene3"] = {
},
-- 后面存放更多的场景
} end
其中api
参数是一个表,里面存放了YukimiScript所需要使用的外部定义和Symbols,后文中称作“api表”。
可以使用Lua的require
函数来加载这个文件。
推荐发布时利用构建工具在编译出Lua代码后,再将其编译为Lua字节码形式以降低加载时间。
以下特殊的Symbol将会特殊处理:
-
true
和false
会直接按照true
和false
进行传递 -
null
会被替换为nil
-
nil
会直接按照nil
进行传递
尽管Lua的Codegen会把nil
原样处理,但是建议在YukimiScript中使用null
而不是用nil
,以保证在其他目标的代码生成器中不会出现行为不一致的问题。
除了以上特殊的Symbol以外,其他的Symbol将会从传入的api
表中搜索,如a
会被替换为api.a
。
如果Symbol中存在.
,也依然会按照上面的方式处理,如a.b
会被替换为api.a.b
。
对于外部定义命令,也会从api
表中搜索,但是会把最后一个.
替换为:
,以传入self参数。
如__system_jump
,会被替换为api:__system_jump
。
如bg.play
,会被替换为api.bg:play
。
外部定义命令的实现除了要加入self
参数外,其余参数的顺序需要和YukimiScript中的参数定义一致,例如以下YukimiScript中定义的外部命令:
- extern bg.play fileName effect
则需要定义如下实现:
api = {} -- 这里是api表
api.bg = {} -- 需要在api表中定义bg表,以便于在bg表中查找play命令的实现
function api.bg.play(self, fileName, effect)
--[[bg.play命令的实现]]
end
如果YukimiScript中编写了如下代码:
- scene "main"
bg.play --effect fade --fileName "a.png"
将会生成以下的Lua代码:
return function(api) {
["main"] = {
function() api.bg:play("a.png", api.fade) end
},
} end
PyMO代码生成器需要依赖于libpymo,你可以在代码仓库的根目录中找到它,它用于访问PyMO引擎的各种功能。
在编译YukimiScript代码时,需要将其放到lib中。
关于YukimiScript中Scene编译到PyMO的过程将会进行以下处理:
- YukimiScript单个源文件中允许存在一个$init场景,用于在PyMO启动该文件时首先执行这段代码。
- YukimiScript中和源文件名相同的场景(如
a.ykm
中的场景a
),将会被强制作为第一个场景使用,并且该场景必须存在。 - 除了以上两个场景,其它场景将会被按照YukimiScript中定义的顺序编译到PyMO代码。
libpymo中没有say
命令,你可以直接使用YukimiScript中的文本语法。
如果你需要使用角色,那么你可以使用__define_character
命令告知代码生成器将某个角色名设置到某个symbol上,之后便可使用这个标记作为角色名,如:
- scene "main"
@__define_character a "角色A"
a:角色A的对话。
将会生成以下代码:
#label main
#say 角色A,角色A的对话。
在某个场景内定义的内容只能在本场景内使用,如果你需要让整个脚本都可用,那么你需要在$init
场景中定义角色。
如果你需要让所有脚本均可用,那么你可能需要在lib中定义$init
场景。