@NScripterは分かるがLuaは分からない
ANScripterもLuaも分かる
この暗黙の前提に基づき、NSLa独自の文法を解説する際に「NScripterではmov $0,""
のように値を代入しますが…」といった書き方をせず、「従来の命令に加え…」とだけ書くかもしれません。
その上で@・A両方の層に対応するために、本マニュアルは以下のような記述形態を採ることにします。
「オプションから変更できる挙動、およびNScripter目線(配布状態の動作サンプル)では正しいがLua目線(およびNSLaの実際の仕様)としては間違っている解説」には緑色のマーカーを用います。
たとえば、「NSLaは起動時に000.txt〜255.txtを読み込みます」といった具合です。
この例は同梱サンプルにおけるデフォルト挙動であり、実際には好きな名前のファイルを好きなタイミングで読み込む事ができます。
NSLuaユーザーにとっては重要な事実ですが、NSLaの実体は配列に読み込んだスクリプトを実行する(NSExec()やloadstring()のような)関数です[3.1](実際、関数としてLuaから実行できます)。
しかし混乱を避ける目的から、ある項目(追加/変更される文法や機能)についてNScripterの延長で解説できる限り、NScripter目線で記述することにします。
★は思いっきり書きかけの項目に付けている目印です。あと一か月くらいかかりそうです…
「生の文字」と書いた場合、原則として""
で括られた文字列を意味します。
「生の数字」と書いた場合も、同様に、変数記述ではない数字そのものを意味します。
「生の全角文字」と書いた場合、""
で括られてすらいない全角文字列を意味します。$0=あいうえお
の右辺がその例です。
「命令」と書いた場合、NScripterの命令全般を指します(Luaに命令という概念は(誤解を恐れずに言えば)存在しません)。
「引数」と書いた場合、命令または関数に渡される文字や数字などの値を意味します。
lsp 1,"foo.png",0,0
なら4つの引数がlsp
に渡されており、alert("text","title")
なら2つの引数がalert
に渡されています。
n番目の引数は一般に第n引数と呼ばれます。たとえばbg "foo.png",1
の第1引数は"foo.png"です。
「評価する」と書いた場合、「(該当する文字列を)処理・実行する」と読み替えて構いません。
たとえば「“$0,$1=$1,$0”の右辺を評価し、それから代入する」という文章は、
以下の二段階の操作を意味します。
「真である」「偽である」と書いた場合、前者を「条件を満たす」ないし「変数に何らかの値が代入されている」と読み替えて構いません。 Luaが分かる人は当然そのまま読んでください。
「深度」または「gosub深度」と書いた場合、「(スクリプトの開始位置を基準に)何重にgosubしているか」を表す数値を指します。
一度もgosubしていない状態は深度1、3回gosubしていれば深度4です。
「あと何回returnすればスクリプトを抜け出せるか」と言い換えることもできます。
「ネスト」または「ifネスト」と書いた場合、「(該当深度の開始位置を基準に)何重にifが記述されているか」を表す数値を指します。
NSLaにおけるifの文法に関しては当該項目を参照してください[2.7]。(NSLaではLua方式のifを使用できます。)
ifを使っていない区間はネスト0です。
Lua変数のうち、関数型については文中でhoge()
のように表記することがあります。これは純粋に視認性を目的とした表記であり、当該関数が引数を持たないことを即座に意味するものではありません。
NSNSgyo
に格納されており、NSNSsaveon
がsaveon状態か否かを管理しています。
NSNSで始まらない例として、たとえば以下のような関数を定義しています。
luadata.save() luadata.load() luadata.tload()
これらはLua変数の保存と読み出しを行う関数で、内部動作(ログ系の機能における出力)でも使用しています。
利便性のために追加した関数もあります。
string.split() table.copy() alert() ensafe()
こうした関数は内部動作に使用しておらず、必要に応じて上書きしても構いません。
デフォルトのLua関数のうち、error()とloadstring()とNSSleep()はグローバルに上書きしています。
上記以外に、NSLaの実行そのものを司る関数として以下を使用しています。
nsload() nsloadfile() ns() nsfile() nsboot()
このうちns関数が本体で、他はns関数を実行するためのラッパーです。詳細については拡張スクリプトの動的な読み込みと実行[3.2]を参照してください。
NSNSSaveHensu
で指定した番号の文字変数(通常変数が望ましい)を1つ使用します。;$V4095G2000S640,480L10000 NSNSSaveHensu=1999
この変数は内部情報(ロード時の戻り行など)を記録するための領域として予約されます。 スクリプトの可能な限り早い位置(NSLa読み込み直後、または定義節の先頭付近)で、「極力通常変数末尾に近い(使わない)番号」を割り当ててください。
NScripter変数のローカル化機能(localalias/setvar命令)[2.5]を利用する場合、指定した範囲の文字・数字変数がNSLa側の処理で使用されます。同様に「使っていない通常変数」を指定してください。同梱の000.txtでは以下のように設定されています。
localalias 1000,1255 setvar 1256,1511
defsub
やluasub
は出来なくなっています。--分岐・繰り返し if elseif elif else end outif for next do while repeat until switch case default fallthrough continue break nsbreak nsend --ジャンプ goto gosub return tablegoto skip jumpf jumpb --コルーチン setcoroutine resume yield --その他宣言・代入・内部処理 trap rtrap lrtrap --この辺は厳密に言うと微妙だけど予約としておく local var defsub select selgosub --内部でselnumを呼び出す。selnumは乗っ取り前提 mov reset
main domain english savedir savegame savepoint saveon saveoff loadgame systemcall setwindow setwindow2 setwindow3 clickstr linepage puttext kinsoku setkinsoku addkinsoku trap r_trap lr_trap rmode loadgosub textcolor textclear numalias stralias automode_time skipoff zenkakko --新旧ボタン関連全て セーブデータに用いるログ取り用途・環境の記録 btnwait bclear btndef spbtn cellcheckspbtn btn exbtn cellcheckexbtn btnarea getcursor getenter getfunction getinsert getmclick getpage gettab getzxc spclclk bsp bcursor bdown btrans btndown bdef btime btntime btntime2 exbtn_d kidokumode filelog labelexist ld cl bg lsp lsph csp msp amsp textspeed textspeeddefault indent texton textoff texthide textshow --音回り bgm bgmonce dwave dwaveloop bgmstop dwavestop stop bgmvol defbgmvol getbgmvol bgmfadein bgmfadeout sevol defsevol getsevol voicevol defvoicevol getvoicevol --以下はデフォルト命令ではない uselocalalias usevar loopsevol defloopsevol getloopsevol
NSCALL_animation() NSCALL_save() NSCALL_load() NSCALL_end() NSCALL_savepoint() NSCALL_reset() |
内部からそれぞれ
NSNSCALL_animation() NSNSCALL_save() NSNSCALL_load() NSNSCALL_end() NSNSCALL_savepoint() NSNSCALL_reset() を呼び出してください(呼び出し先で内部処理が実行される)。たとえばNSLaデフォルトのloadコールバックは function NSCALL_load() NSNSCALL_load() end と定義されています。 |
NScall_close()
| 終了前にNSNSsafeend=true と代入しておくか、NSEnd(true) と終了してください。 |
NSCALL_text0() |
文頭で呼び出されます。 文字列を返すとテキストがすり替わり、falseを返すとテキスト表示を中止します。 |
NSCALL_tag() |
文頭で呼び出されます。 NSCALL_tagにラベル名または関数が代入されていた場合、 pretextgosub/zenkakko命令相当の処理を期待してタグ内部のテキストが渡されます。 ※Nスク本来の挙動(クリック待ち毎の判定)と異なるため注意してください。 |
NSCALL_textは使用されず、呼び出されません。入力待ちをカスタマイズする関数としてNSNSclickwait()
が用意されています[2.6.6]。
■「NScripter風に動く」スクリプトを新規に作成する場合
■既存のスクリプトをNSLaに対応させたい場合(NSLaの組み込み)
■NSLua主体のプロジェクトにNSLaを組み込む手順に関しては、[3.1.1]を参照してください。
;以下のように書く
;[[
ここから
ここまで
;]]
; [[
このように
解除の切り替えが容易
;]] 複数のコメントアウトを入れ子にもできます。 ;[[
入れ子にする場合は
;[=[
このように任意個のイコールを挟むことで
;]=]
多重に記述できます。
;]] Lua風の書き方(--、ハイフン2つ)も利用できます。 --このように
--[[
まったくのLua風な表記も
--[==[
通るように
--]==]
なっています。
--]]
同梱ファイルをそのまま利用できます。
dllフォルダ
NSLaを含むライブラリが入っています。
UIフォルダ
NSLa_novel(ノベル用追加ライブラリ)がセーブ/ロード画面やオプション画面、バックログやテキスト・テキストウインドウに用いる素材が入っています。
lua51.dll/lua5.1.dll
dllフォルダ内の一部プラグインが利用しています。
system.lua
「NScripterスクリプトと同じように」[3.1.2]NSLaを呼び出し、実行するコードが記述されています。
000.txt〜255.txt
ここに拡張スクリプトの定義節・実行節を記述します。
表に出しておきたくない場合、テキストアーカイブではなくns2ファイルアーカイブに格納してください。
00.txt
上部メニューの設定(insertmenu/resetmenu等)とレイヤープラグインの定義はこちらに記述します。
最小構成例では
・1行目の初期定義
・上部メニューの非表示
・NSLaの初期実行(NSCOM_main()の呼び出し)
を行っています。NSLaではほとんど使用しませんが、NScripter本体の動作安定に不可欠です。
コンバートツールで自動変換されない内容について、手作業で確認修正する必要があります。
いくつかある非互換性にも注意が必要です。
適用したいスクリプトが以下の条件にあてはまっていないか確認してください。
★コンバートツールの警告検知機能をもう少し更新する
★システムカスタマイズ関連の処理について
システムカスタマイズ(※)している
(※…クリック待ちの乗っ取り)手作業で修正する必要があります。
NSLaではクリック待ちの乗っ取り方が異なります。defsub命令がNSLaと衝突している
修正する必要があります。 既にNSLuaを利用している
コールバックやluasub命令の衝突を確認する必要があります。 自動変換できない非互換性を含む
手作業でスクリプトを修正する必要があるかもしれません。 NSLa非対応文法(※)を含む
(※…主にテキストボタン)NSLaを組み込むことはできません。 1.3.2 最小構成例の起動
●00.txt…NSLa読み込み前に読み込まれる領域です。
前項[2.1.1]で説明した通り、00.txtでは
@初期定義(;$V4095G2000S640,480L10000)
A上部メニューの操作
Bレイヤープラグイン定義
CNSCOM_main関数の呼び出し
のみを行っています。
●000.txt…「定義節で行うべき処理」のほとんどが記述されています。
*defineからreturn(game相当の離脱処理)までの処理に相当します。
●100.txt…「実行節」の例です。全画面ウインドウと下部ウインドウ両方の構成例を示しています。
*start以降の処理に相当します。
1.3.3 スクリプトの実行(大文字・小文字に関する注意)
原則として小文字での命令記述が必要です。
(コンバートツール[2.17]は自動で変換を行います)。
2.1 注釈(コメントアウト)
一般的なプログラミング言語と同等の機能を追加でサポートしています。
2.1.1 複数行の一括コメントアウト
複数の行をまとめてコメントアウトできます。
2.1.2 Lua風のコメントアウト
(唯一の例外として、「%0--」
のような記述は「dec %0」
の略記と見なされます[2.3.4]。)
;パターン1 IF (条件式の否定) :skip (以下の処理をちょうど飛ばすだけの行数) 〜条件を満たした場合の処理〜 IF (条件式の否定) :skip (以下の処理をちょうど飛ばすだけの行数) 〜条件を満たした場合の処理〜 ;パターン2 IF (条件式) :〜条件を満たした場合の処理〜 :goto *label IF (条件式) :〜条件を満たした場合の処理〜 :goto *label *label ;パターン3 IF (条件式の否定) :jumpf 〜条件を満たした場合の処理〜 ~
NSLaの条件分岐は大きく文法が追加され、一般的なプログラミング言語のように複雑な分岐を記述できます。
NSLaにおけるifの最も基本的な形は以下の通りです。
「if節の処理を複数行に分けて書ける」点が素のNScripterとの大きな差です。
if(条件式) 〜〜〜〜 条件を満たした場合の処理(何行でも) 〜〜〜〜 end
条件式にはNScripterおよびLuaとほぼ同じ演算子を使用できます。
(実際には条件式の記号を内部で置換し、Luaコードとして真偽を判定します。)
【>】 【>=】 【<】 【<=】 | 「大小(または辞書順)の判定」 |
【==】 | 「等しい」 |
【~=】 【!=】 【<>】 | 「等しくない」 |
【and】 【&&】 | 「かつ」 |
【or】 【||】 | 「または」 |
【(〜) 】 | 「括弧内の判定結果(真偽)を一つの項と見なす)」 (多重に記述可能) |
【not hoge】 | 「否定(項hogeが偽である)」 |
【+】 【-】 【*】 【/】 | 数値の加減乗除を行います。 |
【%】 | 剰余(割った余り、mod)を計算します。 数値変数と区別するために%の直後には空白を入れてください。 |
【%変数名】 【$変数名】 | NScripter変数へアクセスします。 |
【変数名】 | Lua変数へアクセスします。 エイリアス参照ではない点に注意してください。 |
【fchk("ファイル名")】 | ファイルログを調べます。実際は演算子ではなく関数です。 |
【lchk("ラベル名")】 | ラベルログを調べます。実際は演算子ではなく関数です。 |
if(1) if(2) if(3) 〜〜 end end end
「else
」は「直前までの条件式を満たさなかった場合に以下を処理する」を意味する予約語です。ifと組み合わせて使用します。
if(条件式) 満たした場合の処理 else 満たさなかった場合の処理 end
「elseif
」または「elif
」は「直前までの条件式を満たさなかった場合のみ追加で判定する」を意味する予約語です。ifやelseと組み合わせて使用します。
言語によって表記が様々であるため方言として二語を予約していますが、基本的にはどちらか一方のみ用いてください。
if(条件式A) Aを満たした場合の処理 elseif(条件式B) Aを満たさず、Bを満たした場合の処理 elseif(条件式C) A・Bを満たさず、Cを満たした場合の処理 else どれも満たさなかった場合の処理 end
outif 離脱するネスト数
で最大n個のネストをまとめて離脱します。if(1) if(2) if(3) outif --ここに書くと end end end --ここへ飛ぶ if(1) if(2) if(3) outif 2 --ここに書くと end end --ここへ飛ぶ end
switch文はNScripterにもLuaに備わっていない構文ですが、多くの言語で採用されています。 基本的には「[switchの値]==[caseの値]を満たした最初にcase文」以下のネストだけを実行します。 条件分岐のための構文であり、if〜elseif〜else〜end節に似た機能を持ちます。
switch(%0) ;単純な指定(a==x) case 0 %0==0であった場合に読まれます。 case "あいうえお" %0=="あいうえお"であった場合に読まれます。 case %1 %0==%1であった場合に読まれます。 case abc %0==abc(半角文字はLua変数と見なされます)であった場合に読まれます。 ;複数条件の一本化(a==x or a==y or ...) case 1 case 2 複数のcaseを一つの実行分に紐付けることもできます(条件式のorに相当)。 この場合は%0==1または%0==2であった場合に読まれます。 case 1,2 カンマ区切りにすることで、複数の条件を一つのcaseにまとめても構いません。 ;値の範囲指定(x<=a and a<=y) case 3 to 10 3<=%0<=10であった場合に読まれます。 ;演算子(大小比較) case >=100 %0>=100であった場合に読まれます。 ;他の条件を一切満たさなかった場合(else) default 条件を満たすcaseが一つもなかった場合に読まれます(elseに相当)。 default節は省略可能です。 caseとdefaultの記述順は自由です。 caseとdefaultを紐付けても構いません。 ;おまけ case あいうえお 生の全角文字に限り、""を省略しても構いません。 end
case節の中でbreakが呼ばれた場合、またはcase節の末尾に達した場合はswitch節を終了します。fallthrough
が記述されていた場合のみ、直後のcase/default節を続けて実行します。
switch(str) case "aaa" 何らかの処理 末尾到達でendまで離脱する case "hogehoge" 何らかの処理 fallthrough case "piyopiyo" "hogehoge"からも続けて実行される end
小文字で構文を記述するNSLaにおいて、唯一の例外がIFです。
IF(条件式) :満たせば以下を実行、満たさなければ次行へ移動
なお、IFに続く列はネスト内部とは見なされません(ネストが加算されません)。
;ネスト0 if(条件式A) ;ネスト1 Aを満たした場合の処理 if(条件式B) ;ネスト2 Bを満たした場合の処理 IF(条件式C):outif 1 Bを満たした場合の処理の続き end ;Cを満たした場合ここに離脱する end
forの管理変数は暗黙的に(疑似)ローカル化され、for離脱時に元の値へ戻ります[2.5.4]。
NScripterや多くの言語同様に、breakによってループを離脱できます。
★gosub・gotoの注意(ローカル変数、var変数)
;NScripter変数で管理 for %0=1,5 ループ内部の処理 next --Lua変数で管理(離脱時には元の値に戻る一方、内部的にはグローバル変数を利用する点に注意してください!) for i=1,5 do ループ内部の処理 end
「to」「step」ではなくカンマで区切ります。ステップは省略できます。
★もっといろいろ省略できる
ループ末尾はnext(NScripter風の表記)でもend(Lua風の表記)でも構いません。nextを用いれば対応関係が厳密になり相対的に記述ミスを検出しやすくなります(endはifネスト[2.7.1]の末尾にも用いられるため)が、Luaに慣れたユーザーにとってはend表記の方が自然に使えるかもしれません。
条件式の直後には、必ず「改行」「:
」「do」のうちいずれかを記述する必要があります。
管理変数にはNScripter変数、Lua変数どちらを使っても構いません。
for i in tab do alert(tab[i]) -->変数tabの中身を次々表示する(順番は保証されない) end
for k,v in tab do --何らかの処理 end for k,v in pairs(tab) do --何らかの処理 end
--pairs以外のイテレータも使用できます。 str="1=Tanaka,2=Saitou,3=Gotou,44=松尾芭蕉" for num,value in string.gmatch(str,"(%d+)=([^,]*)") do alert(num) -->"1"→"2"→"3"→"44" alert(value) -->"Tanaka"→"Saitou"→"Gotou"→"松尾芭蕉" end ★ただし管理変数は2つまでしか記述できず、 出てくる値は事前に全て評価される(ループ途中での変更は無視される) ★困るようなら複数行埋め込みLua使ってね
--10回繰り返される %0=0 while(%0<=10)do %0++ %0回目のループです end --中身は読まれない %0=0 while(-1>999)--doはあってもなくてもいい -- end --無限ループ %0=0 while(true)do %0++ %0回目のループです end
--10回繰り返される %0=0 repeat %0++ %0回目のループです until(%0>=10) --一度だけ実行される repeat 一度だけ実行されます until(true) --無限ループ repeat %0++ %0回目のループです until(false)
jumpf/jumpb
の飛び先に名前を付けることができます。jumpf テスト ~ ここには飛ばない ~テスト ここに飛ぶ
goto
と拘束の弱いラベルであるかのように動作するため、NSLaでは~(チルダ)
をローカルラベルと呼ぶことにします。
例外的な挙動として、jumpf/jumpb
側で名前を指定しなかった場合、名前を問わず、最も近い場所に見つけたローカルラベルにジャンプします。
「無名のローカルラベルに絞り込んで飛ぶ」という動作ではないため注意が必要です。
jumpf ~あいうえお ここに飛ぶ ~ ここではない
(goto/gosub/jumpf/jumpb)
は飛び先がネスト外(if
の外部)であることを期待するため、
続けてendを見つけるとデフォルトではエラーを吐きます。
この制約は、ネスト内部でスクリプト位置を移動したい場合において影響してきます。
部分的にネストを飛び出すにはoutif命令[2.7.3]を使ってください。
同一ネスト内でジャンプを行う必要がある場合、skipを用いてください。skipは同ネストへの移動を期待します。
動作オプションをNSNSjumptozeronest=nil
と設定すると、
「jumpf/jumpb
の飛び先は同じネストであることを期待する」と挙動を変更できます。
そうした場合は「ネスト内ジャンプにskip/jumpf
、ifネストからの離脱にoutif
」と役割分担できますが、
一方でjumpf時にネストがずれてしまってもその場でエラーが出ず、skip同様にバグの特定が難しくなります。
以上の問題から、デフォルトではtrue(jumpfにはネスト外ジャンプを期待する)が設定されています。
NSLaにとって、すべてのラベル≒サブルーチンは同時に関数でもあります。
全てのラベルはgosubから引数を受け取り、returnから値を返すことが出来ます。
引数・受け皿・戻り値ともに、記述ルールは代入略記の[2.3.1]左辺・右辺に準じます。
gosub *テスト("あいうえお",15) gosub *テスト "あいうえお",15 ------------------- *テスト(hoge,fuga) $hoge,%fugaが渡されました。@ return
受け皿は暗黙的に深度依存ローカル変数[2.5.3]と見なされます。上記の例ではラベル先頭でvar hoge,fuga="あいうえお",15
gosub *テスト("あいうえお",15) getret($0-1) 「$0」「$1」が戻ってきました。 ------------------- *テスト(hoge,fuga) $hoge=$hoge+"かきくけこ" return $foo,%fuga*3
$0=*テスト("あいうえお",15) 「$0」「$1」が戻ってきました。 ------------------- *テスト(hoge,fuga) $hoge=$hoge+"かきくけこ" return $foo,%fuga*3
●NSLaにおけるコルーチン
「中断できるサブルーチン(gosub呼び出し)」と言い表せます。
gosubの場合 |
return後、定義していたローカル変数の情報は破棄されます。 次に呼び出した場合、またラベルの先頭から再開されます。 |
コルーチンの場合 |
yieldで中断している間、ローカル変数の中身は記憶され続けます。 次に呼び出した場合、中断(yield)した場所の直後から再開されます。 |
●指定したラベルをコルーチンとして準備する命令…setcoroutine "*ラベル名"(,"コルーチン名")
setcoroutine *ラベル名,*コルーチン setcoroutine "*コルーチン" ;名前省略時はラベル名=コルーチン名 setcoroutine "コルーチン" ;*はあっても無くてもよい $0="コルーチン" :setcoroutine $0
●生成済みのコルーチンを呼び出す(再開する)命令…resume "コルーチン名"(,"任意個の引数")
;●実行/再開する resume *コルーチン ;●値を渡しつつ実行/再開する resume *コルーチン("値1","値2") ;●実行/再開し、yield/return後に返り値を受け取る $ret1,$ret2=resume *コルーチン resume *コルーチン :getret($ret1,$ret2) ;●値を渡しつつ実行/再開し、yield/return後に返り値も受け取る $ret1,$ret2=resume *コルーチン("値1","値2") resume *コルーチン("値1","値2") :getret($ret1,$ret2)
●現在のコルーチンを中断する命令…yield "戻り値"
;※yieldは「コルーチン内部」「コルーチン内部からgosubした場所」どちらにも記述できます。 ; つまり、コルーチンからgosubした先はコルーチン内部と見なされます。 ;●中断する yield ;●呼び出し元に値を返しつつ中断する yield "値1","値2" ;●中断し、resume後に値を受け取る $val1,$val2=yield() ;●呼び出し元に値を返しつつ中断し、resume後に値も受け取る $val1,$val2=yield("値1","値2") ;●コルーチンを完全に終了させる場合はreturnで戻る return "値1","値2"
NSNScheckcoroutine("コルーチン名")
checkcoroutine("コルーチン名")
local state=checkcoroutine("コルーチン名") alert(state)
以下のうちいずれかの値が戻ります。"sleep" | 作った直後、またはyieldで中断している休眠中のコルーチン。 Luaの"suspended"に相当 |
"running" | 現在実行中のコルーチン。 コルーチン内部から自分自身をチェックした場合に返る値 |
"parent" | 現在のコルーチンが「コルーチンから多重に呼び出されたコルーチン」であった場合、 かつ「親である(呼び出し元の)」コルーチンをチェックした場合に返る値 Luaの"normal"に相当 ※構造がややこしくなるため、むやみにコルーチンから別のコルーチンを呼び出すべきではありません。 |
"dead" | 未生成、または終了したコルーチン。 コルーチンの先頭深度からreturnで戻った。 |
●実行の流れ
「関数を中断/再開する」用法
;@ラベルをコルーチンとして登録する setcoroutine "*ラベル","コルーチン1" ;A登録したコルーチンを呼び出す resume "コルーチン1" --gosubに相当 ;C中断後、〜何らかの処理〜 ;D中断したコルーチンを再開する resume "コルーチン1" --yieldした場所から再開 --------------------------- ;B実行されるコルーチン *ラベル let str="★" $str -->★ ;ここで中断 yield ;E次回はここから再開する $str -->★ ローカル変数の内容は保持されている return --コルーチンの終了。ここで$strの内容は破棄される
「適宜中断を挟みつつ回数が多いループを並列実行する」用法--メイン処理ループ repeat let str if(checkcoroutine("コルーチン")=="dead") 〜何かの処理〜 setcoroutine "*ラベル","コルーチン1" else 〜何かの処理〜 end $str=resume "コルーチン1" until(false) --------------------------- *ラベル for i=1,10000 do ;★とても時間が掛かるループ処理 yield end return "done"
●その他の補足
※コルーチン内部ではセーブポイントを更新できません
また、loadgameすると全てのNSLaコルーチンは失われます。
セーブ/ロードが絡まない処理にのみ用いてください。
※速度は期待しないでください
「拡張スクリプト自体の重さに加え、強引な実装(出入りするたびにローカル変数を定義し直し、値を再代入)をしています。
Q.Luaのコルーチンで良くない?
A.使えるならばその通りです。「時間のかかる計算処理をバックグラウンドで行う」
といったコードに対してコルーチンは有効に機能しますが、
時間のかかる計算処理はそもそもLuaで行うべきなのです。
NScripter命令・Luaコード・テキスト等、「NSLaにおいて単一の命令と解釈される文字列」も仕込む事が出来ます。
trap[bg "foo.png",0]
trap[$0="あいうえお"]
trap[alert("あいうえお") local a=12 alert(a)]
trap[$0="あいうえお"]
仕込んだ文字列の変数は(trapを定義した瞬間ではなく)発火した瞬間に評価されます。
$0="あいうえお" trap[gosub *テスト($0)] $0="かきくけこ" trapさせます→@しました¥ ---------------------- *テスト(str) $strが渡されました。@/ -->かきくけこが渡されました。 return
trap "〜" r_trap "〜" lr_trap "〜"
"
」を含む命令に問題が出るため、対策として以下の3つが用意されています。trap[〜] rtrap[〜] lrtrap[〜]
off,stop,resume
も使用できます。trap off trap stop trap resume
現時点でtrap2とlr_trap2には対応していません。これはNSLaにおける非互換性の一つです。
【注】 ※ボタンウェイトおよび標準selnumの実行中はtrapが発火しない(本来の挙動と同様)。 ※trapで割り込む命令にgosubを指定した場合のみ、やや特殊な挙動になる @「飛び先からreturnで戻るまでずっと」「trap発火中」と見なされる。 A「trap発火中は右クリックメニューが発火しない」 「独自右クリックメニュー(※1)の中ではtrapが発火しない」 というセーフティ機能が存在する。 ※1…独自右クリックメニュー rgosubに近い機能。 設定例は以下。 NSNSrclickmove="*hoge" --*hogeにgosub NSNSrclickmove="rmenu" --systemcall rmenuに相当 NSNSrclickmove="" --何もしない NSNSrclickmove=hoge() --関数hoge()を呼ぶ。【 (関数を定義してから設定する必要があります) 】 ただし、ラベルへのgosubを設定する場合はselnum命令自作が推奨される。 標準selnumでは複数の問題を起こす(※2)。 ※2…独自メニューにラベルgosubを設定し、標準のselnumを使用した場合に起きる問題 selnum内で独自右クリックメニューを発火させた上で、 ・飛び先ラベルでsystemcallを呼んでも反応しない ・飛び先ラベルでloadgameするとバグる(実行中だったselnumが消せずに残ってしまう) ・飛び先ラベルで多重にselnumすると元の選択肢が化ける ・飛び先ラベルでテキストを表示すると選択肢に重なって表示される 2番目の問題が比較的致命的であるため、selnumの自作を推奨することになる。
mov %0,10
の略記です。%0=10
●まとめて代入%0=10 :$0="あいうえお"
の略記です。%0,$0=10,"あいうえお"
●右辺が余った(項が左辺より多い)場合%0,%1=0,1,2,3 -->%0=0,%1=1
●右辺が不足している(項が左辺より少ない)場合%0,%1,%2,$3=0,1 -->%0=0,%1=1,%2=0,$3=""
●左右の辺で型が合わない場合%0,%1,$2="123","あいうえお",456 -->%0=123,%1=0(変換失敗),$2="456"
●左辺の範囲指定%0,%1,%2
の略記です。%0-2=0,1,2
●左右の範囲指定%0-2=%10#12
●同じ型の変数を連続で指定する場合($%)
を省略しても構いません。%0,2,$10=0,2,"あいうえお"
●文字変数に全角文字を代入する場合$0=あいうえお
●右辺に四則演算を記述する場合()
で括る必要があります(処理順の都合)。%0=(15-1)*3/4 -->10 $0="あいう"+(15-1) -->"あいう14" $0="あいう"+15-1 -->エラー!
●代入略記における結合"+"(NScripter式)
と".."(Lua式)
のどちらを用いても構いません。$0="あ"+"い".."う" -->"あいう"
代入処理は、内部的にはNSNSmov()関数への文字列渡しです。
右辺に値や変数ではなく「*」を指定すると、当該変数の初期化(0または"")を意味します。
右辺不足時に実際に補われているのはこの記号です。
%0,$0=*,*
右辺の変数/値/*の直後には、特別な末尾オプションを付加できます。
%0! | 左辺の残った項すべてに同じ値(%0 )を代入します。 |
%0@ | 左辺の当該項以降に同じ型(数値変数)が続く限り、同じ値(%0 )を代入します。 |
%0@数字 | 指定した個数分の項に一括代入します。"あ"@3 は"あ","あ","あ" と等価です。 |
# @ ! 表記いずれかの後に追加のオプション「?(数字)」を指定できます。
「?(数字)」を指定すると、連続して代入される値が(数字)または1
ずつ増減します。負の値を指定しても構いません。
0@3?10 は 0,10,20 と等価です。
文字に対してこのオプションが指定されると、末尾に半角で番号を付与します。
【"ぬ"@3?】 は 【"ぬ1","ぬ2","ぬ3"】 と等価です。
【"ぬ"@3?4】 は 【"ぬ4","ぬ8","ぬ12"】 と等価です。
# @ ! を記述せずに?(数字)のみ指定した場合、「@?(数字)」の略記と見なします。
(左辺の型が変わるまで連続代入+(数字)ずつ増加or連番付与)
同様に、!(数字)は!?(数字)の略記と見なされます。
(左辺の残り全てに連続代入+加算または連番付与)
add,sub,mul,div,mod
に略記が用意されています。
%0+=100
はadd %0,100
の略記です。
%0-=100
はsub %0,100
の略記です。
%0*=100
はmul %0,100
の略記です。
%0/=100
はdib %0,100
の略記です。
%0%=100
はmod %0,100
の略記です(見づらいため注意)。
このうち+=
のみは文字変数に対しても適用できます。他はエラーを返します。
剰余(割ったあまりの数、mod)を%
で表すのはLua風の表記です(実際のところ、計算はLuaに投げています)。ところが%
は数字変数の語頭でもあるため、NSLaでは剰余と数字変数の記号が衝突しています。
この衝突は、以下のような記述(数字変数による変数番号の指定)において、意図に反した動作を招くおそれがあります。
;【問題1】 %0=3 if(10 % %0==1) -->10 % 3==1 ;条件を満たす end if(10%%0==1) -->103==1 ;条件満たさず end ;【問題2】 %3=5 if(10 % 3==1) ;条件を満たす end if(10%3==1) -->105==1 ;条件満たさず end
これはif/for条件節が内部的にLuaスクリプトとして処理される仕様に由来する問題です[2.16.5]。「` (Windowsキーボードではshift+@)」
を「(数字変数ではない)%」
として解釈させる回避手段も用意されていますが、剰余を扱う際は極力半角スペースやタブを間に挿入すべきです。
一方代入略記[2.3.1]では独自に項を切り分けて処理されるため、剰余の直後に数字変数を記述しても正しく解釈されます。
%1=10%%0 -->%1=1
%0++
はinc %0
の略記です。%0--
はdec %0
の略記です。
%0-- %0--;あいうえお %0----あいうえお
| 「%0 を1 減少させる(+注釈)」を表します |
%0 -- %0--あいうえお %0 --あいうえお
| 「%0 というテキスト(+注釈)」を表します |
実際はLuaのグローバル変数(テーブル型)を埋め込んで使用しているだけです。[2.16.2]したがって以下の記述は読み飛ばして問題ありません。
NSLaで配列を用いるには、
foo={}
のように定義を行う必要があります。このとき使用する名前は原則としてエイリアスと重複しないようにしてください。
配列に値を代入する場合は
foo[1]="bar"
のように表記します。代入する値の型は文字でも数字でも構いません。
命令の引数として値を呼ぶ場合はlsp 1,&foo[1],0,0
のように&
を付けて表記します。
この例ではfooという配列変数の1番に代入された値を呼び出しています。
添え字(上記例でいう1
の部分です)に文字列を使うこともできます。&foo["あいうえお"]
のように記述します。一般に、これは連想配列と呼ばれます。
★多次元配列の定義方法 dimの場合★
★値の初期値は0ではない(nilである)点
★1オリジンにすべきである点
★保存方法
*label let hoge --現在のifネスト/gosub深度以下で$hoge,%hogeを使用できる --何か処理をする return
一時的に値を格納したいだけの場合など、コード全体から見えなくてもよい変数を定義節でいちいちnumaliasせずともよくなります。local $0 --$0が一時的にローカル変数化
NSNSclickwait(frommyselnum,voicewaiting,notfromtextwait)
この関数は俗にシステムカスタマイズと呼ばれる処理のうち、
「クリック待ち時の動作を変更する」
に相当します。
つまり、「テキスト中のクリック待ち(@¥)」または「自作selnum」から呼び出され、以下の処理を担う関数です。
@(必要であれば)常駐ボタンを定義する
A(bexec命令で)クリックやキーやボタン押下等の「入力」を検知する
B結果(何が入力されたのか)をNSLaに返す
NSLaデフォルトの関数は実装例としてのサンプルを兼ねています。
NSLaでは、単に常駐ボタンを実装するだけなら常駐ボタン設置関数[2.6.5]を使用して配置・取得する事ができます。
その場合クリック待ち関数を修正・自作する必要はありません。つまり、この関数を改変する場合は
「有効なキー入力を追加・変更したい(Zキーで読み進めたい、F12リセットは要らない等)」
「ゲームパッド主体のジャンルにおいて、テキスト表示時にゲームパッドの入力を検知したい」
どちらかの状況にある事が想定されます。
■引数について
第一引数(bclearを行わずtrapを発火させないフラグ)
自作selnum等から呼び出して使う場合はtrueを渡してNSNSclickwait(true)とします。これはtrapの発火を防ぐためです。
第二引数(オートモードのボイス待ちミリ秒)
オートモード[2.12]中であり、かつボイス待ちを行う(クリック待ち関数を再帰的に呼び出す)場合に次回確認までの待ち時間(ミリ秒)を渡します。デフォルト関数では100ミリ秒ごとにボイス再生の有無を判定しています。
外部からの呼び出し時は(何か特別な演出を用いるのでない限り)使用しません。
第三引数(常駐ボタンを起動しないフラグ)
判定に常駐ボタンを用いたくない場合はtrueを渡します。これは自作バックログからの呼び出し等を想定しています。
これらの引数は全てデフォルト関数内で利用しています。したがって、関数を自作する場合はこの限りではありません。
第四引数(テキストクリック待ちから呼ばれた場合にクリック待ち記号が渡される)
NSLaデフォルトのオプション環境では「@ \ @ ¥」の四種類いずれかが渡されます。
一部のテキスト系命令で自動挿入されるクリック待ちについてはその機能に準じます。
(つまり ("@" または "@")で一致判定を行えばとりあえず問題ありません。)
これはispage命令相当の機能ですが、標準で定義している関数では特に利用していません。
■NSLaデフォルトの関数で行っている処理
NSLaにおいて、デフォルトの「クリック待ち」が担う処理は以下の3つです。
@(必要であれば)常駐ボタンを定義する | デフォルトの関数では常駐ボタン設置関数[2.6.5]を(定義されていれば)自動で呼び出しますが、 そちらで定義したくない特別な理由があればここで直接定義しても構いません。 | A(bexecを用いて)クリックやボタン等の入力を取得する | bexecそのものです。特別な理由があれば代わりに旧ボタンを用いても構いませんが、様々な理由(反応速度の差、常駐ボタン設置関数との兼ね合い等)から推奨はしません。NSLaはテキストボタンを使用できない[2.18.2]点にも注意してください。 | BNSLaに結果を返す | bexecの結果に応じて起こすべきアクションを決めます。以下で詳しく解説※します。 |
●(常駐)ボタンを押したか?
常駐ボタン設置関数を呼び出してチェックしています。
特別な理由がない限りは常駐ボタン関数に任せてよいはずですが、必要であれば関数内に直接記述しても構いません。
以下に該当箇所のコードを(若干書き換えつつ)引用します。
--100番を押していた場合 if(bkekka=="S100")then --●何らかの入力を検知させたい場合 --右クリック扱いしたい場合 --bkekka="RCLICK" --ホイール上入力扱いしたい場合 --bkekka="WHEELUP" --スキップしたい場合のみsystemcallではなくこちらで行います --bkekka="SKIP" --●systemcallしたい場合(標準メニューを呼ぶ等) --NSNSrclickmove_systemcall="windowerase" --※倍角テキストモード[2.6.8]中にデフォルト回想を呼ぶとエラーを招くため、 -- lookbackのみは以下のように書く必要がある -- if(not NSNSbsmm_mul)then NSNSrclickmove_systemcall="lookback" end --●任意の命令で割り込みたい場合 (基本的には自作selnumからの呼び出しで発火させるべきではない) -- NSNSdotrap=NSNStrap --trapの発火 -- NSNSdotrap=NSNSrtrap --r_trapの発火 -- NSNSdotrap=NSNSlrtrap --lr_trapの発火 -- NSNSdotrap='caption "常駐ぼたんくりっく"' --割り込む命令を直接指定する例 -- NSNSdotrap='押 し た な ?' --テキストも通る(使い所はやや乏しい) -- NSNSdotrap='%20=57' --NSLa特有の略記やLuaコードも通る -- NSNSdotrap='*何らかのラベル' --ラベルを記述するとgotoジャンプする -- NSNSdotrap='gosub *trapによるgosubジャンプ' --[[ 【割り込む際の注意】 ※ボタンウェイトおよび標準selnumの実行中はtrapが発火しない(本来の挙動と同様)。 ※trapで割り込む命令にgosubを指定した場合のみ、やや特殊な挙動になる @「飛び先からreturnで戻るまでずっと」「trap発火中」と見なされる。 A「trap発火中は右クリックメニューが発火しない」 「独自右クリックメニュー[2.11.4](※1)の中ではtrapが発火しない」 というセーフティ機能が存在する。 詳しくはtrapによる命令割り込み[2.11]を参照。 --]] --200番を押していた場合 elseif(bkekka=="S200")then end --常駐ボタン関数[2.6.5]がセットされていたらそちらでも判定する if(not notfromtextwait and type(NSNSpermanentbuttons)=="function")then local newkekka=NSNSpermanentbuttons("check",resnum) if(newkekka)then bkekka=newkekka end end
●オートモード関連の処理
オートモード[2.12]中であるか、オートモードの切り替わりが発生するかどうかを判定します。NSLaにおけるオートモードの扱いは当該項目を参照してください。
if(bkekka=="AUTO" or (bkekka=="TIMEOUT" and NSNSautomode))then if(bkekka=="AUTO" and NSNSautomode)then NSNSautomode=false else bkekka="WHEELDOWN" NSSystemCall("automode") --「ボイス鳴ってる間は待つ」の処理(自身を再帰的に呼び出す) --NScripterDS.dllまたはNSOgg2.dllが前提、前者はろだのラッパー(nds関数)利用が前提 if((type(NSNSexistogg2)=="string" and type(nds)=="function" and nds("isplaying",NSNSexistogg2)==1) or (NSNSexistogg2 and NSOggIsPlaying(0)))then return NSNSclickwait(frommyselnum,100) end end end
●デフォルトおよびNSLa追加分の入力判定
以下の判定を行っています(コードは省略)。
クリック(相当のキー)
右クリック(相当のキー)
ホイール上(回想呼び出し)
F11(デバッグコンソール呼び出し)
F12(リセット)
単に特定のキーをクリック扱いする場合などはここに加筆すると良いでしょう。
ツクール系列に揃えたZキー読み進めなどを足しておくと便利かもしれません。
●オートモードを止める入力だった場合はskipoffする
●結果をreturnする
bkekka(文字),resnum(数字)
の二つを返しています。
以上がデフォルト関数で行われている処理です。
大多数を占めるであろうノベル用途では、デフォルト関数(と常駐ボタン設置関数[2.6.5]の調整)でおよそ事足りると思います。
■関数を改変すべき場合
ここで想定するシチュエーションは
「ゲームパッド主体のジャンルにおいて、ゲームパッドの入力をテキスト送りに使いたい」
というものです。
たとえばNScripterで弾幕STGを制作するならば、当然プラグインを用いてゲームパッドに対応するべきです。
しかしデフォルトのクリック待ち(およびbexec)はパッドの入力を検知出来ません、標準テキストの利用に問題が生じます。
その場合標準テキストを諦めるか、検知可能なbexecを自作するか、この関数を改変または自作する必要があります。
繰り返しになりますが、この関数を自作する場合、最低限行うべき処理は以下の3点です。
@(必要であれば)常駐ボタンを定義する
Aクリックやキーやボタン押下等の「入力」を検知する
B結果(何が入力された事にするか)を返す
入力検知、つまりAの箇所を改変する場合、
btimeを短く設定した上でbexecとプラグイン側の入力チェックを同時に行う
bexecを使わず自力で入力を取得する
といった方法が考えられます。
text0 | テキストの先頭で呼ばれ、これから処理する予定の文字列が引数として渡されます。文字列を返せばそのテキストにすり替え、falseを返せばテキスト表示を中止します。 ★zenkakko、pretextgosubとの兼ね合い |
text | 呼ばれません ※クリック待ち時の挙動はNSNSclickwait()[2.6.6]で処理されます。 |
puttext |
非推奨です(テキストも: で区切れるため意味がない&テキストに伴うべき機能が一部働かない)
|
;■全て有効にする場合 ;fchk()用のファイルログ filelog "flogl.dat" ;lchk()用の既読ラベルログ NSNSkidokulabelpath="labell" ;fchk,lchkはLua関数として条件式内で呼び出します。 ;既読スキップ用のテキストログ NSNSkidokudatapath="kidokul" ;■全て無効にする場合 NSNSkidokulabelpath=nil NSNSkidokudatapath=nil
kidokumode 1
です。NSNSkidokudatapath="kidokul"
既読スキップを実行中、true実行のns関数[3.2.6]に遭遇した場合の挙動をオプションで指定できます。NSNSkidokutrue=true -- trueなら 「未読テキストとして読み込み、関数を抜けたタイミングでスクリプトやラベル共々破棄」 -- falseなら「全テキストを既読扱い(停止判定を省略)し、関数を抜けるまでスキップ続行」
なお、「false実行[3.2.7]で一時的に追加された仮想スクリプト」内部のテキスト既読情報は、当該関数を抜けたタイミングで一緒に破棄されます。 ★「スクリプト先頭からの行/列」で既読フラグを保存しています。filelog "flogl.dat"
より多くの命令や自作関数でログを追加したい場合、NSNSaddfilelog("ファイル名")
の形でログに追加できます。fchk("ファイル名")
の形を用います。
if(fchk("*ラベル名")) 既知のファイルです。 end
特殊文字構文として命令中に埋め込む場合、&(fchk("ファイル名") && "true.png" || "false.png")
のように用います。
lsp 1,&(fchk("ファイル名") and "true" or "false")+".png",0,0
両者の実体はLua関数です。特に後者は引数のように埋め込むLua[2.16.2]と三項演算の組み合わせです。fchk=NSNSfchk
と代入しようとします。--複数の引数を指定すると、複雑な判定を行うことができます。 NSNSlchk({"ファイル1","ファイル2","ファイル3"},true,true) --第1引数には文字列、またはラベル名文字列が並んだテーブルを渡します。 -- この文字列がNSNSlabellogテーブルに保存されているかをチェックします。 --第1引数がテーブル(=複数の値)だった場合、第2引数で判定方法を決めます。 -- 第2引数が真なら「ファイルのうちどれかが既知なら真(or)」で判定、偽なら「全て既知なら真(and)」で判定します。
--デフォルトで以下のファイル名が宣言されています。ラベルログが不要な場合は=nilとします。 NSNSkidokulabelpath="labell" --true(既定値)ならラベル名を暗号化して管理します(メモ帳などでログファイルを開いてもラベル名は読めません) NSNSfilelabellogangou=true
if節で既読判定を行う場合、lchk("*ラベル名")
の形で用います。
if(lchk("*ラベル名")) 既知のラベルです。 end
特殊文字構文として命令中に埋め込む場合、&(lchk("*ラベル名") && "true.png" || "false.png")
のように用います。
lsp 1,&(lchk("*ラベル名") and "true" or "false")+".png",0,0
fchk()と同じく実体はLua関数です。--複数の引数を指定すると、複雑な判定を行うことができます。 NSNSlchk({"*ラベル1","*ラベル2","*ラベル3"},true,true) --第1引数には文字列、またはラベル名文字列が並んだテーブルを渡します。 -- この文字列がNSNSlabellogテーブルに保存されているかをチェックします。 --第2引数がtrueならfalseログ(仮想スクリプト内でのみ読んだ事のあるラベル名)も既知と認めます(※)。 --第1引数がテーブル(=複数の値)だった場合、第3引数で判定方法を決めます。 -- 第3引数が真なら「ラベルのうちどれかが既知なら真(or)」で判定、偽なら「全て既知なら真(and)」で判定します。
※falseで多重実行したns関数内の仮想スクリプト[3.2.7]でのみ通過したラベルを記録するか否か、オプションで設定できます。NSNSkidokulabelstrict=false(既定値) --trueの場合、仮想モード中のラベルは完全に破棄される。 --falseの場合、仮想モード中のラベルはfalseとして保存され、lchk()の第二引数が真である場合のみ既知と見なす。
入力欄が空 | デバッグモードを終わります。 |
NScripter変数 |
変数の中身をウインドウ(タイトル欄)に表示します。$hoge -->$hogeの中身を表示 カンマ( , )で区切ることで複数の変数を表示できます。$foo,$bar -->$foo,$barの中身を表示 |
変数=値 | その場でNScripter変数に値を代入します。文法は代入の略記[2.3.1]に準じます。 |
ラベル名 | ラベルが存在すれば位置を、存在しなければその旨を教えてくれます。 |
Luaとして認識される文字列 | 入力内容をその場で実行します。 デフォルトではNスク変数らしき文字を変換します。 不要な場合は埋め込みLua同様「//」に続けてください。 |
「@」で始まる文字列 |
@以降の文字で命令割り込みを予約します。 入力内容はデバッグモードを抜けた瞬間に発火します。 また、特殊な制御コマンドとして 「@」現在の発火予定を表示 「@@」発火予定の消去 「@@@」実行予定の復元(デバッグモードに入る前の内容に戻す) 「@@@@」前回の入力内容を繰り返す が用意されています。 |
上記以外 | @を省略した記述(命令の割り込み予約)と見なします。 |
*start bg black,1 alert() このように :alert("「次のコロンまで」がコードの範囲と見なされます。") :local a,b a=1 b=2
エスケープ「::」/. ./複数行埋め込むこともできる 埋め込んだLua内部に拡張スクリプトを記述することもできる
長いコードを実行しても構いませんが、処理の末尾まで単一命令として扱われる(=NSLaが介在しない)点に注意してください。 F12キー(リセットボタン、命令の合間に判定が行われる)のレスポンスを考えるならば、「描画やアニメーションを挟む命令」の直後にコードを区切った方が賢明です。 加工するためloadstringを乗っ取っている点に注意bg ^^hoge^^,1 bg ^^foo..bar^^,1 bg ^^hoge="foo" return hoge^^,1
^^〜^^
内部には
テキストに埋め込む事もできます。
あいうえお^^kakikukeko^^さしすせそ@