TJS2和KAG

 TJS ( TJS2 ) 是能让吉里吉里引擎直接解析执行的脚本语言,和JavaScript、JAVA 很相似。整个 KAG 就是用 TJS 脚本编写的。
 TJS 脚本能编写出比 KAG 中执行的 ( 抽象的 ) scenario 更接近引擎的 ( 具体的 ) 代码,虽然使用上变得更困难些,但是可以控制的内容却更广泛。

 KAG 中有很多需要指定「TJS表达式」的场合,比如 eval emb link if 等tag的 exp 参数,各个tag的 cond 参数、entity ( 带 & 的tag的参数 ) 等等。
 通过使用TJS表达式,可以更方便地操作吉里吉里引擎中那些隐藏在 KAG 内部的功能。
 还可以使用 iscript 这个tag来直接执行 TJS2 脚本。利用这一点,我们可以执行更加复杂的逻辑,或者对KAG的機能进行扩展等等。

 TJS2 的详细的语法请参考 TJS2 的文档,有关吉里吉里引擎提供的功能(內建的类等等)请参考吉里吉里2的文档。

KAG内的各个对象的结构

 因为 KAG 本身是使用 TJS2 脚本编写的,( 不管是否合适 ) 我们可以用 TJS2 直接操作 KAG 的内部结构。
 将 KAG 内部的变量带入到scenario中可以编写出高灵活性的scenario脚本,直接修改 KAG 所管理的对象的变量的值之类的操作则需要谨慎行事(译者注:这么做有可能影响KAG的正常执行,所以要谨慎小心,但是很多时候值得我们这么做。因为只要小心一些就不会出问题,而这么做的好处可是大大的)。

KAGWindow 类的对象
 KAGWindow 类 ( 定义在 MainWindow.tjs 中 ) 负责管理KAG的游戏主窗口,可以使用全局变量 kag 来操作这个对象。
 比如说、KAGWindow 类的 skipMode 变量 ( 记录了現在处于哪一种快进模式的变量 ) 可以用 kag.skipMode 来操作。
背景图层
 背景图层是 BaseLayer 类 ( 定义在 GraphicLayer.tjs 中 ) 的对象。
 表画面的背景图层可以用 kag.fore.base、裏画面的背景图层可以用 kag.back.base 来操作。
前景图层
 前景图层是 CharacterLayer 类 ( 定义在 GraphicLayer.tjs 中 ) 的对象。
 表画面的前景图层可以用 kag.fore.layers[n]、裏画面的前景图层可以用 kag.back.layers[n] 来操作 ( n 是前景图层的序号 0 〜 )。
文字层
 文字层是 MessageLayer 类 ( 定义在 MessageLayer.tjs 中 ) 的对象。
 表画面的文字层可以用 kag.fore.messages[n]、裏画面的文字层可以用 kag.back.messages[n] 来操作 ( n 是文字层的序号 0 〜 )。
 kag.current 代表当前正在使用的文字层。
文字层内的对象
 操作文字层内创建的图片按钮、编辑框、复选框等对象可以使用文字层对象中的 links 成员变量。
 links 是个数组,选项、图片按钮、编辑框、复选框等对象都按照它们创建的顺序,把管理它们的对象的引用存放到了这个数组里。其中,对图片按钮、编辑框、复选框来说,可以用links中对应的序号的对象中的 object 变量来对它们的对象进行直接的操作。
 比如说,对于表画面的0号文字层(message0)写了下面的脚本的话,

@cm
@edit length=420 name="f.name"

 对这个编辑框设置输入焦点(让这个编辑框能接受键盘的按键输入)的话,可以

@eval exp="kag.fore.messages[0].links[0].object.focus()"

 这么写 ( 显示编辑框并催促玩家输入内容时会很方便 )。
音效缓冲
 音效缓冲是 SESoundBuffer 类 ( 定义在 SE.tjs 中 ) 的对象。
 可以用 kag.se[n] 来操作 ( n 是音效缓冲的序号 0 〜 )。
BGM 对象
 BGM 对象是 BGM 类 ( 定义在 BGM.tjs 中 ) 的对象。
 可以用 kag.bgm 来操作。
菜单
 菜单对象可以用 kag.menu 来操作。kag.menu 是 MenuItem 类的对象, kag.menu 本身会在游戏主窗口中显示一个菜单栏,作为其下属而登录的菜单项会排列显示在菜单栏中。
 虽然菜单中的各个菜单项是在 Menus.tjs 中创建的,但是如果通过直接修改 Menus.tjs 来改变菜单项的内容的话 在KAG系统升级的时候还要每次都在修改一便,用后面要提到的 编写 AfterInit.tjs 并把要修改的内容写进去的方式会更方便。

