吉里吉里用のVisual Studio Codeプラグインを作った

Visual Studio Code用の拡張機能を作りました。
TJS用とKAG/KAGEX用があります。

TJS : プラグイン 説明書
KAG/KAGEX : プラグイン 説明書

今のところ以下の機能が使えます。

1.シンタックスハイライト

tjsのソースコードやタグを色分け表示できます。

2.定義へジャンプ・定義をここに表示

クラス、関数、変数やマクロの定義などにジャンプできます。
この機能を使うにはctagsとctagsxを入れる必要があります。詳しくは説明書を見てください。

※colorRectの定義を表示しているところ

3.リファレンスパレット

クラス名やタグ名からリファレンスを検索できます。

4.スニペット

forやfunctionなどのスニペットが使えます。

吉里吉里Zでのリリース

吉里吉里Zで作ったゲームを配布するにはリリース作業が必要です。吉里吉里Zにはリリーサなどの配布用ツールが同梱されていないため少し手間がかかります。

xp3にまとめる

吉里吉里2ではリリーサ(krkrrel.exe)を使いましたが吉里吉里Zには同梱されていません。吉里吉里2のリリーサをそのまま使えます。

アイコンを変える

吉里吉里2では吉里吉里設定ツール(krkrconf.exe)で変更できましたが吉里吉里Zでは使えません。

代わりに一般的なアイコン変更ツールが使えます。例えばResourceHackerなどが利用できます。

破損チェックツール

破損チェックツールを一緒に配布すると、ゲームが起動しないときにファイルが破損していないかチェックできます。
吉里吉里2のチェックツール(ファイル破損チェックツール.exe)と署名ツール(krkrsign.exe)がそのまま使えます。

エンジン設定ツール

吉里吉里2ではエンジン設定ツール(エンジン設定.exe)が使えましたが吉里吉里Zでは使えません。

代わりに、吉里吉里Z本体を起動する際に-userconfオプションを付けて起動すると設定ツールが開きます。配布する際は以下のようにするのが簡単です。

  1. ゲーム本体と同じフォルダに「ユーザ設定ツール.bat」というファイルを作成します。
  2. 作成したファイルをテキストエディタで開きます。
  3. 以下を書き込んで保存します。「tvpwin32.exe」の部分は自分のゲームのファイル名に書き換えてください。
    start "" %~dp0/tvpwin32.exe -userconf
    

以上で「ユーザ設定ツール.bat」を起動すると設定ツールが開くようになります。

吉里吉里のループチューナで開けないwavファイル

たまに吉里吉里本体では再生できるのにループチューナでは開けないwavファイルがあります。

どうやらループチューナでは最初のチャンクがfmtチャンクでないと読み込めないようです。
普通はそうなっているので問題ないのですが、Cubaseで書き出したwavファイルがjunkチャンクから始まるため読み込めません。
このようなファイルはAudacityなど他のソフトで開いて保存しなおせば読み込めるようになります。

たくさんある場合はSoXでコマンドラインで処理するのが楽だと思います。

linq.tjs公開しました

TJSでLINQ to Objectsが使えるようになるライブラリlinq.tjsを公開しました。
https://github.com/sakano/linq.tjs

LINQ to ObjectsはC#の強力なコレクション操作ライブラリです。foreachすらないtjsではfor文を回すしかありませんでしたが、linq.tjsではより簡潔に複雑な処理を記述出来ます。

吉里吉里2/Zどちらでも使えます。

使用方法

  1. releaseページからSource codeをダウンロードします。
  2. linqフォルダに入っているlinq.tjs, linq_utility.tjs, linq__order.tjs, linq_generate.tjsを自分のプロジェクトフォルダに入れます。
  3. 最新のScriptsEx.dllをダウンロードして吉里吉里と同じフォルダに入れます。
  4. 以下のコードのようにlinq.tjsを読み込みます。
    Scripts.execStorage("linq.tjs");
    

使用例

例えば以下のように使えます。

Enumerable.range(1, 10)
    .where(function(x) { return x % 2 == 0;})
    .select(function(x) { return x * 2; })
    .forEach(function(x) { System.inform(x); });
// 4,8,12,16,20 が表示される

使い方はC#のLINQと全く同じです。簡単に説明すると次のように動作します。
1. Enumerable.range(1, 10)で1~10の整数を生成
2. .where(function(x) { return x % 2 == 0;}) で偶数のみ抽出
3. .select(function(x) { return x * 2; }) で抽出した偶数を2倍
4. .forEach(function(x) { System.inform(x); }); でダイアログを表示

文字列関数

