システムカスタマイズについてダラダラやるよーその6

システムカスタマイズについてダラダラやるよーその5 - 永字八法の続き。
NScripterのシステムカスタマイズについて、1から話をしてみようと思う。その第6回目。
今回はボイスについて語る為に、その前にタグについて語る為に、その前に発言者欄について語るために、その前にテキストウィンドウについて。

テキストウィンドウ概要

setwindow命令は、その引数の多さからして、NScripterの初心者殺しとして有名な命令ですが、しかし小窓式のテキストウィンドウを実装するには、避けては通れない命令でもあります。
まず、テキストウィンドウには、色味型と画像型があります。
デフォルトでは、全画面色味型になっていますが、まあ、こんなのを実装しているゲームは今更ないんじゃないですかね。全画面色味型は、別名「かまいたちの夜」型とか私は勝手に呼んでますが、現在の主流は画面下方小窓の画像型ではないでしょうか。
画面下方小窓型の最近の仕様では、このテキストウィンドウそのものの不透明度を変更できるようにするのが求められる場合があります。「本当に?」とお疑いの方は、下の画像をご覧になってください。
http://sociorocketnews.files.wordpress.com/2012/08/sexymensswimmer11.jpg
一部の特殊な趣味の方は、「テロップが透明にならないかな……」とか思わなかったでしょうか。
おわかりですね。需要はあるんです。少ないかもしれませんが。
「テキストウィンドウはコンフィグにおいて半透明もしくは透明にできる(文字は除く)」と言う仕様は採用したいと思います。
ですが、NScripterのデフォルトのテキストウィンドウでは、これは実現できないんですね。
なので、次善策を取ります。テキストウィンドウとスプライトを重ねます。テキストウィンドウは完全透明で、文字だけを表示します。その下(重なり方で言うと画面奥)にスプライトでテキストウィンドウの小窓を表示する訳です。テキストウィンドウを消したり表示したりする時は、両方一度に操作するように、サブルーチンを組むのがよいでしょう。
あと、ついでに画面の大きさも設定しておきましょう。
画面の大きさはデフォルトでは、640×480ですが、現在の主流は800×600以上でしょう。ちなみに縦横のアスペクト比を変えることもできますが、スチル作成についても非常に大きな理論的転換を強いられるので、原画屋さんがうんと言わない限りは採用しないことをオススメします。いや、空間バランスとかがガラリと変わるんですよねえ。
ちなみに800×600が主流になってからも結構な時間が経ちますので、もしかしたら最近ではもっと大きなサイズを求めているユーザーも多いかも知れませんが、面積が増えればそれだけグラフィックの作成時間も伸びますので、いや、痛し痒しですね。

画面の大きさを変更

本稿では最終版(nscr.exeのサイズが1メガ超えてる奴)を使っていますので、多少遣り方が変わりました。
00.txtの一番最初の行に以下を書きます。

;$V2000G1000S800,600L10000
V
後の数字は、初期化する変数の数です。例では2000になっていますので、0〜1999番までの変数が使えます。
G
後の数字は、グローバル変数の最初の番号です。例では1000になっていますので、1000番からがグローバル変数です。*define節でのglobalon命令をお忘れなく。でないと有効になりません。
S
後の数字二つが、画面の大きさです。任意の数字を指定できます。例では800×600になっていますね。
L
後の数字が、使えるラベルの数です。任意の数字を指定できます。例では10000になっていますね。普通はこれだけあれば充分ですけど。

画面サイズは800×600にします。後々便利ですので、画面サイズも動的に取得しておきましょう。

スプライト番号の決定

まず、humanz命令を使います。humanzは立ち絵の表示される順番を制御します。
デフォルトは25500になっていて、25500番のスプライトのすぐ上(奥行きで言うと画面手前)に立ち絵が表示されます。
windowback命令は、デフォルトだと画面一番手前に表示されるテキストウィンドウを、humanzの位置まで奥に持っていきます。
デフォルトの25500の状態でwindowbackを使ったなら、