使用 TJS 时的注意事项

 即使手动向 KAG 的存档数据中加入一些不会被保存到存档文件中的内容,在 KAG 读取存档的时候这些内容也不会再现出来。
 如果利用 KAG 插件的 onStore 和 onRestore 函数(存档/读档时由KAG系统以hook方式自动调用)来把数据保存到存档中的话当然没问题,但是除此以外的场合就要注意了。
 尤其是在使用 Layer 类中的图像复制/描绘之类的函数来修改 KAG 管理的背景图层,前景图层等的内容的地方。KAG 能记录图层中加载了什么图像文件,但是却不记录那些在图层上作的后续的绘制和和修改。这样一来,那种状态下通过「存档可能的标签」,然后存档,再读这个存档的时候,对这个图层所做的修改是不会再现的。
 遇到这种情况的地方,可以在下一个「存档可能的标签」之前清除图层中的图像或者读入其他图像文件来让图层恢复到能被 KAG 管理的状态,或者在对图层作出修改的过程中不写「存档可能的标签」,这样就能回避这个问题。
 使用 TJS 的时候,请特别注意和存档有关的地方。

表达式的运算、条件判断、显示中的应用

&&||
 这两个在TJS中是运算符,&& 代表「与」运算,|| 代表「或」运算。
 例如,想得到 f.flag1 的值是 1 ,并且,f.flag2 的值是 2 这个条件的真假,可以

[if exp="f.flag1==1 && f.flag2==1"]

 这样写。
 再比如,f.flag1 的值是 1 或者 2 或者 3 这个条件,则可以

[if exp="f.flag1==1 || f.flag1==2 || f.flag1==3"]

 这样写 ( 如果 f.flag1 是整数,也可以 f.flag1>=1 && f.flag1<=3 这样写 )。
 就像一般的表达式中乘法的优先级比加法高一样,&&|| 的优先级也不一样,&& 的优先级更高。
 这样一来,如果想计算 f.flag1 的值是 1 ,并且,f.flag2 的值是 3 或者 5 的话,必须像这样

[if exp="f.flag1==1 && (f.flag2==3 || f.flag2==5)"]

 使用小括号来调整优先级。
randomintrandom
 random 会返回大于等于 0 且小于 1 的实数型随机数。


例:
@eval exp="f.ransuu = random"


 像上面这样写的话,f.ransuu 中会被赋值为大于等于 0 且小于 1 的实数型随机数。

 相对的,intrandom 是返回指定的最大值和最小值之间的整数型随机数的函数。

格式 : intrandom(最小値, 最大値)


例:
@eval exp="f.ransuu = intrandom(0, 5)"


 像上面这样写的话,大于等于 0 且小于 5 的整数型随机数会赋值到 f.ransuu 中。
length
 length 能返回字符串的长度。使用方法为 已经代入字符串值的变量的后面写上 . (小数点) ,接着写上 length 。


例:
[if exp="f.namae.length>=8"]名字太长了。[l][jump target=*input][endif]


 上面的例子中,f.namae 的长度大于或等于 8 的时候显示「名字太长了。」,并跳到 *input 标签。
 字符不分全角半角,计算长度的时候都按一个记数。其他的操作字符串的功能中也是这样。
substring
 substring 用来切取字符串中的一部分。
 使用方法为 已经代入字符串值的变量 ( 或者代表字符串的其他东西 ) 的后面写上 . (小数点) ,接着写

substring(切取的起始位置, 切取的长度)

 这样的格式。切取的起始位置为 0 的话代表从字符串的开头开始。

 例如,想要获得 f.furigana 变量中的第二个字符的话,f.furigana.substring(1, 1) 这样写就可以了。


例:
@emb exp="f.furigana.substring(1, 1)"


 上面的列子会在当前文字层中显示 f.furigana 变量中的第二个字符。
indexOf
 indexOf 用来查找字符串中指定的“字符串的一部分内容”最开始出现的位置。可以用来查找某个字符串中是否包含其它的某个字符串。

格式 : 字符串.indexOf(部分字符串)

 例如,字符串为 "ABCDEFGHIJKL" ,要查找的部分字符串为 "ABC" 的话,"ABCDEFGHIJKL".indexOf("ABC") 会返回 0 。要查找的部分字符串为 "BCD" 则返回 1,为"DEF" 的话则返回 3 。  如果要查找的内容在字符串中没有出现的话,则会返回 -1 。想判断某个字符串是不是另一个字符串的一部分的话只要用这个函数的返回值与 -1 比较就行了。


例:
[if exp="'尼屁尻'.indexOf(f.objname)!=-1"]〜〜[endif]


 上面的例子中,如果 f.objname 的值是 "尼" "屁" "尻" "尼屁" "屁尻" "尼屁尻" 中的任意一个的话,就会执行截止到 endif 的那段代码。
 如果想排除 "尼屁" "屁尻" "尼屁尻" (只想承认 "尼" "屁" "尻" 这三种情况 ) 的话,可以把 '尼屁尻'中的各个字符用 f.objname 中不会出现的字符(或者记号) 隔开来实现这个效果。
 例如,用 \v 这个特殊的控制符号来分隔,把上面的例子改写成

[if exp="'尼\v屁\v尻'.indexOf(f.objname)!=-1"]〜〜[endif]
这样就可以了 ( 因为通常情况下,\v 是不会在 f.objname 里出现的 )。

 下面的例子中,f.itemname 中包含 'コップ' 这个字符串的话,会执行截止到 endif 的那段代码。


例:
[if exp="f.itemname.indexOf('コップ')!=-1"]〜〜[endif]