function(x) { return ~; }が鬱陶しいのでlinq.jsを見習って文字列で指定できるようにしています。
“XXX => YYY”を”function(XXX) { return YYY; }”に変換するだけですが結構便利です。先の例は以下のようになります。

Enumerable.range(1, 10)
    .where("x => x % 2 == 0")
    .select("x => x * 2")
    .forEach("x => System.inform(x);");
// =>,4,8,12,16,20

さらに省略してYYY部分のみでも使えます。この場合は第1引数が_、第2引数が_2、第3引数が_3、…のようになります。つまり”function(_, _2, _3, _4, _5, _6, _7, _8, _9) { return YYY; }”に変換されます。

Enumerable.range(1, 10)
    .where("_ % 2 == 0")
    .select("_ * 2")
    .forEach("System.inform(_);");
// =>,4,8,12,16,20

引数の受け渡し

tjsにはjavascriptと異なりレキシカルスコープがありません。つまりfunctionの中で外にある変数が参照できずLINQを使う上で非常に不便です。なのでselectやwhereなどコールバックを引数にとる関数では、通常の引数の後ろに渡された引数をそのままコールバックに渡すようにしています。

以下の例ではargやcaptionとして引数を受け取っています。indexはここでは使っていませんが現在のインデックス番号です。

Enumerable.range(1, 10)
    .where("x, index, arg => x % arg == 0", 2)
    .select("x, index, arg => x * arg", 2)
    .forEach("x, index, caption => System.inform(x, caption);", "結果表示");
// =>,4,8,12,16,20

Enumerable.from

Enumerable.fromに配列、辞書配列、文字列を渡すとLINQが使えるようになります。

配列の場合はそれぞれの要素が順番に列挙されるだけです。また、toArray()を使うと列挙中のシーケンスを配列に変換できます。

var array = Enumerable.from([10, 20, 30])
    .select("x => x * x")
    .toArray();
// arrayは[100, 400, 900]になる

辞書の場合はkeyにキー、valueにその値が入った辞書配列として列挙されます。

Enumerable.from(%["hoge" => 100, "moge" => 200])
    .forEach(function(x) {
        System.inform(@"キー:${x.key}, 値:${x.value}");
    });
// "キー:hoge, 値:100"
// "キー:moge, 値:200"
// と表示される。

文字列の場合は先頭から1文字ずつ列挙されます。toString()でシーケンスを文字列に変換できます。

var str = Enumerable.from("StRinG")
    .where("_ == _.toUpperCase()") // 大文字のみ抽出
    .toString();
// strはSRGになる。

Enumerable.extendTo

LINQを頻繁に使う場合、Enumerable.fromを毎回呼び出すのは面倒です。今のところ配列限定ですが、一度Enumerable.extendTo(Array);を呼び出せば配列からselectなどの関数を直接呼び出せるようになります。

Enumerable.extendTo(Array);

var array = [1,2,3,4,5];
array = array.select("_ * 10").toArray();
// arrayは[10,20,30,40,50]になる

関数一覧

C#のLINQ to Objectsにある関数は全て移植済みです。その他にもInteractive Extensionsやlinq.jsなどから必要そうな関数を追加しています。

クエリ関数一覧

aggregate, aggregateWithSeed, all, any, average, buffer, concat, contains, count, defaultIfEmpty, distinct, do, elementAt, elementAtOrDefault, except, first, firstOrDefault, forEach, force, groupBy, groupJoin, indexOf, innerJoin, intersect, isEmpty, last, lastOrDefault, max, min, orderBy, orderByDescending, outerJoin, repeat, reverse, scan, scanWithSeed, select, selectMany, sequenceEqual, single, singleOrDefault, skip, skipWhile, startsWith, sum, take, takeWhile, thenBy, thenByDescending, toArray, toDictionary, toLookup, toString, trace, union, where, zip

生成関数一覧

Enumerable.from, Enumerable.empty, Enumerable.range, Enumerable.repeat, Enumerable.return, Enumerable.matches, Enumerable.random, Enumerable.randomInt, Enumerable.generate

Enumerable.matches

linq.tjs独自の関数としてEnumerable.matchesを追加しています。TJSではRegExpクラスで正規表現を使えますが癖があっていまいち使いづらいです。Enumerable.matchesでは正規表現のマッチ結果を扱えます。

Enumerable.matches(/([a-zA-Z]+)=([0-9]+)/g, "foo=100 bar=200")
    .forEach(function (matches) {
        System.inform(@"0:${matches[0]}, 1:${matches[1]}, 2:${matches[2]}");
    });