画面手前→24499番スプライト→テキストウィンドウ→立ち絵→25500番スプライト→画面奥

の順番になります。
なお、テキストウィンドウに文字を出力する段階では、テキストウィンドウの優先順位は必ず最高、つまり画面の一番手前になります。表示が終わった途端に、windowback命令やhumanz命令の影響を受けて奥に引っ込みます。
ここでは、テキストウィンドウに使われるスプライト番号をsp_textwindowのnumaliasに設定します。

	numalias sp_textwindow,%1:dec %1 ; テキストウィンドウ用のスプライト番号
	humanz sp_textwindow ; 立ち絵の位置を設定
	windowback ; テキストウィンドウをスプライトの位置まで引っ込ませる。

画像を読み込む

えーとまあ、言を翻すようですが、すぐに素材を用意できない場合は、塗りつぶし長方形を使うのがよいかと。
また、スプライトを使いますので、影響範囲の大きいcsp -1のような命令は今後一切使用しないようにしましょう。

; 素材が用意できるまではこれ使えよー。
lsph sp_textwindow,":c;>800,150,#999999",0,0

また、後々計算を楽にするために、スプライトのサイズも取得しておきましょう。

getspsize sp_textwindow,%textwindow_width,%textwindow_height

こうしておけば、画像を差し替えても、そこ以外にスクリプトを修正させる必要がなくなります。

位置を決める。

位置を決めやすいように、専用の命令を作りました。

news_position
第1引数と第2引数
座標を受け取る数値変数
第3引数と第4引数
親領域の広さ。普通は%window_width,%window_height
第5引数と第6引数
親領域の中で動かしたい子領域の広さ。
第7引数
子領域のx軸方向配置。-1は左に詰める。1は右に詰める。0は真ん中に配置。
第8引数
子領域のy軸方向配置。-1は上に詰める。1は下に詰める。0は真ん中に配置。

この命令を使えば、

	; テキストウィンドウの配置場所の決定
	news_position %textwindow_x,%textwindow_y,%window_width,%window_height,%textwindow_width,%textwindow_height,-1,1

最後の二つの引数が-1,1なので、テキストウィンドウが画面左下にぴったりくっつくような座標が計算されます。

フォントの設定

完全な理想を言うならば、フォントの大きさはユーザーがコンフィグで設定・変更できるのが最高です。
しかし一方でシナリオライターは、画面でどのように文字が配置されるか、改行を含めた視覚効果による演出を考慮してテキストを決めています。(そのはずですよね?)
ですので、文字の大きさの決定権はどちらが持つかと言うのは、難しい問題なのですが、NScripterの仕様上は固定サイズにするのが無難ですので、本稿では作成側が持つことにします。

	; フォントの設定
	mov %font_width,22
	mov %font_height,22
	mov %font_space,2
	mov %line_space,2
	mov %font_bold,0
	mov %font_shadow,1
	mov %font_ruby_width,11
	mov %font_ruby_height,11

ついでに、ルビも振るように設定しましょう。
なお、ここで決めた数値は、テキストウィンドウ以外にも、バックログの表示にも使うことにします。

実際の表示領域の設定

テキストウィンドウの画像は設定しましたが、実際にその画像の(0,0)から文字を表示する訳ではないですね。
つまり、オフセットを設定しなければなりません。

	; パディングの設定
	mov %textwindow_padding_top,8
	mov %textwindow_padding_bottom,8
	mov %textwindow_padding_left,12
	mov %textwindow_padding_right,12

テキストウィンドウの不透明度

グローバル変数で保存し、また、コンフィグで変更できるとします。

その他

なお、文字の表示速度の指定も必要ですが、それはコンフィグで容易に変わるものですので、別にtextspeedを実行することで解決させます。

ちょっといい話