正则表达式
 使用“匹配模式” ( 被 / 和 / 包围的部分 ) ,根据匹配模式对指定的字符串进行分解或検査。
 “匹配模式”本身和 Perl の的正则表达式很相似 ( 虽然使用方法不同,但是正则表达式的语法基本上是互换的 )。
 译者注:“匹配模式”其实就是直接写在代码里的正则表达式常量,原文为“正規表現パターン”,这里这么翻译是因为更容易组织语言。。。某日语水平不济啊。。。

 检测一个字符串是否符合指定的模式可以用 test 函数。


例:
[if exp="/[^0-9]/.test(f.nyuryoku)"]输入的字符中包含数字以外的内容[endif]


 test 函数的调用方法请参考上面的例子。test 是在被检测的字符串与模式相匹配的时候返回真,不匹配时返回假的函数(正则表达式对象的成员函数)。在上面的例子中,使用 [^0-9] ,也就是检测是否包含数字以外的字符的匹配模式,来对 f.nyuryoku 中是否包含数字以外的字符进行检查。

 字符串分解则使用 match 函数。match 会返回数组对象。和指定模式不匹配的话,数组中的元素个数 ( count ) 是 0。除此以外,数组中的第一个元素(序号为0) 是所有相匹配的内容的总和、从序号为 1 的元素开始是与匹配模式中的各个 ( ) (括号) 指定的部分相匹配的内容。


例:
[eval exp="f.matched = /([0-90-9]+)[-−]([0-90-9]+)/.match(f.input)"]
[if exp="f.matched.count == 0"]请按照「数値-数値」的格式来输入。[jump target=*input][endif]
[eval exp="f.s1 = str2num(f.matched[1]), f.s2 = str2num(f.matched[2])"]

 在上面的例子中,检测 f.input 是否符合「数値-数値」的格式,符合的话,- (短横线) 的前面的部分会转换为数值后代入到 f.s1 中,后面的部分会转换为数值后代入到 f.s2 中。
str2num
 str2num 用来把字符串转换为数值。

格式 : str2num(字符串或代入了字符串的变量)

 和 + 的区别在于,str2num 能把全角数字也转换为字符串。可以用在像 input 指令这种用户有可能以全角模式输入数字的地方。传入无法识别为数字的字符串的时候会返回0。


例:
[input name="f.kazu" prompt="请输入数字"][emb exp="f.kazu=str2num(f.kazu)"]

kansuujikansuuji_simple
 kansuuji 用来把给定的数值转换为汉字型的格式。kansuuji_simple 也是一样,只是结果中不会包含位值。
 9223372036854775807 这个数值用 kansuuji 函数会转换为 "九百二十二京三千三百七十二兆三百六十八億五千四百七十七万五千八百七" ,用 kansuuji_simple 函数则转换为 "九二二三三七二〇三六八五四七七五八〇七" 。

例:
@emb exp="kansuuji(f.num)"

 在上面的例子中,会在游戏中显示 f.num 转换为汉字型之后的值。
number_format
 number_format 函数会将给定的数值每三个数字一组使用 , (逗号) 隔开来表示。例如,9223372036854775807 这个数值会转换为 "9,223,372,036,854,775,807" 。

例:
@emb exp="number_format(f.num)"

 上面的例子中,f.num 中的数字会以每三位一组用逗号分隔的形式显示在游戏中。
Storages.addAutoPathSystem.exePath
 Storages.addAutoPath 用来添加自动搜索的文件路径。
 System.exePath 中包含吉里吉里引擎文件(exe)所在的文件夹。
 用这些可以把文件夹和文件包(xp3)设置为自动搜索路径,详细内容请参阅吉里吉里 SDK Help 。
 自动搜索路径是为了不特意指定所在文件夹,也能找到指定的文件的机能(原文为"仕組み")。默认会包含 system image scenario bgimage fgimage bgm sound rule others video 这些路径,也可以用 Storages.addAutoPath 来添加新路径。
System.exePath 则是吉里吉里引擎文件(exe)所在的文件夹。

 例如,在吉里吉里引擎文件所在的路径下有名为 cgdata 的文件夹,想把这个文件夹指定为自动搜索路径的话,

[eval exp="Storages.addAutoPath(System.exePath + 'cgdata/')"]

 就这么写 ( cgdata 后面的 / 是必须要加的 )。

 如果吉里吉里引擎文件的相同目录下有名为 cgdata.xp3 的文件包,想把这个文件包指定为自动搜索路径的话,

[eval exp="Storages.addAutoPath(System.exePath + 'cgdata.xp3>')"]

 可以像上面这么写。cgdata.xp3 后面的符号是 '>' 。在文件包里指定自动搜索路径的时候用 > ,在文件夹内指定的时候则用 / 。
 文件包后面的符号从 吉里吉里2 2.19 beta 14 开始由 '#' 变更为 '>' 。
Storages.searchCD
 Storages.searchCD 会返回具有由参数指定的卷标的CD所在的驱动器的驱动器名。
 例如,和上面介绍的 Storages.addAutoPath 配合使用,为了把带有 FOO_BAR_DISC 卷标的CD CD-ROM 中的 image 文件夹添加到自动搜索路径中,

