【编程开发】日式 AVG——我那凄惨无比的 NVL 探索经历
【编程开发】日式 AVG——我那凄惨无比的 NVL 探索经历
nvl \(_{(与kag、krkr相互合作打出的又臭又长的_{比夏天美少女脱下来的裹在长筒靴里的长筒袜还要香喷喷的}无限套娃组合拳...)}\) \(\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ {}\) 虐我千百遍;
我待\(_{(...语法与C++惊人相似且有完整代码高亮的)}\) \(\ \ \ \ \ \ \ \ \ \ \ \ \ \ {}\) js 如初恋。
1.【编写代码时添加高亮】
notepad++可以自定义。为了设置高亮花了不少时间,但这大大提高了之后的制作效率(也更保护眼睛....)
2.【自定义宏】
一开始啥都不懂,有一些效果无论我用 @backlay
和 @wt
怎么搞,就是实现不了,简直不要太折磨。
后来发现 title.ks
文件和 prologue.ks
中使用的诸如 @fg
、@bg
、@hidemes
等等一堆东西是在 nvl\macro_play.ks
里定义的宏(还有一些诸如 trans
、history
的东西没找到,不知道是在哪儿定义的)。
于是乎,写自定义宏就瞬间解除了诸多限制。
3.【查看对话历史记录时顺带隐藏立绘、头像(layer图层),并在退出时恢复】
(1).【问题】
进入对话历史界面时会自动隐藏message层(对话框和按钮等等),立绘和头像等等所在的 layer 图层会保留。
但是,我希望把layer=8的头像层也一起隐藏掉。
(2).【解决方案+碎碎念】
一堆tjs翻来翻去,最终总算是理清了函数互相调用的顺序(nmmd,根ta喵的俄罗斯套娃一样蹦来蹦去,难受死了):
在 nvl\macro_ui.ks
的【系统按钮全部定义】下面找到 history 按钮的代码:
[mysysbutton name="history" dicname="f.config_dia.history" exp="kag.onShowHistoryMenuItemClick()"]
exp 指向 kag.onShowHistoryMenuItemClick()
函数,它定义在system\MainWindows.tjs
里面:
function onShowHistoryMenuItemClick(sender)
{
if(historyLayer.visible) hideHistory(); else showHistory();
}
然后可以在同一个文件里找到 showHistory()
函数和 hideHistory()
函数的定义:
function showHistory()
{
// メッセージ履歴レイヤを表示する(显示对话历史层)
...(此处省略若干代码)...
historyLayer.dispInit();
...(此处省略若干代码)...
}
function hideHistory()
{
// メッセージ履歴レイヤを非表示にする(隐藏对话历史层)
historyLayer.dispUninit();
...(此处省略若干代码)...
}
这里代码中调用的 dispInit()
函数和 dispUninit()
函数定义在 nvl\MyHistoryLayer.tjs
里:
function dispInit()
{
kag.hideMessageLayerByUser();
...(此处省略若干代码)...
}
function dispUninit()
{
...(此处省略若干代码)...
kag.showMessageLayerByUser();
}
然后...nmmd这两个函数的定义又要回到system\MainWindows.tjs
里去找:
function hideMessageLayerByUser()
{
// メッセージレイヤを一時的に隠す(暂时隐藏消息层 )
if(messageLayerHiding) return;
// 画像のクリア
setMessageLayerHiddenState(true);
...(此处省略若干代码)...
}
function showMessageLayerByUser()
{
// 一時的に隠されていたメッセージレイヤを元に戻す(恢复暂时隐藏的消息层)
if(!messageLayerHiding) return;
setMessageLayerHiddenState(false);
...(此处省略若干代码)...
}
又在紧挨在它上面的位置找到 setMessageLayerHiddenState()
函数的定义:
function setMessageLayerHiddenState(b)
{
var layers;
layers = fore.messages;
for(var i = layers.count-1; i >= 0; i--) layers[i].setHiddenStateByUser(b);
layers = fore.layers;
for(var i = layers.count-1; i >= 0; i--) layers[i].setHiddenStateByUser(b);
selectLayer.setHiddenStateByUser(b);
//mapSelectLayer.setHiddenStateByUser(b);
// プラグインを呼ぶ
forEachEventHook('onMessageHiddenStateChanged',
function(handler, f) { handler(f.hidden); } incontextof this,
%[hidden:b]);
}
喵喵喵???????
这里不是有隐藏layers的代码咩?迷惑...
我试着在下面加了个 fore.layers[8].setHiddenStateByUser(b);
nmmd...没反应。
琢磨了好久,最后想到试着搬一下freeimage的代码,同样是这个tjs文件,找到它的定义:
freeimage : function(elm)
{
// 画像のクリア
updateBeforeCh = 1;
getLayerFromElm(elm).freeImage(elm);
return 0;
} incontextof this,
emm....虽然不知道 updateBeforech
是干嘛的,但是在前面可以找到getLayerFromE1m()
的定义,顾名思义,就是根据参数elm返回一个layer层。于是我试着在 hideMessageLayerByUser()
里加了一行fore.layer[8].freeImage();
哎,成了!
但是...退出对话历史记录界面时要怎么恢复呢?
好像...要传参数...
然后,我在 hydrozoa大佬 的 krkr进阶教程3 中找到了传参的语法。大佬用了 setOptions()
函数做示范,这玩意儿其实就是layopt,同样在这个tjs文件里可以找到定义:
layopt : function(elm)
{
// レイヤのオプションを設定
updateBeforeCh = 1;
getLayerFromElm(elm).setOptions(elm);
return 0;
} incontextof this,
干脆就用layout好了。
最后改成酱紫,成功得到了我想要的效果:
function hideMessageLayerByUser()
{
// メッセージレイヤを一時的に隠す(暂时隐藏消息层 )
if(messageLayerHiding) return;
//【隐藏layer=8的头像】
fore.layers[8].setOptions(%['visible'=>false]);//【加这一句】
...(此处省略若干代码)...
}
function showMessageLayerByUser()
{
// 一時的に隠されていたメッセージレイヤを元に戻す(恢复暂时隐藏的消息层)
if(!messageLayerHiding) return;
//【显示layer=8的头像】
fore.layers[8].setOptions(%['visible'=>true]);//【加这一句】
...(此处省略若干代码)...
}
或者加到 setMessageLayerHiddenState()
函数里也可以,打个取反符号:
function setMessageLayerHiddenState(b)
{
var layers;
layers = fore.messages;
for(var i = layers.count-1; i >= 0; i--) layers[i].setHiddenStateByUser(b);
layers = fore.layers;
for(var i = layers.count-1; i >= 0; i--) layers[i].setHiddenStateByUser(b);
//【隐藏or显示layer=8的头像】
fore.layers[8].setOptions(%['visible'=>(!b)]);//【加这一句】
...(此处省略若干代码)...
}
4.【清空前面的对话历史记录】
(1).【问题】
到达一个新的章节时,想要清除前一个章节的对话记录。
但nvl只提供了对话记录换行换页功能,而且还整了一道不写题目、不写选项、不写评分规则的阴间完形填空。
孩子被吓傻了。
(2).【解决方案】
在探索问题1时,不小心发现了有意思的东西,顺带解决了问题2(原本还打算自己写一个,但研究源码太折磨就放弃了)。
在 system\MainWindows.tjs
里有定义这样一个函数:
clearhistory : function(elm)
{
// メッセージ履歴レイヤの消去
historyLayer.clear();
historyOfStore.clear();
nextRecordHistory = false;
return 0;
} incontextof this,
这里调用的 clear()
函数可以在 nvl\MyHistoryLayer.tjs
里找到,就是清空记录的意思。
在 nvl\macro_play.ks
里加个自定义宏,然后就可以在剧本文件里使用了:
[macro name=clhistory]
[clearhistory]
[endmacro]
(这么常见的功能,nvl居然没弄)
5.【自定义选项框文字】
想让不同选项显示的文字颜色不同。
其实可以通过选项框图片内置文字的方法解决(设计字体样式更开放),但我试着让选项文字为空,测试的时候程序直接报错了,回头去看剧本文件,好家伙,原本放在那儿的 text=""
直接被 nvl 吃掉了...你她喵的几个意思嘛...
总之,这样的话相当于参数 text
没有传过去,代码应该没有特判为空的情况。
所以还是得去找调用的源函数。
首先是 nvl\macro_play.ks
里的这个东西:
[macro name=selbutton]
[eval exp="createSelbutton(mp)"]
[endmacro]
在 nvl\function.ks
找到函数 createSelbutton()
:
function createSelbutton(mp)
{
var selbutton=%[];
//复制默认设定
(Dictionary.assign incontextof selbutton)(f.setting.selbutton);
//将mp中传入的值覆盖默认设定
foreach(mp,setdictvalue,selbutton);
//删除空值
foreach(selbutton,checkdict);
//根据字典建立按钮
kag.tagHandlers.button(selbutton);
//为描绘文字做出处理
//假如没有填写剧本,则读取当前执行中的剧本名
if (mp.storage==void) mp.storage=Storages.extractStorageName(kag.conductor.curStorage);
//假如连标签也没有,就自动填个标签
if (mp.target==void) mp.target="*start";
//在按钮上描绘文字
drawSelButton(mp.text,mp.storage,mp.target);
}
要用图片内嵌文字的话,就直接加一个参数为空时的特判就可以了:
function createSelbutton(mp)
{
...(此处省略若干代码)...
if (mp.text==void) mp.text="";//【加这一句】
//在按钮上描绘文字
drawSelButton(mp.text,mp.storage,mp.target);
}
但我觉得,自己传颜色参数进去,让选项文字样式和正文一样比较好。所以最后没有这样搞,只是测试了一下可行性。
紧矮在下面的就是 drawSelButton()
,在这个函数里加个 color
参数,然后上面 createSelbutton()
里调用一下就行:
function createSelbutton(mp)
{
...(此处省略若干代码)...
if (mp.color==void) mp.color=-1;//【加这一句】(如果为空就用nvl工程设定统一安排的颜色)
//在按钮上描绘文字
drawSelButton(mp.text,mp.storage,mp.target,mp.color);//【这里把color传进去】
}
function drawSelButton(caption, storage, target, color)//【这里加个color参数】
{
...(此处省略若干代码)...
// 既读文字颜色设定
if ((checklabel!)>0 && read!=void) {sel_color=read;}
else {sel_color=normal;}
if(color!=-1) sel_color=color;//【加这一句】
...(此处省略若干代码)...
}
当然也可以修改指向和选中后的颜色,以及描边、阴影等样式,都可以直接在这里加参数修改。
6.【播放视频时诡异的 YesNo 询问窗口】
在播放视频时点击右上角关闭窗口按钮,会发现这种诡异的现象。
大概是 YesNo 页面的摆放位置不对,有点偏上了。(而且视频没有暂停,下面露出来的那个地方仍在正常播放...)
在 nvl\MyYesNoDialog
中找到函数:
// Yes か No かはっきりさせる関数をのっとる
var askYesNo = function(message, caption = "確認", yesFunc, noFunc, param, doneFunc)
{
if (kag.isMoviePlaying()) {//【视频播放的时候,绘制YesNo页面的代码和平时不一样】
var win;
if (message.indexOf("まで戻りますか?") >= 0) {
win = new MyYesNoDialogWindow("dialogprev");
} else {
var bgd = yesnoMap[message];
//假如能够取得背景图,直接使用对应背景图+空白文字
if (bgd !== void) {
win = new MyYesNoDialogWindow(bgd,"");
}
//否则使用空白图+文字
else {
//win = new YesNoDialogWindow(message, caption);
var ynset=Scripts.evalStorage("uiyesno.tjs");
win = new MyYesNoDialogWindow(ynset.bgd,message);//【只看这一句】
}
}
...(此处省略若干代码)...
}
else {
...(此处省略若干代码)...
}
};
在上面找到 MyYesNoDialogWindow()
函数:
function MyYesNoDialogWindow(baseStorage,message="")
{
...(此处省略若干代码)...
// ウィンドウ位置の調整
if(global.Window.mainWindow !== null && global.Window.mainWindow isvalid)
{
var win = global.Window.mainWindow;
var l, t;
l = ((win.width - width)>>1) + win.left;
t = ((win.height - height)>>1) + win.top;//【是它,就是它,就是这行神仙代码】
if(l < 0) l = 0;
if(t < 0) t = 0;
if(l + width > System.screenWidth) l = System.screenWidth - width;
if(t + height > System.screenHeight) t = System.screenHeight - height;
setPos(l, t);
}
else
{
setPos((System.screenWidth - width)>>1, (System.screenHeight - height)>>1);
}
var ynset=Scripts.evalStorage("uiyesno.tjs");
...(此处省略若干代码)...
}
经过一系列测试,发现那个 win = global.Window.mainWindow
是游戏窗口在电脑屏幕上的的像素显示区域(包括外面那层很细的白边,以及上方标题字区域)。System.screenWidth
和 System.screenHeight
是整个电脑屏幕区域。
单独拿出这两行来看:
l = ((win.width - width)>>1) + win.left;
t = ((win.height - height)>>1) + win.top;//【是它,就是它,就是这行神仙代码】
发现它计算摆放位置的时候,尝试摆放在游戏窗口的正中央。
嗯,行。
没毛病。
但是,你把最上面的标题字区域也计算进去干嘛?
人麻了...
暴力出奇迹,直接截图数像素点,然后手动摆放,改成了这样:
t = 31 + win.top;
好,现在我们来看后面那两行让我迷惑不已的代码:
if(l < 0) l = 0;
if(t < 0) t = 0;
if(l + width > System.screenWidth) l = System.screenWidth - width;
if(t + height > System.screenHeight) t = System.screenHeight - height;
意思是,如果YesNo页面有一部分会跑到电脑屏幕外面去,那么强行把它拉回来让它显示全。
效果图:
我猜测可能是,担心窗口跑到外面点不到,然后就会没办法关闭窗口。
因为此时游戏窗口无法挪动(仅有 在播放视频的时候打开YesNo窗口 这种情况下)。
但是...按右键可以关闭YesNo页面...
所以,还真就是个人类迷糊行为代码。
由于我的 YesNo 页面有一层半透明的黑色幕布作为背景,而 nvl\MyYesNoDialog
中注释有提到:“当视频播放时,背景层不会透明”。也就是说原本应该半透明的背景图在这里会转换成不透明。
所以我这个位置摆放诡异的黑色部分会这么明显,如果没有这个幕布的话,那么上面提到的两个问题应该都是可以忽略不计的,不会存在看起来不美观的问题。
\(To\ be\ continued...\)