Inside KAG3/小技

Last-modified: 2016-03-29 (火) 12:10:36

追加シナリオの配布

data.xp3以降のXP3アーカイブファイルは下表の順で登録され、後に登録したXP3アーカイブファイルの内容ほど優先的に反映されるので、

登録順↑優先度低い
登録順↓優先度高い
1video.xp3
2others.xp3
3rule.xp3
4sound.xp3
5bgm.xp3
6fgimage.xp3
7bgimage.xp3
8scenario.xp3
9image.xp3
10system.xp3
11patch.xp3
12以降patch2.xp3~

おまけの追加シナリオデータをscenario.xp3などにアーカイブし、配布する…といったことが簡単に実現できる。また、追加シナリオ用の修正ファイルもパッチ(patch.xp3以降)に含めることができる。

ムービー再生(レイヤ描画モード)におけるタグの順序

基本的には以下の順序になる。タグの順序によっては正しく動作しなくなるので注意。

  1. videolayerタグ…ビデオレイヤ設定
  2. videoタグ…ビデオ/SWF表示領域の属性を指定
  3. openvideoタグ…ビデオ/SWFの再生準備
  4. preparevideoタグ…ビデオの再生準備
  5. wpタグ…ビデオピリオドイベント待ち
  6. playvideoタグ…ビデオ/SWFの再生
  7. wvタグ…ビデオ再生の終了待ち
  8. clearvideolayerタグ…ビデオレイヤ解除(※通常、記述しなくても問題ない)

playvideoタグで動画ファイルを指定すると再生準備に少し時間がかかるので、タイミングが重要な局面では予めopenvideoタグで動画ファイルを指定しておく方が良い(再生準備に時間がかかるのはオーバーレイモードでも同じ)。

imageタグのpos属性値を増やすには

辞書配列scPositionXにキーを登録すれば、imageタグのpos属性値として使うことができる。
記述位置はConfig.tjsの [start-window-additionals] ~ [end-window-additionals] 内を推奨。
以下は、pos属性値としてleft_left_center(別名llc)、left_center_center(別名lcc)、rcとrの間(別名なし)を追加した例。

//[start-window-additionals]
// pos属性値を追加
;scPositionX.left_left_center = 200;
;scPositionX.left_center_center = 280;
;scPositionX.rcとrの間 = 440;
// 別名を追加(こちらは任意で)
;scPositionX.llc = scPositionX.left_left_center;
;scPositionX.lcc = scPositionX.left_center_center;
//[end-window-additionals]

実際のKAGシナリオは以下の通り。

[image storage="立ち絵1.tlg" layer=0 page=fore visible=true pos=llc]
[image storage="立ち絵2.tlg" layer=1 page=fore visible=true pos=left_center_center]
[image storage="立ち絵3.tlg" layer=2 page=fore visible=true pos=rcとrの間]

キーには平仮名・カタカナ・漢字も使えるので、上手く活用してKAGシナリオの可読性を上げると良い。

[画面]メニューからフルスクリーン時の画面解像度を選択できるようにするには

AfterInit.tjs(なければ新規作成のこと)の先頭に以下の行を追加する。
[画面‐フルスクリーン時の画面解像度]メニューが増え、サブメニュー項目を選択すると次回のフルスクリーン時に設定した値が反映される。
要吉里吉里2.30 rev.2 / KAG 3.30 rev.2以降。