[eval exp="Storages.addAutoPath(Storages.searchCD('FOO_BAR_DISC') + ':image/')"]

 可以像上面这样写。

 Stotages.searchCD 在没有找到带有指定的卷标的驱动器的时候会返回空字符串 利用这一点,比如为了确认指定的 CD-ROM 是否插入驱动器,

[if exp="Storages.searchCD('FOO_BAR_DISC') == ''"]没有插入CD[endif]

 可以像上面这样写。
System.readRegValue
 用 System.readRegValue 可以把记录在注册表中的读出来。例如,将 HKEY_LOCAL_MACHINE\SOFTWARE\Dee\kirikiri\installpath 的值读到变量 f.installpath 中,可以这么写

[eval exp="f.installpath = System.readRegValue('HKEY_LOCAL_MACHINE\\SOFTWARE\\Dee\\kirikiri\\installpath')"]
 务必要注意,在 '' 之间的内容里 \ 必须要要写成 \\ 。
 只能读入字符串和数值类型的数据。如果在注册表里没有这个值会返回 void ,请使用=== (类型识别比较运算符) 来判断这一情况

[if exp="f.installpath === void"]尚未安装[endif]

 比如说像上面的例子这样写。
kag.clickCount
 在游戏画面中用鼠标每单击一下,这个变量的值就会加 1 。因为这个变量也可以被赋值,如果将其设定为 0 的话,在鼠标单击之后,就可以通过该变量的值不再是0来得知鼠标单击过了。
kag.lastMouseDownXkag.lastMouseDownY
 这两个变量是鼠标最后一次单击时的坐标。kag.lastMouseDownX 是最后一次单击时的横坐标,kag.lastMouseDownY 是最后一次单击时的纵坐标。
kag.lastWaitTime
 wait 在以 mode=until 方式使用的时候,wait 实际上需要等待的时间会被保存到这个变量里。如果想要等待的时刻已经过了的话这个值就是0,可以根据 wait tag之后的这个变量的值是否为 0 来决定是否进行某些处理。
 顺便补充一点,如果因为鼠标单击等情况而使得 wait 被中断的时候,这个变量的值就不再准确的表示实际wait指令等待的时间了 ( 而是表示没有被中断的情况下有的等待时间 )。
kag.skipMode
 这个变量中保存了代表当前的skip模式的值。0=不跳过,1=跳过到点击等待提示符, 2=跳过到换页指令, 3=直到下一个停止指令。
 例如,如果想让语音、音效等在skip中不再播放,

@playse cond="kag.skipMode<=1" storage="hogehoeg.wav"

 可以像上面的例子这样写。
kag.autoMode
 这个变量在游戏处于自动读进状态的时候为真,不处于自动读进状态的时候为假。
 例如,如果只想在自动读进模式的时候进行语音或音效播放结束的等待操作的话

@ws cond="kag.autoMode"

 可以像上面的例子这样写。
kag.getBookMarkPageName
 kag.getBookMarkPageName 函数能够在游戏使用非自由存档模式(不能指定存档文件的文件名,而是自动以数字序号为文件名)的情况下获取指定序号的存档中的标签名称。
 在想把存档信息显示在游戏画面中来让玩家选择自己的进度而不是从KAG的系统菜单中来操作的话,可以利用这一功能。
 通常和 kag.restoreBookMark 相配合来使用。

例:
[locate x=10 y=100][link exp="kag.restoreBookMark(0)"][emb exp="kag.getBookMarkPageName(0)"][endlink]
[locate x=10 y=130][link exp="kag.restoreBookMark(1)"][emb exp="kag.getBookMarkPageName(1)"][endlink]
(以下同様)

mp
 mp 变量在宏定义中指向传入宏指令中的参数的字典(只在宏定义代码中有效)。

例:
@macro name=fimg
@image *
@eval exp="sf[mp.storage]=1"
@endmacro

 例如,向上面的例子那样定义一个宏,然后 @fimg layer=base page=fore storage="bg_03" 这样写的话,在这个宏指令被执行的过程中 mp.layer 的值为 'base'mp.page 的值为 'fore''mp.storage' 的值为 'bg_03' 。也就是说,传给宏指令的参数的参数名写在 mp. 后面,就可以获得那个属性的值。
 这个宏用 @fimg layer=base page=fore storage="bg_03" 这个代码调用的话,会通过 eval 这个指令 来执行 sf[mp.storage]=1 这段代码,从而让 sf['bg_03'] 的值变成 1 。
 用这个宏代替 image/img 指令就可以实现显示图像的同时自动将图像文件名记录到系统变量中的功能。
System.getKeyState
 System.getKeyState 可以用来判断当前时刻指定的某个按键是否被按下。

例:
@jump target=*shift_key_pressed cond="System.getKeyState(VK_SHIFT)"
; 如果按下了shift键,就跳转到 *shift_key_pressed 标签