erasetextwindow 0

エフェクト中もテキストウィンドウが消えないようにする命令。

まとめ

上記を反映したスクリプトは以下のとおり。

;$V2000G1000S800,600L10000
*start
	saveoff ; 完全手動セーブ宣言!
	kidokumode 1 ; 既読スキップモードを使用可能にする。
	gosub *if_in_advance ; 一度は実行しておく。
	gosub *text_lb_skip_start ; 一度は実行しておく。
	gosub *initialize_global ; グローバル変数の初期設定

	; 画面サイズを取得しておく、
	getwindowsize %window_width,%window_height

	gosub *initialize_textwindow ; テキストウィンドウの初期設定
	set_window ; テキストウィンドウ表示
click

(阿頼耶/あらや)(識/しき)@
あ\
い\
う\
え\
お\

end

*define

	; メニューバーを全消し
	deletemenu

	mov %0,100 ; numalias定義用一時変数
	mov %1,900 ; スプライト番号定義用の一時変数
	mov %2,1000 ; グローバル変数用

	; グローバル変数を使用
	globalon

	numalias global_first,%2:inc %2 ; グローバル変数の初期設定をしたかしていないかのフラグ。

	; bexecの結果を受け取る専用変数を宣言
	numalias bexec,%0:inc %0

	; システムカスタマイズの宣言
	textgosub *text_lb

	; 既読スキップ有りで。
	kidokuskip
	
	; isskipの結果を受け取る専用変数を宣言
	numalias isskip,%0:inc %0

	; ifの代替命令群の定義
	defsub if_str_goto
	defsub if_str_gosub
	defsub if_int_goto
	defsub if_int_gosub
	defsub notif_str_goto
	defsub notif_str_gosub
	defsub notif_int_goto
	defsub notif_int_gosub
	
	numalias if_a,%0:inc %0
	numalias if_b,%0:inc %0
	numalias if_label,%0:inc %0
	
	; 自動記録なし
	autosaveoff

	; 画面サイズ回り
	numalias window_width,%0:inc %0
	numalias window_height,%0:inc %0

	; setwindow回り
	numalias sp_textwindow,%1:dec %1 ; テキストウィンドウ用のスプライト番号
	humanz sp_textwindow ; 立ち絵の位置を設定
	windowback ; テキストウィンドウをスプライトの位置まで引っ込ませる。
	numalias textwindow_width,%0:inc %0
	numalias textwindow_height,%0:inc %0
	numalias textwindow_x,%0:inc %0
	numalias textwindow_y,%0:inc %0
	numalias font_width,%0:inc %0
	numalias font_height,%0:inc %0
	numalias font_space,%0:inc %0
	numalias line_space,%0:inc %0
	numalias font_bold,%0:inc %0
	numalias font_shadow,%0:inc %0
	numalias font_ruby_width,%0:inc %0
	numalias font_ruby_height,%0:inc %0
	numalias textwindow_padding_top,%0:inc %0
	numalias textwindow_padding_bottom,%0:inc %0
	numalias textwindow_padding_left,%0:inc %0
	numalias textwindow_padding_right,%0:inc %0
	
	numalias textwindow_area_width,%0:inc %0
	numalias textwindow_area_height,%0:inc %0
	numalias textwindow_col,%0:inc %0
	numalias textwindow_row,%0:inc %0
	
	numalias textwindow_alpha,%2:inc %2

	defsub set_window
	
	; テキストの表示速度
	numalias textspeed,%2:inc %2

	; 画面内の位置のNEWS指定
	defsub news_position
	numalias news_ax,%0:inc %0
	numalias news_ay,%0:inc %0
	numalias news_x,%0:inc %0
	numalias news_y,%0:inc %0
	numalias parent_width,%0:inc %0
	numalias parent_height,%0:inc %0
	numalias child_width,%0:inc %0
	numalias child_height,%0:inc %0

game