// "0:foo=100, 1:foo, 2:100"
// "0:bar=200, 1:bar, 2:200"
// が表示される

第1引数は使用する正規表現です。gフラグは必須です。第2引数は正規表現の対象となる文字列です。
このようにするとRegExp.matchesの結果が列挙されます。もう正規表現を使うのにfor文は必要ありません。

吉里吉里のバージョンを調べる

吉里吉里の質問をする時は使用している吉里吉里のバージョンを最初に伝えましょう。
単に吉里吉里といっても吉里吉里2安定版/吉里吉里2開発版と吉里吉里Zの各種バージョンで様々な違いがあります。
さらにKAG3/KAGEX2/KAGEX3などKAGのバージョンの違いがあります。
これらを書かない場合、バージョンを質問し返すのが面倒なためにスルーされる確率が上がります。

吉里吉里のバージョン

吉里吉里を起動してCtrl+F12を押すと以下のような画面が表示されます。
一番最初の行の「吉里吉里[きりきり] 2 実行コア version 2.31.2012.831 ( TJS version 2.4.28 )」が吉里吉里のバージョンになります。
自分の吉里吉里の同じ部分をコピーして送れば大体わかります。
krkr-version

KAGのバージョン

KAGのバージョンは簡単に調べる方法がありません。
しかし最後に更新されたのが5年前になるので普通は最新版だと思います。
KAGとだけ分かれば十分です。

KAGEXの場合は以下の3つのどこからダウンロードしたかでバージョンが変わります。
kag3ex3が最新版です。自分がどれを使っているかくらいは覚えておきましょう。
https://sv.kikyou.info/svn/kirikiri2/branches/kag3ex3/
https://sv.kikyou.info/svn/kirikiri2/branches/kag3ex2/
https://sv.kikyou.info/svn/kirikiri2/branches/kag3ex1/

吉里吉里でフォルダ/アーカイブの選択を省略する方法

吉里吉里を普通に起動すると以下のようなウィンドウが表示されます。
フォルダ/アーカイブの選択

ゲーム開発中は、この画面でプロジェクトフォルダを選択してOKをクリックしてゲームを起動します。
起動するたびに毎回選択するのは面倒です。
自動的にプロジェクトフォルダを選択する方法を紹介します。
2つあるので好きな方を使ってください。

方法1.フォルダ名をdataにする

プロジェクトフォルダをdataという名前にして、krkr.exeと同じ場所に置きます。
そうするとdataフォルダを自動的に選択してゲームを起動してくれます。

dataの代わりにcontent-dataという名前でも大丈夫です。
好きな方を使ってください。

方法2.コマンドライン引数を使う

フォルダの名前を変えずに「コマンドライン引数」でも設定できます。
実際に以下の手順でコマンド引数を指定できます。

1.krkr.exeを右クリックしてショートカットを作成します。

2.作成されたショートカットを右クリックしてプロパティを開きます。

3.ショートカットタブのリンク先の欄に、krkr.eXeの場所が書かれています。
その直後に半角スペースを付けて、さらに””で囲んだプロジェクトフォルダのパスを書き込みます。

下の画像の赤矢印の部分です。
ここでは”F:\project”フォルダをプロジェクトフォルダとして使っています。
shortcut-property

あとはOKをクリックして、設定したショートカットをダブルクリックすればゲームが起動します。

吉里吉里LanczosフィルタをGPU化した

吉里吉里ZではLanczos2,Lanczos3による画像の拡大縮小ができるのですが枚数が多いと非常に時間がかかります。
なのでGPU上で動作するように書き換えて高速化しました。
ソースコード(github)

現在の最新バージョンの吉里吉里Z本体にはLanczos拡縮の機能が含まれていません。(コードはdev_lanczosブランチにありますがマージされていません)
このdllにはCPU版も同梱されているので単純にLanczosを使いたいだけの場合にも便利だと思います。

何も考えずにPhotoShop等でリサイズするよりかなり画質よくなるので出来るなら使った方がいいです。

速度的にはGTX770上でCPU版に比べて3~8倍速まで確認しています。
画像サイズが大きくなるほど差が広がっていくと思われます。
頑張ればもっと速くなりそうですが動作したので一区切り。
それよりpng保存で時間かかるのが何とかならんものか・・・・・・。

吉里吉里からC#を使うメモ

C++/CLIのdllを作ってそこからC#を呼ぶ。それだけ。ncbindも問題なく動作します。
サンプルコード

吉里吉里のウィンドウ(左上)とC#のウィンドウ(右下)の間でメッセージのやりとりができます。
krkr-cli-test