详细说明请参考 吉里吉里2 SDK Help 。

 KAG3可以支持用游戏手柄(joystick)来操作,但是当手柄上压着东西或者手柄的小摇杆的角度没有调整好的话,可能会导致无法正常操作。
 当游戏开始时如果手柄的按键是被按下的,可以给玩家发出提醒 (因为通常在游戏开始的时候,手柄按键不是玩家有意按下,而是因为一些无意的原因而被压着不放的可能性更高)。
 USB接口之类的的游戏手柄使用下面的方法有可能无法很好的检测出按键被压住不放的情况,建议在游戏文档中做出说明。

例:
@if exp="System.getKeyState(VK_PADANY)"
@wait time=500
@if exp="System.getKeyState(VK_PADANY)"
; 将 VK_PADANY 作为参数的话,在手柄的任意一个按键被按下的时候会返回真
; 经过500ms(0.5秒之内)仍然有按键被按下的话就显示提示信息
游戏手柄(joystick)的按键正处于按下的状态。
请确认游戏手柄上是否放了什么东西,或者手柄的小摇杆的角度是否已调整好。
如果这种情况依然存在,请拔掉游戏手柄。
如果拔掉游戏手柄之后情况仍然存在,请退出游戏,然后打开「引擎设定」程序,
将「是否使用游戏手柄」这个选项设定为「不使用」。
[s]
@endif
@endif

关于link、button之类指令中的exp参数指定的内容

System.shellExecute
 System.shellExecute 用来打开参数中指定的文件。在参数中指定 URL 的话则会打开浏览器,使用link 之类的指令执行这个函数就可以实现指向Web页面的链接。


例:
[link exp="System.shellExecute('http://www.yahoo.co.jp/')"]http://www.yahoo.co.jp/[endlink]

kag.closekag.shutdown
 kag.close 的作用是关闭 KAG 。如果设置了关闭时进行确认的话,就会在关闭之前向玩家确认是否关闭。
 kag.shutdown 也是关闭 KAG ,但是不会进行确认。
 如果使用 System.exit() 来关闭KAG的话,system变量有可能或无法保存,所以请不要这么用。也不要把这些函数指定到 eval 指令的 exp 参数来关闭 KAG (请使用 close 指令来代替起)。


例:
[link exp="kag.close()"]退出[endlink]
[link exp="kag.shutdown()"]退出[endlink]

kag.restoreBookMarkkag.storeBookMark
 kag.restoreBookMark 用于在非自由存档模式(不能指定存档文件的文件名,而是自动以数字序号为文件名)读取由参数制定的序号的存档。
 同样的,kag.storeBookMark 用于保存由参数指定的序号的存档。
 只是,直接调用这些函数的话,即使用[store]指令禁用了存档操作,这些函数也会起作用。
 这两个函数在执行成功的时候返回真失败的时候返回假。
 事例请参照 kag.getBookMarkPageName 的说明。
kag.loadBookMarkFromFileWithAskkag.saveBookMarkToFileWithAsk
 kag.loadBookMarkFromFileWithAsk 用于在自由存档模式下打开文件选择对话框让玩家选择存档文件。在玩家点击OK按钮时读取选中的存档。
 同样的、kag.saveBookMarkToFileWithAsk 函数用于打开文件选择对话框,将当前状态保存到指定的存档中。
 这两个函数在存档、读档成功时返回真,在玩家点了取消或者执行失败的时候返回假。

例:
[link exp="kag.loadBookMarkFromFileWithAsk()"]读取存档[endlink]
[link exp="kag.saveBookMarkToFileWithAsk()"]保存存档[endlink]

kag.callExtraConductor
 kag.callExtraConductor 函数用来把KAG脚本作为SubRoutine由tjs调用。用这个函数调用KAG脚本的时候,当前正在执行的脚本必须处于停止状态(l、p指令的等待点击状态或者s指令的停止执行 用 kag.inStable 或者 KAG 插件类的 onStableStateChanged 函数可以得知脚本执行是否处于这种状态 )。
 kag.callExtraConductor 有3个参数。
 第一个参数是要调用的脚本文件名。第二个参数是要调用的标签。
 第三个参数用来指定在脚本执行完毕返回的时候执行的函数或者成员函数,可以省略。不需要的话不写没关系。


例:
[button graphic="showhist" exp="kag.callExtraConductor('rclick.ks', '*showhist')"]


 SubRoutine的编写方法请参照右键菜单的说明。
 在右键菜单开启过程中,或者使用这个函数调用的KAG脚本正在执行的过程中不能再次使用这个函数。(这种情况下可以用kag.processCall)
kag.se[n].play
 音效buffer的 play 成员函数用于播放音效。
 用下面的格式来调用。

 
kag.se[音效buffer编号].play(%[storage: 想要播放的音效的文件名, loop: 是否循环播放]);

 例如,像下面的例子中那样在 link 指令的 onenter 参数中指定这个函数的话,就可以实现在鼠标指针移动到选项上的时候播放音效。(现在因为link中有了enterse和entersebuf参数,就用不着这么费事了,直接用这两个参数就能实现这效果)


例:
[link target=*foo onenter="kag.se[0].play(%[storage:'select.wav', loop: false])"]选项〜[endlink]


 在这个例子中是用0号音效buffer来播放 select.wav ,不做循环播放。其他想要用tjs来控制音效播放的地方也可以很方比阿德用这个方法实现。

