【编程开发】日式 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 里定义的宏(还有一些诸如 transhistory的东西没找到,不知道是在哪儿定义的)。

于是乎,写自定义宏就瞬间解除了诸多限制。

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.screenWidthSystem.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...\)

posted @ 2022-01-29 16:57  辰星凌  阅读(484)  评论(1编辑  收藏  举报