吉里吉里でもkwidgetsを利用するとUI含めて結構作れますが、本格的な物ならC#と.NETが使えると便利です。試してないですがVisualBasicやF#, IronPythonなども同じく使えると思います。

吉里吉里とC#のアプリを別々にするとプロセスが分かれて面倒くさかったのが色々解消される・・・・・・といいなあ。

KAGEX拡張例(既読テキストの色変更)

KAGEXには既読テキストと未読テキストで色を変える機能がありません。少しtjsで書き換えるだけで簡単に実現できます。

書き換えるのはMessageLayer.tjsの2476行目です。drawTextToLayerでテキストを描画していますが、chColorが色の設定です。ルビがある場合は2497行目でまたdrawTextToLayerを使って描画しています。この2箇所を適当に書き換えればいいです。

		drawTextToLayer(ll, dx, dy, ch, chColor);

		if(currentRuby != "")
		{
			// ルビがある
			var cw = llfont.getTextWidth(ch);
			var orgsize = llfont.height;
			llfont.height = rubySize;
			var rw = llfont.getTextWidth(currentRuby);
			var rx,ry;
			if(!vert)
			{
				rx = int(dx + (cw>>1) - (rw>>1));
				ry = int(dy - rubySize - rubyOffset);
			}
			else
			{
				rx = int(dx + rubySize + rubyOffset);
				ry = int(dy + (cw>>1) - (rw>>1));
			}

			drawTextToLayer(ll, rx, ry, currentRuby, chColor);

			llfont.height = orgsize;
			currentRuby = '';
		}

既読かどうかはkag.getCurrentRead()でわかります。現在のテキストが既読ならtrue、未読ならfalseになります。以下では既読なら色を0xFF0000、未読ならchColorそのままになるように変更しました。

		if (kag.getCurrentRead()) {
			drawTextToLayer(ll, dx, dy, ch, 0xFF0000);
		} else {
			drawTextToLayer(ll, dx, dy, ch, chColor);
		}

		if(currentRuby != "")
		{
			// ルビがある
			var cw = llfont.getTextWidth(ch);
			var orgsize = llfont.height;
			llfont.height = rubySize;
			var rw = llfont.getTextWidth(currentRuby);
			var rx,ry;
			if(!vert)
			{
				rx = int(dx + (cw>>1) - (rw>>1));
				ry = int(dy - rubySize - rubyOffset);
			}
			else
			{
				rx = int(dx + rubySize + rubyOffset);
				ry = int(dy + (cw>>1) - (rw>>1));
			}

			if (kag.getCurrentRead()) {
				drawTextToLayer(ll, dx, dy, ch, 0xFF0000);
			} else {
				drawTextToLayer(ll, dx, dy, ch, chColor);
			}

			llfont.height = orgsize;
			currentRuby = '';
		}

KAGEX拡張例(アクションの停止)

アクションの実行の続きです。

kag.stopAction

kag.stopActionを使うと実行中のアクションを停止できます。引数に渡したオブジェクトを対象としたアクションが全て停止されます。そのオブジェクトのアクションならnowaitのアクションも含めて停止されます。

// レイヤを生成して"star.png"を表示
var layer = new Layer(kag, kag.primaryLayer);
layer.loadImages("star.png");
layer.setSizeToImageSize();
layer.visible = true;

// アクションを実行
kag.beginAction(layer, %[
    left : %[
        handler : "MoveAction",
        value : 300,
        time : 5000,
    ]
]);

// 左クリックされたらアクションを停止
kag.addHook("leftClick", function() {
    kag.stopAction(layer);
});

この例では左クリックされたらkag.stopAction(layer);を呼んでlayerを対象としたアクションを停止しています。アクションが停止されるとアクション終了時の状態になります。この例ではlayerはleft=300の状態になります。

kag.stopAllActions

kag.stopAllActionsを使うと実行中のアクションを全て停止できます。kag.stopActionのように対象となるオブジェクトを渡す必要はありません。

// レイヤを生成して"star.png"を表示
var layer = new Layer(kag, kag.primaryLayer);
layer.loadImages("star.png");
layer.setSizeToImageSize();
layer.visible = true;

// アクションを実行
kag.beginAction(layer, %[
    left : %[
        handler : "MoveAction",
        value : 300,
        time : 5000,
    ]
]);

// 左クリックされたら全てのアクションを停止
kag.addHook("leftClick", function() {
    kag.stopAllActions();
});

kag.stopActionと違い、kag.stopAllActionsの場合はnowaitのアクションは停止されません。nowaitのアクションも含めて停止したい場合はkag.stopAllActions(true);のように引数にtrueを指定してください。