数组

 吉里吉里2/KAG3 中可以用很简单的方式来使用数组。
 使用数组的地方必须先用 [ ] 来定义一个数组。


例:
[eval exp="f.hairetsu = []"]


 在上面的例子中定义了一个数组并代入到 f.hairetsu 这个变量中,以后就可以把这个变量当作数组来使用。要注意如果 f.hairetsu 这个变量在代入一个数组之后又用其他的树枝或者字符串之类的赋了值, f.hairetsu 中原来的数组中的内容就会被消除。
 想在系统变量中使用数组的话,可以把sf中的所有变量的默认值都看作是void,然后


例:
[eval exp="sf.hairetsu = [] if sf.hairetsu === void"]


 像上面这样写,这样的话就会只在第一次启动时定义这个数组。第二次及以后的启动不会重新定义数组并赋值,也不会让原来的数组里的内容消失。

 使用 [ ] 来向数组中的元素赋值。[ ] 中写上下标 ( 元素序号 ) 。下标从0开始。


例:
[eval exp="f.hairetsu[0] = 'zero', f.hairetsu[1] = 'one'"]


 在上面的例子中 f.hairetsu[0] 中被代入 'zero' ,f.hairetsu[1] 中被代入 'one' 。
 数组的长度不需要定义。在使用中会自动增长到需要的大小。使用 count 属性可以获取和设置数组的长度,就像 f.hairetsu.count 这样写。

 把数组中的元素拿出来显示也一样是用 [ ] 。具体见下面的例子。


例:
0 : [emb exp="f.hairetsu[0]"]    1 : [emb exp="f.hairetsu[1]"]


 二维数组用起来稍微复杂一些,这里只举一个例子。

@iscript
// 生成第一维长度为5的二维数组
f.twodim = [] if f.twodim === void; // 利用 twodim 变量创建第一维的数组
for(var i = 0; i < 5; i++) f.twodim[i] = [] if f.twodim[i] === void;
// 到这里的时候 f.twodim[0] 到 f.twodim[4] 这些元素也都变成数组了
// 可以用 f.twodim[0][3] 或 f.twodim[4][2] 这种方式来读写了
@endscript

// 或者,如果单纯的创建一个第一维长度为5的二维数组的话
f.twodim = [ [], [], [], [], [] ];
// ( 数组在用 [] 定义的时候,可以在 [] 之间使用逗号间隔来指定默认元素
//   这时候将数组作为外层数组的元素来指定 )

字典

 在 吉里吉里2/KAG3 中也是可以使用字典这种好东西的。
 字典 ( 也可以叫做关联数组 ) 是一种可以将名字和名字所对应的值成组的保存起来的数组。(其实,字典是TJS,乃至整个Kr的基础,就算说“一切皆为字典”也不为过。有兴趣的同学可以去看Kr的源代码,从Kr官网的SVN里可以弄到)
 和数组一样,在使用字典之前也必须用 %[ ] 来定义。


例:
[eval exp="f.dict = %[]"]


 在上面的例子中定义了一个字典并代入到 f.dict 这个变量中,以后就可以把这个变量当作字典来使用。在 f.dict 变量已经代入了字典对象之后的注意事项和数组相同,请参照数组的相关说明。

 向字典中装入数据也是使用 [ ] 运算符 ( 注意,不是 %[ ] )。把名/值对 中的名字写在 [ ] 中。


例:
[eval exp="f.dict['zero'] = 0, f.dict['one'] = 1"]


 在上面的例子中, f.dict['zero'] 中被代入 0f.dict['one'] 中被代入 1 。与普通的数组的区别是 [ ] 中写的是字符串。

 显示其中的数据也一样是用 [ ] 。具体见下面的例子。


例:
zero : [emb exp="f.dict['zero']"]    one : [emb exp="f.dict['one']"]


 顺便说一下 使用 . 来代替 [ ] 也是可以的。f.dict['zero'] 也可以写成 f.dict.zerof.dict['one'] 也可以写成 f.dict.one ( 但是,. 的后面不能写属于“关键字”或者无法作为变量名的名字,而 [ ] 则没有这个限制)。

 KAG 中的 fsf 本身其实也是字典,f.dict 这么写就是在操作字典中 'dict' 这个名字对应的值 ( 当然,f['dict'] 这么写也是一样的 )。

获取日期/时间

 下面的例子能够获得当前的日期和时间。

[iscript]
{
    // ↑  iscript 和 endscript 之间的代码用 {  } 包围起来是为了让这段代码中定义的变量
    // 成为局部变量 ( 否则会成为全局变量 )
    var d = new Date(); // 创建 Date 类的对象
    // Date 类的对象在创建时如果不指定任何参数的话
    // 就会将对象内保存的时间设置为对象创建时的时间
    f.year = d.getYear();  // f.year 中代入年份
    f.month = d.getMonth() + 1; // f.month 中代入月份
    f.date = d.getDate(); // f.date 中代入日期
    f.hours = d.getHours(); // f.hours 中代入小时
    f.minutes = d.getMinutes(); // f.minutes 中代入分
    f.seconds = d.getSeconds(); // f.seconds 中代入秒
}
[endscript]