*text_lb

	; 今がスキップモードなら、さっさと次に回す。
	isskip %isskip
	if_int_goto %isskip,1,*text_lb_end

	; 進む機能を持つキーが押されていないことをチェックする。
	checkkey %bexec,"LCLICK"   :skip (0-%bexec) * 1
	checkkey %bexec,"WHEELDOWN":skip (0-%bexec) * 2
	checkkey %bexec,"SPACE"    :skip (0-%bexec) * 3
	checkkey %bexec,"RETURN"   :skip (0-%bexec) * 4
	checkkey %bexec,"K"        :skip (0-%bexec) * 5
	; CTRLは除く。CTRL押しっぱなしで進むのは、最近のエンジンではよくある仕様だから。

	*text_lb_init_loop

	bclear

	*text_lb_loop

	bexec $bexec,%bexec

	; Sにセーブを、Lにロードを割り当てる。
	if_str_goto $bexec,"S",*save_routine
	if_str_goto $bexec,"L",*load_routine

	; Kに既読スキップモードを割り当てる。
	if_str_goto $bexec,"K",*text_lb_skip_start

	; マウスの左クリックかホイールダウン、キーボードのスペースキーかエンターキーかコントロールキーで次へ。
	if_str_goto $bexec,"LCLICK",*text_lb_end
	if_str_goto $bexec,"WHEELDOWN",*text_lb_end
	if_str_goto $bexec,"SPACE",*text_lb_end
	if_str_goto $bexec,"RETURN",*text_lb_end
	if_str_goto $bexec,"CTRL",*text_lb_end

	; それ以外の入力であれば、ループする
	goto *text_lb_loop

	; スキップモード開始用
	*text_lb_skip_start
	systemcall skip

	; クリック待ちの終了処理。
	*text_lb_end

	texec

return

*initialize_global
	if %global_first=1 return

	; グローバル変数の初期設定
	
	; テキストウィンドウの不透明度
	mov %textwindow_alpha,255
	; テキストの表示速度(一文字表示にかけるミリ秒数)を格納する変数
	mov %textspeed,40

	; フラグを立てる。
	mov %global_first,1
return

*initialize_textwindow
	; テキストウィンドウの画像読み込み
	lsph sp_textwindow,":c;>600,200,#888888",0,0
	; テキストウィンドウのサイズを取得
	getspsize sp_textwindow,%textwindow_width,%textwindow_height
	; テキストウィンドウの配置場所の決定(左下にしている)
	news_position %textwindow_x,%textwindow_y,%window_width,%window_height,%textwindow_width,%textwindow_height,-1,1
	; フォントの設定
	mov %font_width,22
	mov %font_height,22
	mov %font_space,2
	mov %line_space,2
	mov %font_bold,0
	mov %font_shadow,1
	mov %font_ruby_width,11
	mov %font_ruby_height,11
	; パディングの設定
	mov %textwindow_padding_top,8
	mov %textwindow_padding_bottom,8
	mov %textwindow_padding_left,12
	mov %textwindow_padding_right,12
	
	; エフェクト中もテキストウィンドウが消えないようにする。
	erasetextwindow 0
	; 念のため、右クリックを殺しておく。
	rmode 0
return

; 実際にテキストウィンドウを設定する。
*set_window
	; 実際の表示領域の広さを計算する。
	mov %textwindow_area_width,%textwindow_width-%textwindow_padding_left-%textwindow_padding_right
	mov %textwindow_area_height,%textwindow_height-%textwindow_padding_top-%textwindow_padding_bottom
	; 表示領域の広さから、行数や文字数を計算する。
	mov %textwindow_col,%textwindow_area_width/(%font_width+%font_space)
	mov %textwindow_row,%textwindow_area_height/(%font_height+%line_space+%font_ruby_height)
	; 数値を反映した値にする。
	setwindow %textwindow_x+%textwindow_padding_top,%textwindow_y+%textwindow_padding_left,%textwindow_col,%textwindow_row,%font_width,%font_height,%font_space,%line_space,0,%font_bold,%font_shadow,#000000,%textwindow_x,%textwindow_y,%window_width,%window_height
	rubyon %font_ruby_width,%font_ruby_height
	textspeed %textspeed
	amsp sp_textwindow,%textwindow_x,%textwindow_y,%textwindow_alpha
	vsp sp_textwindow,1
