2008年1月 9日 (水): 吉里吉里ヒント集
1.概要
このページでは、W.Dee氏製作のノベルゲーム製作ソフト「吉里吉里2」の追加機能を実装するヒントを紹介しています。
これらの機能は、CircleMebius製作「月下之煌(げっかのきらめき)」にて実装されているものです。
せっかくひーひー言いながら作ったので、少しでも多くの人にご利用下さればなぁと思って公開しました。
変な機能が多いですが、少しでも参考になれば嬉しいです。(・▽・)ノ
ちなみに、プラグイン化の仕方が分からないので、全てTJSやKAGでの組み込み実装方法となってます(笑
2.目次
3.ご利用の注意
- このページに記載されていることについては別に制約はかけませんので、ご自由にお使い下さいませ。
- サポートなどは一切できませんし、質問なども答えられないと思います(^^;。問題などが発生した場合、自力で解決できる方のみご利用下さいませ。
なお、これらの追加機能はCircleMebiusが作成したものです。吉里吉里開発者のW.Dee氏にこれらの機能について直接問い合わせるのはご遠慮下さい。m(_ _)m - 吉里吉里のKAGをマスターしていて、かつTJSを使える方のみを対象としています。どちらかというと中級者向けです。説明不足も多々ありますが、ご了承下さいませ。
- 動作確認は、吉里吉里2 2.28安定版rev2のみで行っています。他のバージョンでは個別に動作確認して下さいませ。
4.謝辞
これらの追加機能ヒントを作成するにあたって、以下のサイトを参考にさせていただきました。ありがとうございました。
また、実用的ですごく参考になるサイトばかりですよ~。
- 吉里吉里情報局
- Aqua Place&おさかな定食
- 清水恵のデータ工房内「吉里吉里2/KAG3覚え書き」
- Project Lips「KAGチョイ技集」
- Outfocus Wiki Inside KAG3
5.吉里吉里機能ヒント
5.1 システム関連
5.1.1 ALT+ENTERでフルスクリーン・ウィンドウモードの切り替え
MainWindow.tjsの最後の方に、以下の命令を追加します。
(場所としては、ファイル一番下の「タグハンドラ群の終わり」という文字列がある場所の後ぐらいに入れればいいかと思います)
//----------------------------------------------- タグハンドラ群の終わり --
interrupt : function(elm) { return -2; } incontextof this ];
}
//↓--------------------------------------- ここから追加部分--
function changeScreenModeClick(sender)
{
changeScreenMode();
}
function changeScreenMode()
{
if(fullScreened)
{
try
{
fullScreen = false;
}
catch(e)
{
Debug.notice("ウィンドウモードに移行できませんでした : " + e.message);
}
fullScreened = fullScreen;
if(fullScreened)
fullScreenMenuItem.checked = true;
else
windowedMenuItem.checked = true;
}
else if(!fullScreened)
{
try
{
fullScreen = true;
}
catch(e)
{
Debug.notice("フルスクリーンモードに移行できませんでした : " + e.message);
}
fullScreened = fullScreen;
if(fullScreened)
fullScreenMenuItem.checked = true;
else
windowedMenuItem.checked = true;
}
saveSystemVariables();
}
//↑--------------------------------------- ここまで追加部分--
}
// TJS スクリプトはここで終わり
MainWindow.tjsのfunction processKeys(key, shift)に、以下の命令を追加します。
//------------------------------------------------------- キーボード操作 --
function processKeys(key, shift)
{
//↓--------------------------------------- ここから追加部分--
if(key == VK_RETURN)
{
// ALT+Enterで画面切り替え。フルスクリーン/ウィンドウ表示
var sg = getKeyState;
if(sg(VK_MENU))
{
// キーが押されてた
changeScreenMode();
}
}
//↑--------------------------------------- ここまで追加部分--
if(checkProceedingKey(key, shift)) return;
備考:
- 現在フルスクリーンかどうかは、変数"kag.fullScreened"で保持されています。trueならフルスクリーン、falseならウィンドウモード。
- if(checkProceedingKey(key, shift)) return;の文よりも前に記述しましょう。でないと反応してくれません。
5.1.2 最初の起動時のみ、フルスクリーン表示をするかの確認表示
以下の命令を、シナリオファイルの最初の方の、適当な場所に追加します。
;-----------------------------------------------------------------------------
;■スクリーンモード確認(最初の1回のみ)
;-----------------------------------------------------------------------------
@if exp="sf.screen_define === void"
@if exp="askYesNo('フルスクリーンで表示しますか?') "
@eval exp="kag.onFullScreenMenuItemClick(this)" cond="!kag.fullScreen"
@wait time=500 canskip=true
@endif
@eval exp="sf.screen_define = 1"
@endif
備考:
- 上記の「ALT+ENTERでフルスクリーン・ウィンドウモードの切り替え」を実装した場合、@eval exp="kag.onFullScreenMenuItemClick(this)" cond="!kag.fullScreen"を@eval exp="kag.changeScreenMode()" cond="!kag.fullScreen"に変更しても可能です。
5.1.3 マウスホイールで、セーブ・ロード画面のページ変更
MainWindow.tjsのfunction onMouseWheel(shift, delta, x, y)に、以下の命令を追加します。
例えば設定ページが表示されている場合、表示状態を保持する変数を作っておき、それをtrueに設定することで、設定ページが表示されていると判断させます。
function onMouseWheel(shift, delta, x, y)
{
// ホイールが回転した
super.onMouseWheel(...);
//↓--------------------------------------- ここから追加部分--
//セーブ・ロード画面の場合
if (saveload_object.showing == true)
{
if (delta > 0)//上にホイールが回った場合(前のページに移動)
{
//ここでページ変更処理をします。
}
else if (delta < 0)//下にホイールが回った場合(次のページに移動)
{
//ここでページ変更処理をします。
}
return;
}
//コンフィグ画面の場合
if (f.config_showing == true)
{
if (delta > 0)
{
//ここでページ変更処理をします。
kag.process(tf.configfile,'*config_mode');//必要であれば該当場所に移動させます。
}
else if (delta < 0)
{
//ここでページ変更処理をします。
kag.process(tf.configfile,'*config_mode');//必要であれば該当場所に移動させます。
}
return;
}
//↑--------------------------------------- ここまで追加部分--
if(!historyLayer.visible)
{
if(delta > 0)
showHistoryByKey(); // メッセージ履歴を表示
else if(System.getTickCount() - lastHistoryHiddenTick > 150)
onPrimaryClick(); // クリックをエミュレート
// ↑ tick を比較しているのは、メッセージ履歴を隠す操作とホイールを
// 手前に回す操作が連続した場合に勝手に読み進むのをある程度防ぐ仕掛け
return;
}
else
{
// メッセージ履歴にイベントを垂れ流す
historyLayer.windowMouseWheel(shift, delta, x, y);
return;
}
}
場合によっては、セーブロード画面や設定画面で使えない場合があります。その場合は、それぞれのセーブ・ロードのプラグインクラスでfunction onMouseWheel(shift, delta, x, y)メソッドを定義して、同様に実装すれば可能です。
5.1.4 総プレイ時間の取得
MainWindow.tjsの最後の方に、以下の命令を追加します。
(場所としては、ファイル一番下の「タグハンドラ群の終わり」という文字列がある場所の後ぐらいに入れればいいかと思います)
//----------------------------------------------- タグハンドラ群の終わり --
interrupt : function(elm) { return -2; } incontextof this ];
}
//↓--------------------------------------- ここから追加部分--
function calcPlayTime()
{
var tmptime;
var nowtmptime = new Date();
var calcsec;
tmptime = nowtmptime.getTime();
sf.total_play_time += tmptime - sf.date_from_presaved;
sf.date_from_presaved = tmptime;
delete nowtmptime;
calcsec = sf.total_play_time;
tf.playtime_hour = (int)Math.floor(calcsec/(1000*60*60));
calcsec -= tf.playtime_hour * (1000*60*60);
tf.playtime_min = (int)Math.floor(calcsec/(1000*60));
calcsec -= tf.playtime_min * (1000*60);
tf.playtime_sec = (int)Math.floor(calcsec/1000);
}
//↑--------------------------------------- ここまで追加部分--
}
// TJS スクリプトはここで終わり
MainWindow.tjsのfunction onCloseQuery()に以下の一行を追加します。
function onCloseQuery()
{
calcPlayTime();//←この行を追加
saveSystemVariables();
if(!askOnClose) { super.onCloseQuery(true); return; }
super.onCloseQuery(askYesNo("終了しますか?"));
}
シナリオファイルの最初の方で、トータルプレイ時間を保持する変数sf.total_play_timeの定義と初期化命令を追加します。
;トータルプレイ時間の取得初期化(初回のみ)と起動時間の取得 @iscript sf.total_play_time = 0 if sf.total_play_time === void; var nowtime = new Date(); sf.start_date = nowtime.getTime(); sf.date_from_presaved = nowtime.getTime(); delete nowtime; @endscript
KAGで総プレイ時間を表示する場合の命令:
[eval exp="kag.calcPlayTime()"]
総経過時間:[emb exp="'%02d'.sprintf(tf.playtime_hour)"]時間[emb
exp="'%02d'.sprintf(tf.playtime_min)"]分[emb exp="'%02d'.sprintf(tf.playtime_sec)"]秒
備考:
- 経過時間を表示する直前にkag.calcPlayTime()を実行して下さい。表示の都度calcPlayTime()を実行します。
- tf.playtime_hour、tf.playtime_min、tf.playtime_sec変数にそれぞれ時間、分、秒が入ります。
- (メモ)MainWindow.tjsのonCloseQuery()メソッドでcalcPlayTime()命令を記述しています。
MainWindow.tjsのclose()メソッド内に記述すると、ウィンドウ右上の×印の閉じるボタンから終了した場合、close()メソッドが呼ばれないようなので、ここに記述しています。
5.1.5 ロード直後にのみ命令を実行
MainWindow.tjsの最初の部分に、以下の変数定義を追加します。
var holdPeriodEventQueue = []; // 保留にされたムービーのピリオドイベントキュー
var isLeavePeriodEvent = false; // ムービーのピリオドイベントを保留にするかどうか
var isWaitPeriodEvent = false; // ムービーのピリオドイベント待ち状態かどうか
var waitedPeriodEventStorageName = void; // ピリオドイベント待ちをコールしたストレージ名
var loaded_flag = false;//←この行を追加
//------------------------------------------------------ コンストラクタ --
function KAGWindow(ismain = true, width = 0, height = 0)
{
MainWindow.tjsのfunction loadBookMark(num, loaduser = true)に以下の一行を追加します。
function loadBookMark(num, loaduser = true)
{
// 栞番号 num からデータを読み出す
loaded_flag = true;//←この行を追加
return loadBookMarkFromFile(getBookMarkFileNameAtNum(num), loaduser);
}
これで、ロードされるとkag.loaded_flagがtrueになります。
そのため、ラベル直後にkag.loaded_flagがtrueかどうかを判定して、trueであればロード直後に実行する命令を記述することで実現可能です。(説明が中級者向けで分かりにくくてすみません(^^;)
KAGで実行する場合のマクロ定義例:(どこでもセーブプラグインを用いた例)
;■ロード直後に1度だけ実行するマクロ [macro name=after_load_func] [if exp="kag.loaded_flag == true"] [eval exp="kag.loaded_flag = false"] ;ここで実行する命令を記述 [endif] [endmacro] ;CircleMebiusでは、文字初期化命令[em]という文字を消去する[er]を拡張したマクロを作り、そこで[label]定義をするように作っています。 [macro name=em] ;どこでもセーブプラグインで使うlabel命令。ここをラベルとして保存されます。 [label] ;labelの直後に実行 [after_load_func] [er] [endmacro]
5.1.6 オートセーブの実装・達成率の計算
オートセーブ機能の仕様は以下の通りです。
- オートセーブのセーブスロット(栞)数は10個とします。
- 一番最近に保存したデータを一番先頭に表示します。
- ここではbookmarkの100~109までをオートセーブ用のスロットとしています。必要に応じて修正して下さい。
- Config.tjsでセーブ用スロットnumBookMarksの値を変更するのを忘れないようにしましょう。
- オートセーブは、セーブ用スロット100,101,102...109,100,101...という順にセーブされます。
(もし最新のものを常に一番上に表示したい場合、表示する際に順番を変更します) - 変数sf.autosaveで、オートセーブ有無を指定します。
- 変数sf.autosave_noticeで、オートセーブ時の情報表示有無を指定します。
- 変数sf.autosave_inskipで、スキップ中のオートセーブ有無を指定します。
- 変数sf.save_askでセーブ・ロード時に確認ダイアログを出すかどうかを指定します。
達成率機能の仕様は以下の通りです。
- オートセーブの場所ごとに、達成率をカウントします。
- 変数sf.complete_record_countに、未読のオートセーブを通過したら、1ずつカウントアップされてゆきます。
- 変数f.total_complete_record_countに、最終的なオートセーブの数を入れておきます。(各自で計算します)
- 最終的に、(sf.complete_record_count / f.total_complete_record_count)*100で達成率(%)を計算しています。
達成率のみ使いたい場合、達成率の該当部分のみ抽出して下さいませ。(←細かく説明するのは大変なので(笑))
シナリオファイルの最初の方で、オートセーブ用の変数定義と初期化命令、オートセーブ用マクロを追加します。
@iscript //オートセーブ 0:なし 1:あり sf.autosave = 1 if sf.autosave === void; //オートセーブ用先頭エントリ 0から9まで sf.autosave_entry = 0 if sf.autosave_entry === void; //オートセーブ時の情報表示 0:表示なし、1:表示あり sf.autosave_notice = 0 if sf.autosave_notice === void; //スキップ中のオートセーブ有無 0:なし 1:あり sf.autosave_inskip = 1 if sf.autosave_inskip === void; //sf.save_ask 0:セーブ・ロード時に確認ダイアログを出さない、1:確認ダイアログを出す sf.save_ask = true if sf.save_ask === void; //達成率カウント用 sf.complete_record_count = 0 if sf.complete_record_count === void; @endscript ;■オートセーブ [autosave] [macro name=autosave] ;↓ここで達成率をカウントします。達成率のみ使いたい場合、この命令(一行)をラベル直前に追加して下さい。 @eval exp="sf.complete_record_count++" cond="!kag.getCurrentRead_autosave()" ;ラベル。ここではどこでもセーブプラグインで定義されるマクロ[label]を使っている例です。 ;http://kgs.tenkyu.com/sp/ ;どこでもセーブプラグインを使わない場合や他のラベル定義をしている場合、適宜修正して下さい。 [label] @if exp="!(!sf.autosave_inskip == 1 && kag.skipMode > 2)" @if exp="sf.autosave && !kag.loaded_flag" @save place=&"sf.autosave_entry+100" ask=no @eval exp="sf.autosave_entry += 1" @eval exp="sf.autosave_entry -= 10" cond="sf.autosave_entry >= 10" @eval exp="f.backskip_enabled = true" @endif @if exp="sf.autosave_notice" @backlay ;ここで「オートセーブしました」というメッセージ表示→消去命令を入れます。 @endif @endif [endmacro] ;■達成率計算 [calc_complete_rate] [macro name=calc_complete_rate] @eval exp="tf.complete_rate = sf.complete_record_count / f.total_complete_record_count * 100" @eval exp="tf.complete_rate = 100" cond="tf.complete_rate > 100" [endmacro]
MainWindow.tjsの最後の方にでも、以下のオートセーブ対応用命令を追加します。
//----------------------------------------------- タグハンドラ群の終わり --
interrupt : function(elm) { return -2; } incontextof this ];
}
//↓--------------------------------------- ここから追加部分--
//オートセーブ対応セーブコマンド
function saveBookmarkAutoMode(num)
{
var result;
if(sf.save_ask)//確認をする場合
if (sf.saveload_page == 10)
{
var autonum = (sf.autosave_entry-num+100);
if (autonum<=0) autonum+=10;
result = kag.saveBookMarkWithAsk(num,true,"自動"+autonum);
}
else
result = kag.saveBookMarkWithAsk(num);
else//確認をしない場合
if (sf.saveload_page == 10)
result = kag.saveBookMark(num,true,"自動"+(sf.autosave_entry-num));
else
result = kag.saveBookMark(num);
if (result) sf.new_savedata = num;
return result;
}
//オートセーブ対応ロードコマンド
function loadBookmarkAutoMode(num)
{
var result;
if(sf.save_ask)//確認をする場合
if (sf.saveload_page == 10)
{
var autonum = (sf.autosave_entry-num+100);
if (autonum<=0) autonum+=10;
result = kag.loadBookMarkWithAsk(num,true,"自動"+autonum);
}
else
{
result = kag.loadBookMarkWithAsk(num);
}
else//確認をしない場合
if (sf.saveload_page == 10)
result = kag.loadBookMark(num,true,"自動"+(sf.autosave_entry-num));
else
result = kag.loadBookMark(num);
return result;
}
//↑--------------------------------------- ここまで追加部分--
}
// TJS スクリプトはここで終わり
KAGでオートセーブを実行する場合の命令:
[autosave]
達成率計算用に、MainWindow.tjsのfunction getCurrentRead()の直後に、以下のようなfunction
getCurrentRead_autosave()メソッドを追加します。
デフォルトのgetCurrentRead()メソッドと全く同じですが、getCurrentRead()メソッドはスキップ関連で変更されている可能性が大きいため、わざと別メソッドにしてます。
function getCurrentRead()
{
// 現在のシナリオ部分が既読かどうかを判定する
return autoRecordPageShowing && currentRecordName != "" &&
+sflags[currentRecordName] || !autoRecordPageShowing;
}
//↓--------------------------------------- ここから追加部分--
//オートセーブ・達成率対応用。
function getCurrentRead_autosave()
{
// 現在のシナリオ部分が既読かどうかを判定する
return autoRecordPageShowing && currentRecordName != "" &&
+sflags[currentRecordName] || !autoRecordPageShowing;
}
//↑--------------------------------------- ここまで追加部分--
達成率を表示する場合、シナリオファイルの最初の方に、オートセーブ数の数を数えて入れておきます。
;全シナリオ中にあるオートセーブの数。手動で計算して下さい(^^;。Devasなどの検索ソフトを使うと便利です。 [eval exp="f.total_complete_record_count = 63"]
KAGで達成率を表示する場合の命令:
[calc_complete_rate]
達成率 [emb exp="'%3d'.sprintf(tf.complete_rate)"]%
備考:
このままでは、100,101,102...と順番にセーブ用スロットに保存されていくため、順番に表示したらセーブ・ロード画面では最新のデータが分かりにくいという問題があります。
セーブ画面でオートセーブ用データは一番上に表示するようにすると見やすいと思います。
その場合、セーブ・ロード画面に表示する場所を各システムに合うように修正して下さい。
表示順入れ替えアルゴリズムの参考:
//CircleMebiusでの使用例です。
//不要な場所は省略してます。
//2005/12/01らんかさん作の「右クリックでの設定画面を TJS2 で実現するサンプル」の
// 2002/6/14 版 改造 :
// システムメニュー実装
// +セーブデータ削除
// +トランジション対応バージョン
//class SaveDataItemLayer extends KAGLayer
//を使わせて頂いてる例です。
//中略
class SaveDataItemLayer extends KAGLayer
{
//中略
var quicksave = false;//クイックセーブの時はTrueになる
var autosave = false;//オートセーブの時はTrueになる
var data_exist = true;//データがない場合はfalseになる
function SaveDataItemLayer(window, parent, num)
{
super.KAGLayer(window, parent);
//中略
this.num = num;
if (num > 99) autosave = true;//100番目以降ならオートセーブモードにする
//中略
if (autosave)//オートセーブデータはチェックボックスは使用できない
{
protectCheckBox.enabled = false;
protectCheckBox.color = 0x000000;
}
//中略
// 番号を表示
var str = string (num + 1);
var ty = font.getTextHeight(str);
//drawText(6, ( imageHeight - ty ) \ 2, str, 0);
if (autosave)
{
var entry = sf.autosave_entry - num + 100;
if (entry <= 0) entry+=10;
if (entry == 10)
str = "自動" + string (entry); //自動セーブデータは「自」と表示する
else
str = "自動 " + string (entry); //位置調整のため半角スペースを追加
drawText(301, 2, str, 0);
}
else
drawText(320, 2, str, 0);
//中略
// 栞の保存名を表示
font.height = 12;
var str = kag.bookMarkNames[num];
if(str == '') str = '情報なし';
if (autosave) str = "(自動)"+ str; //オートセーブデータは「自動」を追加する
var sepnum = str.indexOf(":");
//中略
// 日付を表示
if(kag.bookMarkDates[num] == '')
{
str = " ----/--/-- --:--", commentEdit.enabled = false;
data_exist = false;
}
else
{
if (sf.new_savedata == num && sf.saveload_page != 10)//最新のものは"最新"を追加
drawText(185, 51, "(最新)", 0xff0000);
if (num == ((sf.autosave_entry == 0)? 10:sf.autosave_entry) + 99)//オートセーブの最新のものには(新)を追加
drawText(185, 51, "(新)", 0xff0000);
if (num == (sf.autosave_entry + 100))//オートセーブの最古のものには(古)を追加
drawText(185, 51, "(古)", 0xff0000);
str = kag.bookMarkDates[num];
}
//中略
}
//中略
function makeSaveDataItems()
{
// セーブデータの表示
clearSaveDataItems();
saveDataItems = [];
for(var i = 0; i < 10; i++)
{
var obj = new SaveDataItemLayer(window, this, sf.saveload_page * 10 + i);
saveDataItems[i] = obj;
var entry;
//オートセーブを表示しているページ場合、表示順を入れ替えます。
if (sf.saveload_page == 10)
{
entry = sf.autosave_entry - i-1;
if (entry < 0) entry+=10;//負になった場合、+10して整数にする
}
else//オートセーブページでない場合、そのまま表示。
entry = i;
//表示する位置を変更します。2段組表示をしているので、こんな記述になってます。
if (entry<5)
{
obj.setPos(31, 87 + entry*92);
}
else
{
obj.setPos(372, 87 + (entry-5)*92);
}
//以下は関係ありません。
obj.visible = true;
obj.loadmode = true if state == 1;
obj.loadmode = false if state == 2;
}
}
5.1.7 ショートカットキーの追加
MainWindow.tjsのfunction processKeys(key, shift)に、追加してゆきます。
バーチャルキーは、この辺を参照するといいかと思います。
追加例:
function processKeys(key, shift)
{
if(checkProceedingKey(key, shift)) return;
if(key == #'F')
{
// 次の選択肢/未読まで進む
skipToNextStopByKey();
return;
}
if(key == #'B')
{
// 前に戻る
goBackByKey();
return;
}
if(key == #'A')
{
// 自動的に読み進める
switchAutoModeByKey();
return;
}
if(key == #'E' || key == VK_DELETE)
{
// 文字枠消去命令を記述します。
}
if(freeSaveDataMode)
{
if(key == #'S')
{
// 栞をはさむ命令
return;
}
if(key == #'L')
{
// 栞をたどる命令
return;
}
}
if(key == #'M')
{
// メニュー表示命令
return;
}
if(key == VK_PRIOR || key == VK_NEXT)//PageUP、PageDownボタンです。
{
//ページ変更などの処理を行います。
if (f.config_showing)//コンフィグの場合、ページ変更
{
//ページ変更などの処理
return;
}
else//通常時はメッセージ履歴を表示
{
showHistoryByKey();
return;
}
}
if(key == #'R' || key == #'H')
{
// メッセージ履歴を表示
showHistoryByKey();
return;
}
if(key == VK_ESCAPE)
{
// 右クリックエミュレート
if(typeof this.rightClickMenuItem != "undefined" &&
rightClickMenuItem.enabled)
{
rightClickMenuItem.click(); // クリックをエミュレート
return;
}
}
}
5.1.8 ゲームを最初に起動したときのみ変数に値を入れる
以下のように、if~~を最後に追加することでできます。解説は省略(ぉ
@eval exp="sf.saveload_page = 0 if sf.saveload_page === void"
5.2 メッセージ関連
5.2.1 CTRLキーで素早いスキップ(レスポンスの早いスキップ)
MainWindow.tjsのfunction checkProceedingKey(key, shift)に、以下のようなif文を追加します。
function checkProceedingKey(key, shift)
{
// key が読みすすみのキー ( CTRLキーかReturnキー ) の場合は
// キーを処理し、true を返す。そうでなければ false を返す
if(key == VK_RETURN || key == VK_SPACE)
{
trigger('cancelEraseWindow');
// キーがメッセージキューに溜まってる場合があるので
// 実際にそのキーが押されているのかどうかを
// getKeyState を用いて調べる
var sg = getKeyState;
if(sg(VK_RETURN) || key == VK_SPACE)
{
// キーが押されてた
if((shift & ssRepeat) && clickSkipEnabled &&
conductor.status == conductor.mRun)
{
// キーリピート
if(skipMode != 4 && skipKeyRepressed)
skipToStop2(); // まだskipMode 4に入っていない場合は早送りモードに入る
// skipKeyRepressed をチェックするのは
// 連続してキーリピートが発生しているときに
// cancelSkip 後にスキップに突入するのを防ぐため
}
else
{
skipKeyRepressed = true;
onPrimaryClickByKey();
}
return true;
}
}
//↓--------------------------------------- ここから追加部分--
if(key == VK_CONTROL)
{
clickSkipEnabled=true;
skipToStop2();
return;
}
//↑--------------------------------------- ここまで追加部分--
return false;
}
MainWindow.tjsのfunction skipKeyPressing()に、以下のようにsg(VK_CONTROL)の部分を追加します。
function skipKeyPressing()
{
// VK_RETURN あるいは VK_SPACE が押されているかどうか
var sg = getKeyState;
return sg(VK_RETURN) || sg(VK_SPACE) || sg(VK_CONTROL);//←ここに追加
}
備考:
- ここではclickSkipEnabled(KAGで言う[clickskip]タグ)を強制的にtrueにしています。もしゲーム中で[clickskip]をfalseにする場合があれば、押す前に別変数で状態を保持しておいて、function
skipCancelKeyPressing()で復旧させるようにすれば対応できるかと思います。
(スキップ中に[clickskip]で変更された場合の対応をしなければならないので、少々ややこしくなると思います)
5.2.2 ルビを太文字にする
MessageLayer.tjsのfunction processCh(ch)メソッドを修正します。
if(currentRuby != "")
{
// ルビがある
var cw = llfont.getTextWidth(ch);
var orgsize = llfont.height;
var old_baldfont = llfont.bold; //←この行を追加
llfont.bold = true; //←この行を追加
llfont.height = rubySize;
var rw = llfont.getTextWidth(currentRuby);
// 中略
else
ll.drawText(rx, ry, currentRuby, chColor, 255, antialiased); // 文字
llfont.bold = old_baldfont; //←この行を追加
llfont.height = orgsize;
currentRuby = '';
}
備考
- デフォルトのフォントは太字の場合がありますので、その場合はそれ以上は太くなりません(^^;。
5.2.3 文字速度・オートモードの速度サンプル表示

このような、文字速度を変更した場合の速度サンプルゲージを表示するものです。
仕様:
- スライダー変更時に描画開始
- 文字速度とオートプレイの文字速度を連動
シナリオファイルの最初の方に、以下の定義を追加します。変数は個別に修正して下さい。
;-----------------------------------------------------------------------------
;■コンフィグの文字速度表示サンプル
;-----------------------------------------------------------------------------
@iscript
var targetlayer = kag.fore.messages[0];//描画するメッセージレイヤーを指定します。
var teststr; // テスト文字列
var teststr_timer; // タイマー
var teststr_counter; // カウンター
//領域の背景色
var rectcol = 0x44000000;
//文字速度テスト領域座標
var teststr_rectx = 202;
var teststr_recty = 139;
var teststr_rectw = 136;
var teststr_recth = 15;
//文字速度の"■"の座標
var teststr_strx = teststr_rectx+2;
var teststr_stry = teststr_recty+2;
var teststr_strcol = 0xffffff;
//文字速度表示用
function speedTest(speed)
{
teststr_counter = 0;
teststr = "■■■■■■■■■■■";//長さを変更する場合、この文字数を増やして下さい。
targetlayer.fillRect(teststr_rectx, teststr_recty, teststr_rectw, teststr_recth, rectcol); // 前回のテスト文字列を消す
if (speed == 0) {
targetlayer.drawText(teststr_strx, teststr_stry, teststr, teststr_strcol);
}
else {
teststr_timer = new Timer(onTeststrTimer, '');
teststr_timer.interval = speed;
teststr_timer.enabled = true;
}
}
function deleteTeststrTimer()//コンフィグ終了時に呼び出すためのもの
{
if (teststr_timer !== void)
{
invalidate teststr_timer;
teststr_timer = void;
}
}
function speedTest_rectdraw()
{
kag.back.messages[1].fillRect(teststr_rectx, teststr_recty, teststr_rectw, teststr_recth, rectcol); // 前回のテスト文字列を消す
kag.back.messages[1].drawText(teststr_strx, teststr_stry, teststr, teststr_strcol);
}
function onTeststrTimer()
{
targetlayer.drawText(teststr_strx + targetlayer.font.getTextWidth(teststr.substring(0, teststr_counter)),
teststr_stry,teststr.charAt(teststr_counter),teststr_strcol);
teststr_counter++;
if (teststr_counter == teststr.length) {
invalidate teststr_timer;
teststr_timer = void;
teststr_counter = 0;
}
}
//------------------------------------------------------------------------
//自動読み進めの速度表示用
var autostr; // テスト文字列
var autostr_strtimer; // 文字タイマー
var autostr_autotimer; // 再読込タイマー
var autostr_strcounter; // カウンター
var autostr_autocounter; // カウンター
var autostr_repeatcount;
//文字速度テスト領域座標
var autostr_rectx = 202;
var autostr_recty = 220;
var autostr_rectw = 136;
var autostr_recth = 15;
//文字速度の"■"の座標
var autostr_strx = autostr_rectx+2;
var autostr_stry = autostr_recty+2;
var autostr_strcol = 0xffffff;
//速度一時保存用変数(プログラム的には美しくない作り方ですが)
var autostr_strspeed;
var autostr_autospeed;
function autospeedTest(speed,autospeed)
{
autostr_strcounter = 0;
autostr_repeatcount = 3;//オートプレイのサンプルを繰り返す回数
autostr_strspeed = speed;
autostr_autospeed = autospeed;
if (autostr_strtimer !== void)
invalidate autostr_strtimer;
if (autostr_autotimer !== void)
invalidate autostr_autotimer;
autostr = "■■■■■■■■■■■";//長さを変更する場合、この文字数を増やして下さい。
targetlayer.fillRect(autostr_rectx, autostr_recty, autostr_rectw, autostr_recth, rectcol); // 前回のテスト文字列を消す
if (speed == 0) {
targetlayer.drawText(autostr_strx, autostr_stry, autostr, autostr_strcol);
}
else {
autostr_strtimer = new Timer(onAutoTimer, '');
autostr_strtimer.interval = speed;
autostr_strtimer.enabled = true;
}
}
function deleteAutostrTimer()//コンフィグ終了時に呼び出すためのもの
{
if (autostr_strtimer !== void)
{
invalidate autostr_strtimer;
autostr_strtimer = void;
}
if (autostr_autotimer !== void)
{
invalidate autostr_autotimer;
autostr_autotimer = void;
}
}
function autospeedTest_rectdraw()
{
kag.back.messages[1].fillRect(autostr_rectx, autostr_recty, autostr_rectw, autostr_recth, rectcol); // 前回のテスト文字列を消す
kag.back.messages[1].drawText(autostr_strx, autostr_stry, autostr, autostr_strcol);
}
function onAutoTimer()
{
targetlayer.drawText(
autostr_strx + targetlayer.font.getTextWidth(autostr.substring(0, autostr_strcounter)),
autostr_stry,autostr.charAt(autostr_strcounter),autostr_strcol);
autostr_strcounter++;
if (autostr_strcounter == autostr.length) //書き終えた場合
{
invalidate autostr_strtimer;
autostr_strtimer = void;
autostr_strcounter = 0;
autostr_autotimer = new Timer(onAutoTimer_auto, '');//繰り返しタイマ設定
autostr_autotimer.interval = autostr_autospeed;
autostr_autotimer.enabled = true;
}
}
function onAutoTimer_auto()
{
autostr_repeatcount--;
if (autostr_repeatcount<=0)//リピート回数過ぎるとやめる
{
if (autostr_autocounter == autostr.length) {
invalidate autostr_autotimer;
autostr_autotimer = void;
autostr_autocounter = 0;
}
}
else//繰り返し
{
targetlayer.fillRect(autostr_rectx, autostr_recty, autostr_rectw, autostr_recth, rectcol); // 前回のテスト文字列を消す
autostr_autotimer.enabled = false;
autostr_strtimer = new Timer(onAutoTimer, '');
autostr_strtimer.interval = autostr_strspeed;
autostr_strtimer.enabled = true;
}
}
@endscript
KAGで文字速度サンプルを表示する場合の命令:
以下の命令を、文字速度変更スライダーを変更して文字速度を変更した後に実行させます。
[eval exp="speedTest(kag.userChSpeed)"]
KAGでオートプレイ速度サンプルを表示する場合の命令:
以下の命令を、オートプレイ速度スライダーを変更してオートプレイ速度を変更した後に実行させます。
[eval exp="autospeedTest(kag.userChSpeed, kag.autoModePageWait)"]
KAGで文字速度サンプル、オートプレイ速度サンプルを消去する場合の命令:
以下の命令を、設定画面終了時に実行します。これで削除ができます。(これを実行しないと、オートプレイ速度サンプル表示が終了していない場合、設定画面終了後も描画されてしまいます)
[eval exp="deleteTeststrTimer()"]
[eval exp="deleteAutostrTimer()"]
備考
- 既読文章のスライダーも追加したい場合、function speedTest(speed)をコピーして、既読用に修正すればすぐに対応できると思います。
追記
一部内容に問題があったようです。以下に指摘内容を記しておきます。
(情報提供:186様。ありがとうございました)
上記の「文字速度・オートモードの速度サンプル表示」ですが、
autostr_autocounter が不要なのではと思いました。
autostr_autocounter が使われている部分を下記に拾い出しましたが、
autostr_autocounter を宣言した後、初期化をしていない状態で autostr.length と比較をしております。
また autostr_autocounter の値が増減する箇所も見当たりません。
--------------------------------------------------------------------------------
var autostr_autocounter; // カウンター
function onAutoTimer_auto()
{
autostr_repeatcount--;
if (autostr_repeatcount<=0)//リピート回数過ぎるとやめる
{
if (autostr_autocounter == autostr.length) {
invalidate autostr_autotimer;
autostr_autotimer = void;
autostr_autocounter = 0;
}
}
else//繰り返し
{
targetlayer.fillRect(autostr_rectx, autostr_recty, autostr_rectw, autostr_recth, rectcol); // 前回のテスト文字列を消す
autostr_autotimer.enabled = false;
autostr_strtimer = new Timer(onAutoTimer, '');
autostr_strtimer.interval = autostr_strspeed;
autostr_strtimer.enabled = true;
}
}
--------------------------------------------------------------------------------
この状態で autostr_autocounter == autostr.length が成り立つ事は無いと思われます。
こちらで試した所、
「オートモードの速度サンプル」を3回実行した後、
if (autostr_repeatcount<=0) 内で無限ループしておりました。
上記のソースでも目的の動作は達成できる事は確認しましたが、
この場合、下記の3行は不要なのではと思いました。
--------------------------------------------------------------------------------
if (autostr_repeatcount<=0)//リピート回数過ぎるとやめる
{
// if (autostr_autocounter == autostr.length) {
invalidate autostr_autotimer;
autostr_autotimer = void;
// autostr_autocounter = 0;
// }
}
--------------------------------------------------------------------------------
5.2.4 句読点ウェイト
仕様:
- 変数sf.wait_periodが1ならウェイトあり、0ならウェイト無しで動作。
シナリオファイルの最初の方に、以下の変数を定義します。
;句読点ウェイトの有無 0:ウェイトなし、1:ウェイト有り @eval exp="sf.wait_period = 0 if sf.wait_period === void"
MessageLayer.tjsのclass MessageLayer extends KAGLayerクラスに、以下の変数を追加します。
class MessageLayer extends KAGLayer
{
var wwFollowing = "%),:;]}。」゙゚。,、.:;゛゜ヽヾゝ"
"ゞ々'")〕]}〉》」』】°′″℃¢%‰"; // 行頭禁則文字
var wwFollowingWeak="!.?、・ァィゥェォャュョッー・?!ーぁぃぅぇぉっゃゅょゎァィ"
"ゥェォッャュョヮヵヶ"; // 行頭(弱)禁則文字
var wwLeading="\\$([{「'"(〔[{〈《「『【¥$£"; // 行末禁則文字
//↓--------------------------------------- ここから追加部分--
var wwWaitChar="、。";//句読点ウェイトをする文字。
var period_waittime=400;//句読点ウェイト時間。
//↑--------------------------------------- ここまで追加部分--
MessageLayer.tjsのfunction processCh(ch)に、以下の命令を追加します。
function processCh(ch)
{
// 文字 ch を描画する
// 改行が行われ、かつそれがページ末端を越える場合は true を返す
// それ以外は false
var vert = vertical;
if((vert ? y >= relinexpos : x >= relinexpos ) && autoReturn)
{
// 中略
}
//↓--------------------------------------- ここから追加部分--
if (sf.wait_period && wwWaitChar.indexOf(ch)!=-1)//句読点ウェイト処理
{
kag.waitTime(period_waittime,true);
}
//↑--------------------------------------- ここまで追加部分--
changeLineSize() if sizeChanged;
KAGで句読点ウェイトする場合の命令:
[eval exp="sf.wait_period = 1"]
KAGで句読点ウェイトしない場合の命令:
[eval exp="sf.wait_period = 0"]
5.3 画像関連
5.3.1 クリック待ち画像有無
仕様:
- 変数sf.showpagebreakが0なら非表示、1なら表示。
シナリオファイルの最初の方に、以下の変数を定義します。
;クリック待ち画像の表示 0:非表示、1:表示 @eval exp="sf.showpagebreak = 1 if sf.showpagebreak === void"
MessageLayer.tjsのfunction showPageBreakGlyph(glyphobj)に、以下の一文を追加します。
function showPageBreakGlyph(glyphobj)
{
// ページ待ち記号を表示
if (sf.showpagebreak)//←この一行を追加
showBreakGlyph(glyphobj, pageBreakGlyph, pageBreakGlyphKey);
}
KAGでクリック待ち画像を表示する場合の命令:
[eval exp="sf.showpagebreak = 1"]
KAGでクリック待ち画像を表示しない場合の命令:
[eval exp="sf.showpagebreak = 0"]
5.3.2 セーブ時に、特定レイヤーを表示しないサムネイルを取得
MainWindow.tjsのfunction lockSnapshot()に、以下のようなレイヤー非表示にする部分を追加します。
エフェクトレイヤーや、不要なボタンなどがあるレイヤーをサムネイルに入れないようにできます。
以下は記述例です。
function lockSnapshot()
{
// スナップショットをロックする
// 初めてスナップショットがロックされた時点での画面を保存する
if(snapshotLockCount == 0)
{
var mes2 = false;//それぞれのレイヤーが表示か非表示かを保持する変数です。必要なだけ作ります。
var mes3 = false;
var mes4 = false;
var mes5 = false;
if(snapshotLayer === void)
snapshotLayer = new Layer(this, primaryLayer);
snapshotLayer.setImageSize(scWidth, scHeight);
snapshotLayer.face = dfAlpha;
//スナップショットに表示させたくないレイヤーが表示されている場合、一時的に非表示にします。
if (kag.fore.messages[2].visible)
{
mes2 = true;
kag.fore.messages[2].visible = false;
}
if (kag.fore.messages[3].visible)
{
mes3 = true;
kag.fore.messages[3].visible = false;
}
if (kag.fore.messages[4].visible)
{
mes4 = true;
kag.fore.messages[4].visible = false;
}
if (kag.fore.messages[5].visible)
{
mes5 = true;
kag.fore.messages[5].visible = false;
}
if (kag.fore.messages[6].visible)
{
mes5 = true;
kag.fore.messages[6].visible = false;
}
//スナップショット作成
snapshotLayer.piledCopy(0, 0, kag.fore.base, 0, 0, scWidth, scHeight);
//レイヤーの表示状態を元に戻します。
if (mes2)
{
kag.fore.messages[2].visible = true;
}
if (mes3)
{
kag.fore.messages[3].visible = true;
}
if (mes4)
{
kag.fore.messages[4].visible = true;
}
if (mes5)
{
kag.fore.messages[5].visible = true;
}
if (mes5)
{
kag.fore.messages[6].visible = true;
}
}
snapshotLockCount ++;
}
5.4 便利なマクロ
5.4.1 特定レイヤーをぼかす
以下のようなマクロ定義で可能です。(たぶん、どこかのページを参考にしたと思うんですが、出典がどこか忘れました(^^;)
縦のみ、横のみとかを使うと、戦闘シーンなどでいい効果が出せます。
;----------------------------------------------------------------------------- ;■ぼかし [blur layer="対象レイヤー" page="ページ" x=0 y=0] ;layer...対象のレイヤ(base/0,1,2...)。指定必須 ;page...表画面か裏画面かを指定する(fore/back)。省略時fore ;x...横方向のブラーの範囲(ピクセル) ;y...縦方向のブラーの範囲(ピクセル) ;----------------------------------------------------------------------------- @macro name=blur @eval exp="kag.getLayerFromElm(mp).doBoxBlur(+mp.x, +mp.y)" @endmacro
KAGでの記述例:
[blur layer="対象レイヤー" page="ページ" x=0 y=0]
5.4.2 文中コメント
文頭で';'(セミコロン)を置くことでコメント文にできますが、文中でのコメントができない場合、以下のようなマクロでできます。
;----------------------------------------------------------------------------- ;■文中コメント [c mes="コメント"] ;----------------------------------------------------------------------------- [macro name=c] [endmacro]
KAGでの記述例:
[c mes="コメント"]
単に何もしないマクロを作るだけですが(^^;。でも、1行で命令+コメントができると、かなり見やすくなります。
例:[playbgm storage="m07"][c mes="07:日常:お笑いBGMその2"]
6.便利プラグイン・テクニック紹介
- 履歴レイヤー拡張:よかひげ
履歴表示をスクロールバー形式にします。
- 履歴にルビ表示:メッセージ履歴でルビを表示する(言ノ葉迷宮)
- ドクン、と鼓動を表現するプラグイン:HeartBeatPlugin(WestSideRoom)
- 回転ズームプラグイン:KAG3用回転ズームプラグイン
- 複数文字に均等にルビを振る:KAGで複数文字に渡るルビを振る(何もない場所)
改行にも対応して綺麗に表示されます。
7.プロフェッショナル版
現在、月下之煌で作った「上下左右の簡易メニュー」や、「文字のフェード表示」、「ボイスのフェード機能」、「セーブ・ロード機能」という機能もご提供しています。
こちらは有償でのご提供になります。
それぞれ、プログラマーの人件費で0.5~1.5(人/月)の範囲でおさまるぐらいです。
自力でのゼロから開発するコストと比べると、それなりのコスト削減になると思います。
とりあえず実装方法を無料でお送りして、実装ができて採用が決定したらお支払いで、実装できなかったり使えなかったら支払い不要、という形式になっています。(ライセンス形式という形になります)
こちらもアフターサービスなしになりますので、実装して使えそうならお支払い、という流れになります。
吉里吉里プロフェッショナル版は、こちらのページからお申し込みすれば、閲覧できるようになります。
(2009/4/1:追記)フリー公開しましたので、このヒント集同様に無料でご利用頂けるようになりました。
以下にどんな機能があるのか、ご紹介します。
これらの機能は、CircleMebius製作「月下之煌(げっかのきらめき)」にて実装されているものですので、体験版でどんな動作なのかご確認できます。
プロフェッショナル版では、このシステムをさらに改善・改良したものです。
是非お試し下さい。
7.1 ウィンドウ内での簡易メニュー

- 簡易メニュー(下が命令、右が保存、左が設定、上がシステム)
- マウスが指定領域内に入ったら表示、領域から出たら消去(常に表示・常に非表示も可能)
- キーボード操作対応
- ツールチップヒントの表示機能
- 簡易メニュー内でスライダーの使用も可能
- オートセーブ機能付き(オートセーブ内容は、新しい順に表示されます)
7.2 文字のフェード表示
こんな感じで文字をフェードで表示する機能です。
NScripterにはあるみたいですが、吉里吉里ではありませんでしたので。
見栄えを美しくしたい場合には効果を発揮すると思います。

- 1文字ずつ透明度を上げながら表示することによって、フェードっぽく見えます。
- ノーウェイトを含む、ある一定速度より速く表示される速度の場合、フェード表示を無効にします。
- CPUにあまり負荷がかからないので、追加しても軽く動作します。
7.3 ボイスシステム
ボイス再生をメインとした、BGM、効果音系の基本的な命令セットです。
吉里吉里のデフォルト機能では機能不足でしたので、これを導入することでボイス関係の基本的な命令や環境を整備できると思います。
- ボイスの再生、停止、フェードアウトなどの基本機能
- ページ変更時のボイス消去有無
- ボイス再生時にBGM、ループ効果音音量を下げる設定有無
- セリフ部分のテキストフォントの色を、キャラごとに変更可能
- 複数ボイスの同時再生も可能
- BGM、効果音などの音に関する基本的命令も一通り揃っています
7.4 セーブ・ロード画面

セーブ・ロード画面のテンプレートとしてご利用頂けます。
一から作り上げるのは時間がかかりますので、これをご利用いただければ時間短縮&人件費削減になると思います。
- セーブ・ロード画面テンプレート
- オートセーブ機能付き(オートセーブ内容は、新しい順に表示されます)
- コメント、データ保護機能、データ削除機能有り
- セーブ・ロード・削除時に問い合わせるかどうかの設定機能有り
- 100個のセーブエントリと、10個のオートセーブエントリ
- キーボード操作対応
7.5 吉里吉里プロフェッショナル版の閲覧
以下のページから閲覧できます。
→吉里吉里プロフェッショナル版のページは、こちらです。
8.更新履歴
- 2009/04/01:「吉里吉里プロフェッショナル版」のフリー公開化について追加
- 2008/07/10:「吉里吉里プロフェッショナル版」の公開
- 2008/03/23:「5.2.3 文字速度・オートモードの速度サンプル表示」に問題部分に対する指摘を追加。
- 2008/01/09:公開(作成:江本あやえもん)