process

 kag.process 用来让脚本从指定的地方开始执行。
 第一个参数是要读入的脚本文件名。指定空字符串的话就使用当前的脚本。
 第二个参数是开始执行的标签,指定空字符串的话就从脚本的开头开始执行。


例:
kag.process('', '*label2')
kag.process('scenario4.ks', '*label5')


 要注意的是,即使脚本正在执行中,调用了这个函数也会直接跳到指定的标签。

leftClickHook, rightClickHook, keyDownHook

 KAG 具备在鼠标左键被按下,鼠标右键被按下以及键盘上的按键被按下的时候调用已经注册了的特定函数的功能,这被称为“钩子”(hook)。
 为了注册多个函数,钩子被设计成了数组。鼠标左键,右键和键盘分别对应 kag.leftClickHookkag.rightClickHookkag.keyDownHook 这三个数组。只要把函数装到这些数组里,就能在对应的鼠标或键盘的按键被按下的时候自动调用这些函数。
 如果在注册到钩子数组中的函数返回 true 的话,KAG 中定义的这些按键拥有的功能就不会继续执行了。例如,R 键被按下的时候,keyDownHook 中注册的函数返回 true ,则R键原本在KAG系统中的功能“显示履历层”就不会执行了。(注意,能屏蔽的只有KAG系统的功能,如果注册了多个函数,第一个函数返回了true的话,其余的几个函数还是会被调用的。还有就是,多个函数的话,只要其中有一个函数返回了true,KAG系统的功能就会被屏蔽。)

 leftClickHook 和 rightClickHook 中的函数在调用时没有传入参数。
 leftClickHook 在 Enter键 和 Space键 这些被按下的时候也是会触发的。而在用鼠标单击选项之类的东西的时候却不会触发。


例:
@iscript
function myLeftClickHook()
{
    kag.process('', '*label');
    return true;
}
@endscript
@eval exp="kag.leftClickHook.add(myLeftClickHook)"
@s

*label
@eval exp="kag.leftClickHook.remove(myLeftClickHook)"
やあー。
@s


 上面的例子中,单击鼠标左键则会让脚本转到 *label 标签执行。
 请注意这个操作会让脚本强制跳到 *label 而不管当前的执行的脚本是什么。如果可能会在正在执行trans或者move的时候进行上面的操作的话,跳转后先用 stoptrans 和 stopmove 指令把这些停止会比较安全。

 keyDownHook 中的函数在调用时会传入两个参数。第一个参数是被按下的按键的虚拟键码,第二个参数是在这个按键被按下的同时shift键的状态。详细说明请参见 吉里吉里2 SDK Help 。