// [画面‐フルスクリーン時の画面解像度‐自動]メニューハンドラの定義
kag.onFullScreenAutoClick = function(sender)
{
  System.setArgument("-fsres", "auto");
  kag.fullScreenAutoMenuItem.checked = true;
} incontextof kag;
// [画面‐フルスクリーン時の画面解像度‐縦横比が同じ解像度]メニューハンドラの定義
kag.onFullScreenProportionalClick = function(sender)
{
  System.setArgument("-fsres", "proportional");
  kag.fullScreenProportionalMenuItem.checked = true;
} incontextof kag;
// [画面‐フルスクリーン時の画面解像度‐最も近い解像度]メニューハンドラの定義
kag.onFullScreenNearestClick = function(sender)
{
  System.setArgument("-fsres", "nearest");
  kag.fullScreenNearestMenuItem.checked = true;
} incontextof kag;
// [画面‐フルスクリーン時の画面解像度‐解像度を変えない]メニューハンドラの定義
kag.onFullScreenNoChangeClick = function(sender)
{
  System.setArgument("-fsres", "nochange");
  kag.fullScreenNoChangeMenuItem.checked = true;
} incontextof kag;
// 各種メニューアイテムを作成
kag.fullScreenResolutionModeItem = new KAGMenuItem(kag, "フルスクリーン時の画面解像度(&R)", 0, void, false);
kag.fullScreenAutoMenuItem = new KAGMenuItem(kag, "自動(&A)", 1, kag.onFullScreenAutoClick, false);
kag.fullScreenProportionalMenuItem = new KAGMenuItem(kag, "縦横比が同じ解像度(&P)", 1, kag.onFullScreenProportionalClick, false);
kag.fullScreenNearestMenuItem = new KAGMenuItem(kag, "最も近い解像度(&N)", 1, kag.onFullScreenNearestClick, false);
kag.fullScreenNoChangeMenuItem = new KAGMenuItem(kag, "解像度を変えない(&C)", 1, kag.onFullScreenNoChangeClick, false);
// 上記メニューアイテムを[画面]メニューに登録
kag.displayMenu.add(new MenuItem(kag, "-"));  // セパレータ
kag.displayMenu.add(kag.fullScreenResolutionModeItem);
kag.fullScreenResolutionModeItem.add(kag.fullScreenAutoMenuItem);
kag.fullScreenResolutionModeItem.add(kag.fullScreenProportionalMenuItem);
kag.fullScreenResolutionModeItem.add(kag.fullScreenNearestMenuItem);
kag.fullScreenResolutionModeItem.add(kag.fullScreenNoChangeMenuItem);
// 初期値を設定
switch (System.getArgument("-fsres")) {
case "":
case "auto":
  kag.fullScreenAutoMenuItem.checked = true;
  break;
case "proportional":
  kag.fullScreenProportionalMenuItem.checked = true;
  break;
case "nearest":
  kag.fullScreenNearestMenuItem.checked = true;
  break;
case "nochange":
  kag.fullScreenNoChangeMenuItem.checked = true;
  break;
}

コマンドラインオプションを動的に変更しているだけで、次回ゲーム起動時には元の設定に戻るので注意(設定を記録するにはエンジン設定を使う)。

[ヘルプ]メニューからセーブデータフォルダを開けるようにするには

AfterInit.tjs(なければ新規作成のこと)の先頭に以下の行を追加する。
[ヘルプ‐セーブデータフォルダを開く]メニューが増え、選択するとセーブデータフォルダを開けるようになる。

// [ヘルプ‐セーブデータフォルダを開く]メニューハンドラの定義
kag.onOpenSaveDataLocationClick = function(sender)
{
  System.shellExecute( Storages.getLocalName(saveDataLocation) );
} incontextof kag;
// [セーブデータフォルダを開く]メニューアイテムを作成
kag.helpSaveDataLocationMenuItem = new KAGMenuItem(kag, "セーブデータフォルダを開く(&O)", 0, kag.onOpenSaveDataLocationClick, false);
// [セーブデータフォルダを開く]メニューアイテムを[ヘルプ]メニューに登録
kag.helpMenu.add(kag.helpSaveDataLocationMenuItem);

なお、吉里吉里2 2.29-dev.20070521以降の場合、[ヘルプ‐セーブデータフォルダを開く]メニューハンドラの定義は以下のように記述しても構わない(どちらでも動作する)。

// [ヘルプ‐セーブデータフォルダを開く]メニューハンドラの定義
kag.onOpenSaveDataLocationClick = function(sender)
{
  System.shellExecute( Storages.getLocalName(System.dataPath) );
} incontextof kag;

[ヘルプ]メニューからサポートサイトのURLをお気に入りに登録できるようにするには

AfterInit.tjs(なければ新規作成のこと)の先頭に以下のような行を追加する(要win32ole.dllプラグイン)。この例では[ヘルプ‐お気に入りに kikyou.info を追加]メニューが増え、選択すると[お気に入りの追加]ダイアログボックスが開く。Microsoft Internet Explorer 6 SP2での動作を確認している。*1

