http://eelglass.g1.xrea.com/game/NScriptercallback.htmlに移転しました。
Googleはbotくんに検出させてください。
方針@:それぞれのコールバックが具体的にいつ呼ばれるか正確に把握する
方針A:textとtext0以外は割と単純なので万人が使えるようにする
方針B:入門者向けの解説を書こうとして失敗した感が無くもない(赤字で強調した箇所は中級者〜にも有益な情報?)
(書いた日:2016/04/02 最終更新:2016/12/22)
準備:
NSLua導入
コールバックとは?
関数とは?
初級:
reset
close
tag
save
中級:
load
savepoint
end
animation
上級:
text0
text
NSLua導入、準備
@公式から一通りファイルを落とし、
A「nslua.dll」をnscr.exeと同じフォルダ、またはdllフォルダ内部に置く。
Bnscr.exeと同じフォルダにプレーンテキストファイル(〜.txt)を作り、
C用意したテキストファイルを「system.lua」というファイルにリネームする
----ここまで準備----
Dリネームしたファイルをメモ帳や他のエディタで開き、
E望むままにLuaスクリプトを記述する。
NSLuaは単独の言語ではない
「『Lua5.1』という言語」に「NScripterと連携するための命令(関数)」が追加されたもの。
「NSLua」初心者向け講座でピンと来ない場合、「Lua」初心者向け講座をググって探すとよい。
そもそも変数にどうやって代入するのか?とか文字列の連結どうするの?とか、その辺りから解説されている。
いわゆる三大聖地の一角である
NScripter - Tipsの解説ページで紹介されている物以外だと、
たとえば
八角研究所の記事や
ヤマハルーターの公式ページが公開してくれている解説が丁寧かつシンプルで分かりやすい(ただし後者はヤマハルーター独自の文法が多少混じっている)。
コールバックとは?
「NScripterが特定の動作を行う際に」
「割り込んで実行されるor動作そのものを乗っ取る」
「特別な名前のLua関数」
定義節内部で「luacall ○○」命令を実行すると、
○○に対応したコールバックが有効になる。
「コールバックが有効になる」とは、たとえば
「luacall load」ならロード後に「NSCALL_load()というLua関数呼び出しが実行されるようになる」
という状態を指す。
loadgosub命令を思い浮かべてください。
関数とは?
@「呼び出し元から渡された値を受け取る」
A「何か行う」
B「何らかの値を呼び出し元に返す」
こうした動作をひとまとめに行うことができる命令。
一般的には@〜Bのどれか(または全て)を行う。
@AはNScripterの命令が行っている動作と何も変わらない。
「NScripterの命令」もだいたい関数である
と考えることができる。
NScripter風に言えば、
@「gosub元から渡された値を受け取る」
A「何か行う」
B「returnしつつ何らかの値を呼び出し元に返す」
こうなる。defsubを想像してください、知ってる動作ですね?
たとえば「csp 1」なら、「1」という値を「csp」関数に渡している。
※この「1」=渡された値は関数において「引数」と呼ばれる。
cspは渡された「1」を見て特定の動作を実行し、
特に値は返さずreteurnしている。
「(forやgotoといった、行移動を伴う)僅かな命令だけが例外で、
他はNスク命令、gosub、defsub命令呼び出し、全部関数だ」と思えばだいたい合っている。
※forやifなどの命令は、一般には命令よりも上位の存在(予約語)と見なされる。
余談:
中学〜高校の数学で「f(x)=x^2+2x+1 y=f(x) 」のような記述を習った覚えがあるならば、それが関数。
Luaとして書き直せば
function hoge(a)
return a^2+2a+1
end
y=hoge(x)
こうなる。
Luaの世界ではもう少し柔軟に関数を作ることができる。
function hoge(a,b)
return a+5,b+10
end
x,y=hoge(6,12)
のように複数の値を受け渡ししてもよい。
中学数学的な定義は「一方の値(渡す値)に応じて他方の値(計算結果)が決まる関係」となっていたが、
いずれにせよ
@「呼び出し元から渡された値を受け取る」
A「何か行う(渡された値を使った計算)」
B「何らかの値(計算結果)を呼び出し元に返す」
という性質は一貫している。
コールバックを使う@かんたんなやつら
NSCALL_reset …実行節開始時、リセット直後に呼ばれる。
------------------------------------------------------
--使用例。Luaのコメントアウトは「--」で行う。
--[[
複数行コメントアウトも出来る。
Lua入門者向け解説サイト見た方が早いのでこういった記述は以後カット。
--]]
function NSCALL_reset()
NSOkBox("リセットしました。","確認")
NSSetStrValue(0,"リセット直後") --NSExec('mov $0,"リセット直後"')
end
------------------------------------------------------
有効になっていると、「実行ブロック開始直後」にNSCALL_reset関数を呼び出す。
何ができるか
リセットを検知できる。
「goto *start」等といった(特殊な)記述を用いていない限り、
「*start」直後にスクリプトを書く(luasubした関数を呼び出す)のと変わらない。
NSCALL_close …「閉じる」をクリック時に呼ばれる。デフォルト動作を乗っ取る。
使用例@
function NSCALL_close()
local a=NSYesNoBox("終了しますか?","終了確認")
if(a==true)then
return true
else
return false
end
end
--使用例A
function NSCALL_close()
local a=NSYesNoBox("終了しますか?","終了確認")
return a
end
--使用例B
function NSCALL_close()
return NSYesNoBox("終了しますか?","終了確認")
end
------------------------------------------------------
有効になっていると、×(閉じる)ボタンを押した際にデフォルトの確認ウインドウを出さずNSCALL_close関数を呼び出す。
タスクバーから「閉じる」を選んだ場合も同様の処理になる。
関数からtrueを返すと終了、falseを返すと終了中止。
上の3例は全て同じこと(確認ウインドウを出す)をしている。
何ができるか
確認ウインドウ無しで終了させる、
確認ウインドウの文言を変える、
グラフィカルなウインドウを表示する、
など。
(最後の使い道は自分で画像を表示してクリック待ち等を行わねばならないため、もう少し深い理解が必要)
ほか、「終了しようとしたのを検知して何か演出を挟む」「特定条件下で終了ボタンを無効化する」
といったシステム寄りの仕掛けも作れるが、ユーザーは終了する気でボタンを押すので反感買わないように注意するべき。
endコールバックと併用する場合は内部でerror()関数を呼んではいけない。
(というか極力コールバック内でerror()関数を呼ぶべきではないが……)
NSLua側の処理がそこで終わってしまうのか、意図しない妙な挙動になる。
NSCALL_tag …pretextgosubの代わりにタグテキストを処理する。
------------------------------------------------------
--使用例
function NSCALL_tag(str)
NSOkBox("「"..str"」というタグが呼ばれました。","タグ")
end
---------
*define
game
*start
[田中,笑い] ;←これがタグ
「俺はいくらでも湧いてくるんだ」\
end
------------------------------------------------------
有効になっていると、「タグ」が関数に渡される。
pretextgosubのLua版。pretextgosubは無効になる(同時使用はできない)。
何ができるか
pretextgosubと同じことができる。それ以上でも以下でもない。
最も分かりやすい使い方は立ち絵などの略記用途……
つまり「ld l,"tatie\keiiti_warai.png",2」といちいち書く代わりに[圭一,左,笑い]と書くような使い方。
バックログ自作等複雑な操作でも世話になるかもしれない。
テキストの分割や参照は自分で(Lua関数を用いて)行う事になる。
Lua5.1には標準でsplit相当の関数がないためやや戸惑うかもしれない(ググれば自作関数は出てくる)。
代わりに文字列検索(string.find)や一致判定(string.match)等は一通り揃っているため、
慣れればNScripter側で処理するよりも柔軟にタグを解釈できる。
NSCALL_save …セーブ直前に呼ばれる。
------------------------------------------------------
--使用例
function NSCALL_save(i)
NSOkBox(i.."番に保存します。","セーブ")
end
------------------------------------------------------
有効になっていると、「セーブ
直前」にNSCALL_save関数を呼び出す。
第1引数(上の例では「i」)にセーブする番号が渡される。
何ができるか
実は使い道に乏しい……公式の注意書きにもある通り、セーブデータに格納されるべきは「セーブポイントを更新した時点でのデータ」であることが大半であるから。
データの保存はNSCALL_savepointで行われるべきなので、それ以外(たとえばセーブ時のエフェクト、演出)で用いる事になるか。
しかしクイックセーブに演出が付いたりしてもそれはそれで妙なので、その場合セーブする番号を見て分岐を行うべき。
「セーブデータとは別にLua変数の状態保存するファイル作ればよくね?」と思った人はここでファイルを出力する事になるかもしれない。
コールバックを使うANScripterの仕様と縁深いやつら
NSCALL_load …ロード直後に呼ばれると思いきやそうではない。
------------------------------------------------------
--使用例
function NSCALL_load(i)
NSOkBox(i.."番をロードしました。","ロード")
end
------------------------------------------------------
NScripterはloadgameの直後にセーブポイントを初期化=作成する。
つまり
savepointコールバックが先に呼ばれる。
実行順序は
@(ロードによる変数やスプライトの復帰)
ANSCALL_savepoint
BNSCALL_load
Cloadgosubラベル
となる。したがって、正しい説明は以下のようになる。
有効になっていると、「
loadgosub判定の直前」にNSCALL_load関数を呼び出す。
何ができるか
主に(セーブデータ内外に格納された)データの復帰を行う場所となる。
呼び出し順を考えてもloadgosub命令のLua版と思ってほぼ問題ないはず。
ただし、「savepointコールバックが先に呼ばれる」点には注意しなければならない。
何が問題か
@savepoint内部で【Lua変数の値をNスク変数に格納する】
Aload内部で【Nスク変数のデータからLua変数を元の値に戻す】
何も考慮せずこういった処理を作ると、「ロード直後のsavepoint」で格納される値がロード前のものになる。
この問題は「ロード直後のセーブポイントか?」という判定をsavepointコールバック内で行うことで回避できる。
NSCALL_savepoint …セーブポイント更新直前に呼ばれる。
------------------------------------------------------
--使用例
do
local num=0
function NSCALL_savepoint()
num=num+1
NSExec('caption "起動後'..num..'回目のセーブポイント更新です。"')
end
end
------------------------------------------------------
有効になっていると、「セーブポイント更新
直前」にNSCALL_savepoint関数を呼び出す。
loadの項でも書いたが、ロード実行後に最速で呼び出される処理はこいつ。
何ができるか
このタイミングで「セーブデータに保存したいLuaの変数」をNスク変数に代入すれば、
直後のセーブポイント更新でその値が格納される。
大原則としてNスクのあらゆる動作はLua変数にタッチしないため、
ゲーム終了を跨いで保存したいLuaのデータがあればどこかしらに自分で保存する(そしてロード時に復元する)必要がある。
「Nスク変数=セーブデータに放り込むのが一番手っ取り早くね?」と思った人はここで処理する事になる。
「セーブポイント更新直前」ではあるが、起動直後(実行ブロック開始直後)は呼び出されない。
つまり、内部的には
「実行ブロック開始時に初期セーブポイントを作る」のではなく、
「セーブポイントが存在しなければ実行ブロック開始時の状態を使う」
と処理されているのかもしれない。
NSCALL_end …(exeクラッシュを除く)本体終了時に呼ばれるが、
他のコールバック内部でerror()関数を呼び出した場合のみ妙な事になる
------------------------------------------------------
--使用例
function NSCALL_end(str)
local a=io.open("りざると.txt","w")
a:write("倒した村人の数…"..murabito.."人")
a:close()
end
---------
*define
game
*start
[田中,笑い] ;←これがタグ
「俺はいくらでも湧いてくるんだ」\
end
------------------------------------------------------
有効になっていると、「プログラムm終了寸前」に呼び出される。
視界からウインドウが消え、あとはWindowsが後始末をするだけ……というあの瞬間に呼ばれる。
何ができるか
正常終了、エラーによる終了を問わず一定の処理を行える。
本体がクラッシュしてしまった場合(「nscr.exeは動作を停止しました〜」※)はさすがに無理だが、
終了時に行っておきたい処理やログの出力等、を書いておけばいろいろな用途が出てくるはず。
上の例ではテキストを出力しているが、このタイミングで「savegame 0」等を実行すれば終了時オートセーブを作れる。
※よく知られる例は「ウェイト無しで無限ループした場合(CPUが音を上げてクラッシュ)」「strspでテキストが領域をはみ出した場合」の2つ。
endコールバック内でエラーを出してしまうと無限ループ(要プロセス一覧から直切り)するが、以下のように回避できる。
--------------------------------------
do
local end_shita
function NSCALL_end()
if(end_shita)then
return
else
end_shita=true
〜何らかの処理〜
end
end
end
-------------------------------------
NSCALL_animation …「アニメーションが実行される直前」に呼ばれる。
単に「アニメーションさせるためのコールバック」ではない。(もちろん絵を動かしても構わないが)
「アニメーション実行時=定期的に割り込み実行される処理」がこいつの本質。
------------------------------------------------------
--使用例
function NSCALL_animation()
--〜処理内容〜
end
NSLuaAnimationInterval(17) --17ミリ秒に一回、およそ秒間30回を目途に呼び出す
NSLuaAnimationMode(true) --定期実行を開始する
------------------------------------------------------
「アニメーションが実行されるタイミング」とは?
「セルアニメーションを設定したスプライトが実際にアニメーションするタイミング」を考える。
@アニメーションする:
「テキスト表示〜クリック待ち」「ボタン待ち」「選択肢表示中」「命令と命令の間」
ただし、クリック待ち・ボタン待ち・選択肢の最中はNSGetClick()に制限が掛かる。具体的には「l」と「r」が占有され、ホイールとld/rdしか取得できない。
また、「待ち状態に割り込むべきでない命令」がいくつか存在する(大雑把に言って待ちを伴う命令の多重実行は挙動が怪しくなる)。要注意。
Aアニメーションしない:
「エフェクト実行中」「標準右クリックメニュー呼び出し中」
前者のタイミングで呼び出されるのがanimationコールバック。
trueを返すとアニメーションを実行し、falseを返す(return false)とアニメーションをキャンセルする。
NScripterで唯一行われている並列処理がアニメーションである。
何ができるか
無限の可能性。
たとえば
「selectやボタン待ち中に他の命令で割り込む」
(※ただしごく一部の命令は問題を起こす)
「テキスト表示中に背景やスプライトを動かす」
(※これはdllでもできるが)
といった、Nスク本体の命令では実現できない動作を多様に行える。
「変数の状態を常に監視してスプライトを適宜差し替える」といった動作も可能。
厳密には割り込み実行であって並列処理ではないが、「実行にあまり時間がかからない」疑似的な並列処理なら何でも行える。
なお、「実行に時間がかかる並列処理」を行おうと思った場合はLuaのコルーチンという処理(これもまた疑似的な並列処理だが処理を中断・再開できる)を用いることになる。
コールバックを使うB求道者向け
何らかの故あってテキスト表示(およびクリック待ち処理)を乗っ取る必要に駆られた時、
(いわゆる)システムカスタマイズ系命令またはこいつらを使う事になる。
その上で、たとえば常駐ボタンの設置程度なら従来のシステムカスタマイズサンプルを借りた方が早い事も付け加えておく。
NSCALL_text …表示文(変換後)の処置を乗っ取る。puttextには関与しない。
------------------------------------------------------
--使用例
function NSCALL_text(str)
NSExec('puttext "'..str..'"')
--スキップ時はクリック待ちを行わない
if(NSGetSkip()==0 and NSGetKey("ctrl")==false)then
--クリック以外の入力を許さない熱い設計
NSClick()
--実際はbexec等でクリック待ちループを行い、
--その結果から常駐ボタンを発火させたりテキストを進め(ループ離脱し)たりする。
end
end
------------------------------------------------------
変数を中身に変換した、「あとはこれを表示するだけ」という状態のテキストが渡されてくる。
注意書きにある通り標準のテキスト表示は行われなくなるため、この関数内では
@テキスト表示(puttextなりその他手段なり)
Aクリック待ち(bexecやbtnwait)
を行うことになる。いわゆるシステムカスタマイズと等価。
Luaに慣れてきたら検討すると(扱えるデータの幅が広いため)便利。
「下部ウインドウレイアウトのサウンドノベルは今時常駐ボタンあって当然」
「常駐ボタン付けるにはクリック待ち処理を乗っ取る必要がある(面倒)」
というジレンマが苦しみの元であるなら、動くサンプルの改変から入った方が色々と手っ取り早い(と思う)。
需要あれば供給あり、一通り機能の付いたサンプルを公開してくれている解説サイトがある。
NSCALL_text0 …表示文(変換前)の操作を乗っ取る。puttextには関与しない。
------------------------------------------------------
--使用例
function NSCALL_text0(str)
--ごめん思いつかない
end
------------------------------------------------------
「あいうえお$0%0」のように変数の変換すら行われていない文字列が渡されてくる。
おまけに
テキスト表示絡みの標準処理はキャンセルされる。
この二点で頭痛くなった人は見なかった事にしよう。
よほど特殊な処理を行いたい場合に限る。
「%$0」のような本来エラーとなる記述を通したい場合に自分で変数を変換するとか?
オチはない(なんか最近geocitiesの広告が大きくなっていてヤバさ増量されている)