例:
@iscript
function myKeyDownHook(key, shift)
{
    if(key == #'R')
    {
        // 如果按下了R键
        kag.process('', '*label');
        return true;
    }
}
@endscript
@eval exp="kag.keyDownHook.add(myKeyDownHook)"
@s

*label
@eval exp="kag.keyDownHook.remove(myKeyDownHook)"
やあー。(这个不翻了)
@s

touchImages

 System.touchImages 用于把图像文件读入到缓冲区中。
 详细说明请参考 吉里吉里2文档中的 System.touchImages 成员函数。例如,如果想利用那些因为一些wait指令而空闲下来的时间,可以用这个函数,把以后可能用到的图像提前读取出来。
 在使用 KAG 的场合,前景和背景图像 ( 但是不能用于需要指定 key 参数的图像,用PNG的人基本上不用担心这个 ) 都是可以用的。请将 image 或 img 指令的 storage 参数中将要指定的文件名装进数组传给 storages 参数。
 第2个参数指定 -2*1024*1024 左右就可以了。
 第第3个参数是延迟时间,指定 - 200ms 左右就可以了。


例:
@resetwait
@eval exp="System.touchImages(['24_5', '24_4', 'uni', '24'], -2*1024*1024, 800)"
@wait mode=until time=1000


 但是,这个成员函数的行为有不确定性,并不能保证一定能把图像提前加载进来。所以如果想万无一失的提前加载图像的话,是不能用这个方法的。如果有这种要求的话还是使用后面提到的 assignImages 中的方法比较保险。

assignImages

 assignImages 用于将Layer中的图像复制到其他Layer。
 例如,

@eval exp="kag.fore.base.assignImages(kag.fore.layers[0])"

 像上面的代码中这样写的话,就可以将表前景图层的layer0中加载的图像复制到表背景图层中。
 实际上,assignImages 并不是把图像的数据复制过去,而是设置一个“复制的源图层和目的图层中的图像变得一样了”的标记,所以这个操作是很快速而高效的。demo scene等演出途中读取图像产生的时间延迟会对演出造成影响的场合,可以预先把图像加载到隐藏状态的前景图层中,需要的时候再用这个函数复制到背景层等演出用的图层。

hact 指令的应用

 hact 指令可以在履历层中的文字上设置可点击区域,在点击时执行任意的tjs表达式,于是可以利用这一功能实现支持语音回放的履历层。
 下面的例子实现了这个功能。其中 pv 这个宏的功能为播放语音, sv 宏的功能为停止播放语音。

例:
@iscript
function stopAllVoices()
{
    // 停止buf序号为 2 〜 6 的音效
    for(var i = 2; i <= 6; i++) kag.se[i].stop();
}
function playVoice(buf, storage)
{
    // 用buf参数指定的序号的音效buffer 来播放 storage 参数指定的音品文件
    // 在 KAG 处于快进状态的时候不做这个处理
    if(!kag.skipMode)
    {
        stopAllVoices();
        kag.se[buf].play(%[ storage : storage ]);
    }
}
function createHistoryActionExp(buf, storage)
{
    // 生成点击履历层的hact区域时执行的tjs表达式
    return "stopAllVoices(), kag.se[" + buf  +"].play(%[ storage : '" + storage + "' ])";
}
@endscript
@macro name=pv
@hact exp="&createHistoryActionExp(mp.b, mp.s)"
@eval exp="playVoice(mp.b, mp.s)"
@endmacro
@macro name=waitvoices
@ws buf=2
@ws buf=3
@ws buf=4
@ws buf=5
@ws buf=6
@endmacro
@macro name=sv
@endhact
@waitvoices cond="kag.autoMode"
@eval exp="stopAllVoices()"
@endmacro


 createHistoryActionExp 函数用来生成传递给 hact 指令的 exp 参数的 TJS 表达式。在这两个宏中生成的 TJS 表达式会被执行。

 这两个宏的使用方法见下面的例子。


例:
[pv b=2 s=hoge.ogg]ほげ[l][sv][r]
[pv b=3 s=hogera.ogg]ほげら[l][sv][r]
[pv b=4 s=hogemoge.ogg]ほげもげ[p][sv]

游戏程序初始化时执行的脚本

 为了让 KAG 的系统能够定制,在初始化的某些阶段中可以执行任意的 tjs 脚本。现在的版本中使用以下的方法来实现。

Override.tjs
 这个文件如果存在的话,会在 MainWindow.tjs 加载之后执行。默认这个文件是不存在的,如果需要的话请自行创建。
AfterInit.tjs
 这个文件会在所有的默认初始化步骤执行完毕之后,在 first.ks 执行之前执行。默认这个文件也是不存在的,如果需要的话请自行创建。
追加设置
 Config.tjs 中有若干个可以填写「◆ window和动作的追加设置」或者「追加设置」的区域。在这些区域填写的内容会在 Config.tjs 的执行过程中的各个阶段被执行。

菜单的定制

 如果想在菜单中添加单纯的可以设置 on/off 的菜单项的话,可以在 AfterInit.tjs 中填写类似下面的内容。


例:
kag.menu.insert(kag.optionsMenu =
    new KAGMenuItem(this, "効果(&G)", 0, void, false), 2);
kag.optionsMenu.stopRecur = true;

kag.optionsMenu.add(
    kag.doTransMenuItem = new KAGMenuItem(
        this,
        "切换画面(&T)",
        0,
        function(sender) { sf.dotrans = sender.checked = !sf.dotrans; },
        false));

if(sf.dotrans === void) sf.dotrans = true;
kag.doTransMenuItem.checked = sf.dotrans;

kag.optionsMenu.add(
    kag.playSEItem = new KAGMenuItem(
        this,
        "播放音效(&S)",
        0,
        function(sender) { sf.playse = sender.checked = !sf.playse; },
        false));

if(sf.playse === void) sf.playse = true;
kag.playSEItem.checked = sf.playse;


 kag.menu.insert(kag.optionsMenu = new KAGMenuItem(this, "効果(&G)", 0, void, false), 2); 这行代码会在KAG的菜单栏中插入「効果」子菜单。kag.optionMenu 则成为这个「効果」子菜单的对象。insert 成员函数的第2个参数是菜单项的插入位置。
 下一行代码把这个对象的 stopRecur 设置为 true ,这是为了让 kag.internalSetMenuAccessibleAll 不去搜索不必要的菜单项。

 调用 kag.optioneMenu 的 add 成员函数来添加下属的菜单项。

 KAGMenuItem 的第4个参数指定在菜单项被点击的时候执行的tjs表达式。

 if(sf.dotrans === void) sf.dotrans = true; 这行代码在 sf.dotrans 的值为 void ( 也就是说,里面什么数据也没有的状态 ) 的时候将初始值代入。kag.doTransMenuItem.checked = sf.dotrans; 这行代码在设置菜单项的默认选中状态。因为这个状态是用时系统变量来记录的,即使程序退出了,在下一次启动的时候也会继承上一次的状态。

 在这之后,因为 sf.dotrans 和 sf.playse 中已经把当前的菜单的状态记录下来了,

@playse storage="kon.wav" cond="sf.playse"
 可以实现类似这样的功能,用这个变量得知菜单项的状态,从而控制一些指令的执行。

 用这个方法可以实现很多应用。

来编写 KAG 用 的插件吧^-^

 可以通过创建 KAGPlugin 类 的子类并注册到KAG中来实现扩展KAG的机能的插件。
 在KAG的程序包里包含了这方面的事例,在编写插件的时候请参照这些事例来编写。