Plugins.link("win32ole.dll");  // ←すでにwin32ole.dllを組み込んでいるなら、この行は不要
// [ヘルプ‐お気に入りに kikyou.info を追加]メニューハンドラの定義
kag.onAddFavoriteClick = function(sender)
{
  var uih = new WIN32OLE("Shell.UIHelper");
  uih.AddFavorite("http://kikyou.info/", "kikyou.info");
  invalidate uih;
} incontextof kag;
// [お気に入りに kikyou.info を追加]メニューアイテムを作成
kag.helpAddFavoriteMenuItem = new KAGMenuItem(kag, "お気に入りに kikyou.info を追加(&F) ...", 0, kag.onAddFavoriteClick, false);
// [お気に入りに kikyou.info を追加]メニューアイテムを[ヘルプ]メニューに登録
kag.helpMenu.add(kag.helpAddFavoriteMenuItem);

デスクトップにショートカットを作成するには

AfterInit.tjs(なければ新規作成のこと)の先頭に以下のような行を追加する(要win32ole.dllプラグイン)。この例では[ヘルプ‐デスクトップに kikyou.info へのURLショートカットを作成][ヘルプ‐デスクトップに System.title のショートカットを作成]メニューが増える。

Plugins.link("win32ole.dll");  // ←すでにwin32ole.dllを組み込んでいるなら、この行は不要
// [ヘルプ‐デスクトップに kikyou.info へのURLショートカットを作成]メニューハンドラの定義
kag.onAddUrlShortcutClick = function(sender)
{
  var objShell = new WIN32OLE("WScript.Shell");
  var desktop = objShell.SpecialFolders("Desktop");
  var objShortcut = objShell.CreateShortcut(desktop + "\\kikyou.info.url");
  objShortcut.TargetPath = "http://kikyou.info/";
  objShortcut.Save();
  invalidate objShortcut;
  invalidate objShell;
} incontextof kag;
// [ヘルプ‐デスクトップに [System.title] のショートカットを作成]メニューハンドラの定義
kag.onAddShortcutClick = function(sender)
{
  var objShell = new WIN32OLE("WScript.Shell");
  var desktop = objShell.SpecialFolders("Desktop");
  var objShortcut = objShell.CreateShortcut(desktop + "\\" + System.title.replace(/[\\\/:*?"<>|]+/g, " ") + ".lnk");
  objShortcut.TargetPath = Storages.getLocalName( System.exeName );
  objShortcut.WindowStyle = 1;
  objShortcut.Description = System.title + " を開始します";
  objShortcut.WorkingDirectory = Storages.getLocalName( System.exePath );
  objShortcut.Save();
  invalidate objShortcut;
  invalidate objShell;
} incontextof kag;
// [デスクトップに kikyou.info へのURLショートカットを作成]メニューアイテムを作成
kag.helpAddUrlShortcutMenuItem = new KAGMenuItem(kag, "デスクトップに kikyou.info へのURLショートカットを作成(&U)", 0, kag.onAddUrlShortcutClick, false);
// [デスクトップに kikyou.info へのURLショートカットを作成]メニューアイテムを[ヘルプ]メニューに登録
kag.helpMenu.add(kag.helpAddUrlShortcutMenuItem);
// [デスクトップに [System.title] のショートカットを作成]メニューアイテムを作成
kag.helpAddShortcutMenuItem = new KAGMenuItem(kag, "デスクトップに " + System.title + " のショートカットを作成(&S)", 0, kag.onAddShortcutClick, false);
// [デスクトップに [System.title] のショートカットを作成]メニューアイテムを[ヘルプ]メニューに登録
kag.helpMenu.add(kag.helpAddShortcutMenuItem);

サムネイル画像をセピア調にして保存するには

MainWindow.tjsをテキストエディタで開き、KAGWindow.saveBookMarkToFile()メソッドの以下の箇所を、

          /*
          // サムネイル画像をセピア調にして保存する場合はコメントアウトを解除
          tmp.doGrayScale();
          tmp.adjustGamma(
                  1.3, 0, 255,  // R gamma, floor, ceil
                  1.0, 0, 255,  // G gamma, floor, ceil
                  0.8, 0, 255); // B gamma, floor, ceil
          */

次のように修正する(コメントを外すだけ)。

          // サムネイル画像をセピア調にして保存する場合はコメントアウトを解除
          tmp.doGrayScale();
          tmp.adjustGamma(
                  1.3, 0, 255,  // R gamma, floor, ceil
                  1.0, 0, 255,  // G gamma, floor, ceil
                  0.8, 0, 255); // B gamma, floor, ceil

領域アクション定義ファイルでの条件分岐など

領域アクション定義ファイルは、パレットインデックスの後の半角コロンから後ろを、匿名関数のステートメントとして実行しているだけなので、以下のような書き方ができる。

1: hint="自宅"; target="*自宅";
2: if (f.money >= 5000) { hint="デパート"; target="*デパート"; } else { hint="選択できません"; }
3: hint="学校"; target="*学校";

この例では、パレットインデックス2で、所持金を意味する変数f.moneyが5000以上ならデパートがクリック可能に、そうでなければ「選択できません」というツールチップヒントを表示するようにしている。

GraphicLayer.internalLoadProvinceActions()の実装、および掲示板過去ログ559も参照のこと。

グラフィカルボタンでセーブデータのサムネイル表示

グラフィカルボタンに任意の文字列を描画(逆引きマニュアル参照)できるのなら、任意の画像データを描画することもできる。

以下のKAGシナリオでは、例としてダミーのグラフィカルボタン(横399×縦99ピクセル)に栞0のサムネイル画像を描画している。
なお、399という数値はConfig.tjsのthumbnailWidth(デフォルトでは133)の3倍、99という数値はthumbnailWidth*scHeight/scWidth(小数点以下切り捨て)から求められる。

; 前景レイヤ0裏画面に「通常の状態」用の画像データ(グレースケール)を読み込む
[image storage="&kag.getBookMarkFileNameAtNum(0)" layer=0 page=back grayscale=true]
; 前景レイヤ1裏画面に「ボタンが押された状態」用の画像データを読み込む(ガンマを少し上げる)
[image storage="&kag.getBookMarkFileNameAtNum(0)" layer=1 page=back rgamma=1.5 ggamma=1.5 bgamma=1.5]
; 前景レイヤ2裏画面に「ボタンの上にマウスポインタがある状態」用の画像データを読み込む
[image storage="&kag.getBookMarkFileNameAtNum(0)" layer=2 page=back]
; グラフィカルボタンを表示;クリックしたら栞0をたどる
[button graphic="thumbnail_button.png" exp="kag.restoreBookMark(0)" hint="&kag.getBookMarkPageName(0)"]
; サムネイル画像の高さを求める
[eval exp="tf.th = (int)(kag.thumbnailWidth*kag.scHeight/kag.scWidth)"]
; グラフィカルボタンの左側に前景レイヤ0裏画面の画像データをコピー
[eval exp="kag.current.links[0].object.copyRect(0, 0, kag.back.layers[0], 0, 0, kag.thumbnailWidth, tf.th)"]
; 中央に前景レイヤ1裏画面の画像データをコピー
[eval exp="kag.current.links[0].object.copyRect(kag.thumbnailWidth, 0, kag.back.layers[1], 0, 0, kag.thumbnailWidth, tf.th)"]
; 右側に前景レイヤ2裏画面の画像データをコピー
[eval exp="kag.current.links[0].object.copyRect(kag.thumbnailWidth*2, 0, kag.back.layers[2], 0, 0, kag.thumbnailWidth, tf.th)"]

以下のようにマクロ化できる。

;
; loadthumbnailマクロ
;
; 機能概要
;   グラフィカルボタンにサムネイル画像を描画する
;
; 属性
;   num       … 描画対象の栞の番号(0~)。省略不可
;   buttonnum … グラフィカルボタンの番号(0~)。省略不可
;   worklayer … 作業用の一時バッファに使うレイヤ名。省略不可
;   workpage  … 同、ページ。省略時fore
;
; 備考
;   ‐buttonタグの直後で使用すること
;       この際、ダミーのグラフィカルボタン(デフォルトでは横399×縦99ピクセル)を読み込むこと。
;       なお、グラフィカルボタンのサイズはConfig.tjsの以下のパラメータと式で決定される。
;           横:thumbnailWidth*3
;           縦:thumbnailWidth*scHeight/scWidth(小数点以下切り捨て)
;   ‐内部的に一時変数tf.''_st、tf.''_wk、tf.___thを使用している
;   ‐Config.tjsのfreeSaveDataModeをfalseに、saveThumbnailをtrueに設定してあること
;   ‐作業用の一時バッファには前景レイヤ裏画面を推奨
;
[macro name="loadthumbnail"]
  ; 栞numがはさまれていたら…
  [if exp="kag.getBookMarkPageName(mp.num) != '(未設定)'"]
    ; 栞numのローカルストレージ名を求める
    [eval exp="tf.___st = kag.getBookMarkFileNameAtNum(mp.num)"]
    ; 作業用のレイヤオブジェクトを求める
    [eval exp="tf.___wk = kag.getLayerFromElm(mp, 'work')"]
    ; サムネイル画像の高さを求める
    [eval exp="tf.___th = (int)(kag.thumbnailWidth*kag.scHeight/kag.scWidth)"]
    ; サムネイル画像を読み込む(グレースケール)
    [[image storage="&tf.___st" layer=%worklayer page=%workpage>fore grayscale=true]]
    ; グラフィカルボタンの左側(通常の状態)に画像データをコピー
    [eval exp="kag.current.links[mp.buttonnum].object.copyRect(0, 0, tf.''_wk, 0, 0, kag.thumbnailWidth, tf.''_th)"]
    ; サムネイル画像を読み込む(ガンマを少し上げる)
    [[image storage="&tf.___st" layer=%worklayer page=%workpage>fore rgamma=1.5 ggamma=1.5 bgamma=1.5]]
    ; グラフィカルボタンの中央(ボタン上にマウスがある状態)に画像データをコピー
    [eval exp="kag.current.links[mp.buttonnum].object.copyRect(kag.thumbnailWidth, 0, tf.''_wk, 0, 0, kag.thumbnailWidth, tf.''_th)"]
    ; サムネイル画像を読み込む
    [[image storage="&tf.___st" layer=%worklayer page=%workpage>fore]]
    ; グラフィカルボタンの右側(ボタンをクリックした状態)に画像データをコピー
    [eval exp="kag.current.links[mp.buttonnum].object.copyRect(kag.thumbnailWidth*2, 0, tf.''_wk, 0, 0, kag.thumbnailWidth, tf.''_th)"]
  [endif]
[endmacro]
; (中略)
[button graphic="thumbnail_button.png" exp="kag.restoreBookMark(0)" hint="&kag.getBookMarkPageName(0)"]
[loadthumbnail num=0 buttonnum=0 worklayer=0 workpage=back]
; (中略)
[button graphic="thumbnail_button.png" exp="kag.restoreBookMark(1)" hint="&kag.getBookMarkPageName(1)"]
[loadthumbnail num=1 buttonnum=1 worklayer=0 workpage=back]

TJSスクリプトに書き換えると以下のようになる(loadthumbnailマクロの使い方は同じだが一時変数は使用しない)。

[iscript]
function loadThumbnail(num, buttonnum, work)
{
  if (kag.getBookMarkPageName(num) == "(未設定)")
    return;
  // 以下の3つの変数の値を、ボタンのサイズに応じて調整すること
  var buttonWidth = kag.thumbnailWidth;  // 見かけ上のボタンの幅(ピクセル)
  var offsetLeft = 0;                    // サムネイル画像を描画するX座標(ピクセル)
  var offsetTop = 0;                     // 同、Y座標(ピクセル)
  with (kag.current.links[buttonnum].object)
  {
    var st = kag.getBookMarkFileNameAtNum(num);  // サムネイル画像のローカルストレージ名
    var tw = kag.thumbnailWidth;                 // サムネイル画像の幅(ピクセル)
    var th = (int)(kag.thumbnailWidth*kag.scHeight/kag.scWidth);  // 同、高さ(ピクセル)
    // グラフィカルボタンの左側(通常の状態)にサムネイル画像をコピー(グレースケール)
    var dic_l = %["storage"=>st, "grayscale"=>true];
    work.loadImages(dic_l);
    .copyRect(offsetLeft,               offsetTop, work, 0, 0, tw, th);
    // グラフィカルボタンの中央(ボタンが押された状態)にサムネイル画像をコピー(ガンマを上げる)
    var dic_c = %["storage"=>st, "rgamma"=>1.5, "ggamma"=>1.5, "bgamma"=>1.5];
    work.loadImages(dic_c);
    .copyRect(buttonWidth+offsetLeft,   offsetTop, work, 0, 0, tw, th);
    // グラフィカルボタンの右側(ボタン上にマウスがある状態)にサムネイル画像をコピー
    var dic_r = %["storage"=>st];
    work.loadImages(dic_r);
    .copyRect(buttonWidth*2+offsetLeft, offsetTop, work, 0, 0, tw, th);
    // 後始末
    invalidate dic_l;
    invalidate dic_c;
    invalidate dic_r;
  }
}
[endscript]
; (中略)
[macro name="loadthumbnail"]
  [eval exp="loadThumbnail(+mp.num, +mp.buttonnum, kag.getLayerFromElm(mp,'work'))"]
[endmacro]

これを併せれば、グラフィカルボタンにサムネイル画像、栞の名前、保存した時間の全てを描画できることが判る。
以下は簡単な右クリックサブルーチンのサンプル(セーブしかできない)。

filethumbnail_button.lzh

一時的に隠されたメッセージをマウス右クリックで解除するには

配列kag.rightClickHookにハンドラを登録すれば、hidemessageタグで一時的に隠されたメッセージレイヤを、マウス右クリックでも解除できるようになる。

; first.ks
[if exp="typeof(global.onRightClick) == 'undefined'"]
[iscript]
// マウス右クリックしたらfirst.ksのラベル*rclickにジャンプ
function onRightClick()
{
  kag.process("first.ks", "*rclick");
}
[endscript]
[endif]
[cm]
こんにちは。[l][r]
; マウス右クリックされたときに呼び出されるハンドラにonRightClick()を登録
[eval exp="kag.rightClickHook.add(onRightClick)"]
[hidemessage]
[jump target=*next]
*rclick
; マウス右クリックされたときに呼び出されるハンドラからonRightClick()を削除
[eval exp="kag.rightClickHook.remove(onRightClick)"]
; 一時的に隠されていたメッセージレイヤを元に戻す
[eval exp="kag.setMessageLayerHiddenState(false)"]
*next
こんばんは。

ただし、[システム‐メッセージを消す]メニューで隠されたメッセージレイヤは、この方法では解除できない。
こちらはkag.onPrimaryRightClick()で右クリックサブルーチンを呼び出しており、hidemessageが内部で行う処理とは異なっているため。

Alt+Enterキーで画面モードを切り替えるには

Alt+Enterキーでウィンドウ画面/フルスクリーンを切り替えるには、AfterInit.tjs(なければ新規作成のこと)の先頭に以下のTJSスクリプトを挿入する。*2

// [画面‐フルスクリーン/ウィンドウ表示の切り替え]メニューのハンドラ
kag.onChangeScreenModeClick = function(sender)
{
  if (global.kag.fullScreen)
    global.kag.onWindowedMenuItemClick(global.kag);
  else
    global.kag.onFullScreenMenuItemClick(global.kag);
} incontextof kag;
// [フルスクリーン/ウィンドウ表示の切り替え]メニューアイテムを作成(ただし表示しない)
kag.changeScreenModeItem = new KAGMenuItem(kag, "フルスクリーン/ウィンドウ表示の切り替え", 1, kag.onChangeScreenModeClick, false);
kag.changeScreenModeItem.shortcut = "Alt+Enter";
kag.changeScreenModeItem.visible = false;  // メニューアイテム自体は表示しない
kag.displayMenu.add(kag.changeScreenModeItem);

メッセージ枠(フレーム)の不透明度を調整できるようにするには

メッセージレイヤの不透明度(kag.fore.messages[n].opacityおよびkag.back.messages[n].opacity)を変更すると、描画されたメッセージもこれの影響を受けてしまう。そこで、メッセージ枠(フレーム)の画像を前景レイヤで表示させ、辻褄合わせする。

基本的なKAGシナリオは次のようになる。

  1. imageタグでメッセージ枠(フレーム)の画像ファイルを前景レイヤに表示する。ここでは仮に前景レイヤXとしておく(裏画面も同様)
  2. positionタグで前景レイヤXの上に(本来のメッセージを描画したい)メッセージレイヤを表示する。ここでは仮にメッセージレイヤYとしておく。メッセージレイヤYは完全透明であること(裏画面も同様)
  3. layoptタグで前景レイヤX表裏に対し、autohide=trueとしておく

後はコンフィグ画面(右クリックサブルーチンなど)で前景レイヤXの不透明度を調整すれば、メッセージ枠(フレーム)の不透明度を調整したのと同じ事になる。ただし、これだけではロードする度にメッセージ枠(フレーム)の不透明度が変わるという事態になるため、次のように対処する。

  • コンフィグ画面で設定した不透明度はシステム変数にも代入しておく。ここでは仮にシステム変数Sとする
  • 簡単なKAGプラグインを作成し、onRestore()ハンドラで前景レイヤXの不透明度をシステム変数Sに変更する
  • first.ksの先頭(要するに最初のセーブ可能なラベルより前)にlayoptタグを置き、システム変数Sの値を前景レイヤXの不透明度として設定する

以下は簡単なサンプル。

fileframe_opacity.lzh

未読もスキップできるようにするには

未読でも次の選択肢までスキップできるようにするには、AfterInit.tjs(なければ新規作成のこと)の先頭に以下のTJSスクリプトを挿入する。

// 未読もスキップするか否かのフラグ(システム変数)
if (sf.allskip === void)
  sf.allskip = false;  // 初回起動時にはfalseを代入しておく
// [システム‐未読もスキップ]メニューハンドラの定義
kag.onAllSkipClick = function(sender)
{
  sf.allskip = !sf.allskip;
  allSkipMenuItem.checked = sf.allskip;
  if (sf.allskip)
    skipToNextStopMenuItem.caption = "次の選択肢まで進む(&F)";
  else
    skipToNextStopMenuItem.caption = autoRecordPageShowing ? "次の選択肢/未読まで進む(&F)" : "次の選択肢まで進む(&F)";
} incontextof kag;
// [未読もスキップ]メニューアイテムを作成
kag.allSkipMenuItem = new KAGMenuItem(kag, "未読もスキップ(&L)", 0, kag.onAllSkipClick, false);
kag.allSkipMenuItem.checked = sf.allskip;
// [システム]メニューに[未読もスキップ]メニューアイテムを登録
kag.systemMenu.insert(kag.allSkipMenuItem, 3);
// kag.getCurrentRead()メソッドを差し換える
kag.getCurrentRead_org = kag.getCurrentRead;
kag.getCurrentRead = function()
{
  if (sf.allskip)                 // 未読もスキップするなら全て既読
    return true;
  else
    return getCurrentRead_org();  // そうでなければ旧kag.getCurrentRead()メソッドの戻り値を使う
} incontextof kag;

このTJSスクリプトは以下の機能を追加する。また、システム変数sf.allskipを追加する。

  • [システム‐未読もスキップ]メニューを追加する
  • [システム‐未読もスキップ]のチェックをオンにすると、未読でも次の選択肢までスキップできるようになる
  • [システム‐未読もスキップ]のチェックをオフにすると、KAG本来の仕様に戻る

メッセージの同時描画

KAGの仕様上、複数のメッセージレイヤに同時にメッセージを描画することは出来ないが、1文字描画する度にカレントのメッセージレイヤを切り替えれば、同時描画しているように見える。

[wait time=200]
*start|スタート
[cm]
; メッセージレイヤ0を画面上半分に設定
[position layer=message0 page=fore left=16 top=16 width=608 height=212 frame=""]
; メッセージレイヤ1を画面下半分に設定し、表示
[position layer=message1 page=fore left=16 top=248 width=608 height=212 frame="" visible=true]
; メッセージレイヤ0に描画するメッセージ
[eval exp="tf.mes0 = 'どういうことか説明して欲しいかも!'"]
; メッセージレイヤ1に描画するメッセージ
[eval exp="tf.mes1 = 'どういうことか説明してもらうわよ!'"]
; 現在、何文字目を描画しているかのカウンター
[eval exp="tf.i0 = 0"]
[eval exp="tf.i1 = 0"]
*loop
  [if exp="tf.i0 < tf.mes0.length"]
    ; メッセージレイヤ0に一文字描写
    [current layer=message0]
    [emb exp="tf.mes0[tf.i0]"]
    [eval exp="tf.i0++"]
  [endif]
  [if exp="tf.i1 < tf.mes1.length"]
    ; メッセージレイヤ1に一文字描写
    [current layer=message1]
    [emb exp="tf.mes1[tf.i1]"]
    [eval exp="tf.i1++"]
  [endif]
; 両メッセージを描画し終えるまで繰り返し
[[jump target=*loop cond="tf.i0<tf.mes0.length >| tf.i1<tf.mes1.length"]]
[s]

他にも、左から右へと移り変わるユニバーサルトランジションを使って、メッセージレイヤを同時描画しているように見せる手もある。


*1 万が一を考えて、例外処理を追加しておいた方が良い。
*2 Alt+Enterで画面モードを切り替えるのは、Windows標準のショートカットキーではないので注意。