return

; ある領域の中で、小さな領域を配置する時のための座標を計算する。
*news_position
	getparam i%news_x,i%news_y,%parent_width,%parent_height,%child_width,%child_height,%news_ax,%news_ay
	mov %%news_x,%parent_width-%child_width
	if %news_ax=-1 mov %%news_x,0
	if %news_ax=0 div %%news_x,2
	mov %%news_y,%parent_height-%child_height
	if %news_ay=-1 mov %%news_y,0
	if %news_ay=0 div %%news_y,2
return

; セーブルーチン
*save_routine
	savegame 0
	mesbox "セーブしました。","save game"
goto *text_lb
; ロードルーチン
*load_routine
	loadgame 0

; if 代替命令群本体ルーチン

*if_str_goto
	getparam $if_a,$if_b,$if_label
	if $if_a=$if_b return $if_label
return

*if_str_gosub
	getparam $if_a,$if_b,$if_label
	if $if_a=$if_b goto $if_label
return

*if_int_goto
	getparam %if_a,%if_b,$if_label
	if %if_a=%if_b return $if_label
return

*if_int_gosub
	getparam %if_a,%if_b,$if_label
	if %if_a=%if_b goto $if_label
return

*notif_str_goto
	getparam $if_a,$if_b,$if_label
	notif $if_a=$if_b return $if_label
return

*notif_str_gosub
	getparam $if_a,$if_b,$if_label
	notif $if_a=$if_b goto $if_label
return

*notif_int_goto
	getparam %if_a,%if_b,$if_label
	notif %if_a=%if_b return $if_label
return

*notif_int_gosub
	getparam %if_a,%if_b,$if_label
	notif %if_a=%if_b goto $if_label
return

*if_in_advance
	; 定義した命令を、一度は実行しておくためのルーチン。
	; 起動時に一回だけやっておけばよい。
	if_str_goto ""," ",*dummy
	if_str_gosub ""," ",*dummy
	if_int_goto 0,1,*dummy
	if_int_gosub 0,1,*dummy
	notif_str_goto "","",*dummy
	notif_str_gosub "","",*dummy
	notif_int_goto 0,0,*dummy
	notif_int_gosub 0,0,*dummy

	if_str_gosub "","",*if_in_advance_dummy
	if_str_goto "","",*if_in_advance1

	*if_in_advance1
	if_int_gosub 0,0,*if_in_advance_dummy
	if_int_goto 0,0,*if_in_advance2

	*if_in_advance2
	notif_str_gosub ""," ",*if_in_advance_dummy
	notif_str_goto ""," ",*if_in_advance3

	*if_in_advance3
	notif_int_gosub 0,1,*if_in_advance_dummy
	notif_int_goto 0,1,*if_in_advance4

	*if_in_advance4
return

*if_in_advance_dummy
	; 何もしないよ。
return

もし、テキストウィンドウの設定を変更したければ、*initialize_textwindow以降を修正すること。

補足

テキストウィンドウの関係命令で、windowchip(テキストウィンドウにスプライトを結びつけ、texton/textoffで連動させる)とかwindoweffect(texton/textoffで使うエフェクト番号を指定)とかがあるけど、なんか性能的にイマイチなんで、使わないようにしています。

補足2

texton/textoffを上書きして、テキストウィンドウを消すように改造する予定です。今回しなかったのは、まだ常駐ボタンを作成していないからです。
次こそは発言者欄について。