KAGのintrandomを使ってはならない

吉里吉里2/KAG3のintrandomは簡単に乱数が使えて便利ですが、デフォルトの状態ではバグがあります。例えば「@eval exp=”f.乱数 = intrandom(0, 5)”」とするとf.乱数は0以上5以下の数値になるはずですが、非常に稀に6になってしまいます。
これはtjsのMath.randomがバグっているためですが、吉里吉里2は既にメンテナンスされていないので多分修正されません。ということで自分で直しましょう。以下のスクリプトをOverride.tjsかAfterinit.tjsにコピーしておけば直ります。

// 0以上1未満の乱数を返す(Math.RandomGeneratorを使うように修正)
Math.random = function() {
    if (Math.random.staticGenerator === void) {
        Math.setSeed();
    }
    return Math.random.staticGenerator.random();
};
Math.random.staticGenerator = void;

// Math.randomのシードを設定 
Math.setSeed = function(seed) {
    if (seed === void) seed = System.getTickCount();
    if (Math.random.staticGenerator === void) {
        Math.random.staticGenerator = new Math.RandomGenerator(seed);
    } else {
        Math.random.staticGenerator.randomize(seed);
    }
};

Math.randomをMath.RandomGeneratorを使って計算する形に修正しています。Math.randomが直ればそれを使っているintrandomの動作も直ります。

吉里吉里2から吉里吉里Zへの移行

吉里吉里Zに移行しようとしてみた
吉里吉里Zに移行しようとして挫折した話を読んだ。吉里吉里2と吉里吉里Zではかなり違うので意外と大変そう。

(勝手に)吉里吉里Z移行ガイドはまず読んだ方がいいです。以下、ここに書いていない部分など。

・コンソールがない
githubにあるKrkr2Compatにtjsで書かれたコンソールがあります。
右にあるDownload ZIPボタンから吉里吉里Zのソースコードをダウンロードできます。その中のscript/Krkr2Compat/フォルダがKrkr2Compatです。使い方などの詳しい説明はreadme.txtを読んでください。

・Layerのメソッドが減ってる
Layer.affineBlendなど古いメソッドが無くなっています。以下のスクリプトをstartup.tjsの最初などに書いておけばいいです。厳密にはLayer.faceとLayer.typeを使っていると描画結果が変わることがあるかもしれません。

Layer.affineBlend = function(src, sleft, stop, swidth, sheight, affine, A, B, C, D, E, F, opa=255, type=stNearest) {
    this.operateAffine(src, sleft, stop, swidth, sheight, affine, A, B, C, D, E, F, omOpaque, opa, type);
};
Layer.affinePile = function(src, sleft, stop, swidth, sheight, affine, A, B, C, D, E, F, opa=255, type=stNearest) {
    this.operateAffine(src, sleft, stop, swidth, sheight, affine, A, B, C, D, E, F, omAuto, opa, type);
};
Layer.blendRect = function(dleft, dtop, src, sleft, stop, swidth, sheight, opa=255) {
    this.operateRect(dleft, dtop, src, sleft, stop, swidth, sheight, omOpaque, opa);
};
Layer.pileRect = function(dleft, dtop, src, sleft, stop, swidth, sheight, opa=255) {
    this.operateRect(dleft, dtop, src, sleft, stop, swidth, sheight, omAuto, opa);
};
Layer.stretchBlend = function(dleft, dtop, dwidth, dheight, src, sleft, stop, swidth, sheight, opa=255, type=stNearest) {
    this.operateStretch(dleft, dtop, dwidth, dheight, src, sleft, stop, swidth, sheight, omOpaque, opa, type);
};
Layer.stretchPile = function(dleft, dtop, dwidth, dheight, src, sleft, stop, swidth, sheight, opa=255, type=stNearest) {
    this.operateStretch(dleft, dtop, dwidth, dheight, src, sleft, stop, swidth, sheight, omAuto, opa, type);
};

・自分でビルドしよう
配布されているバージョンはあまり更新されないようなのでできれば自分で最新版をビルドして使った方が良いです。発見されたバグなどが修正されていません。
やり方はgithubのHowToBulid.txtに書いてあります。簡単に手順を説明すると
1.Visual Studio Express 2012をインストールする
2.nasm-2.10.09-installer.exeをダウンロードしてインストールする
3.githubのDownload ZIPボタンからソースコードをダウンロードする
4.解凍してsrc/core/vc2012/tvpwin32.slnをダブルクリックしてVisual Studio 2012を起動する
5.メニューのビルド>ソリューションのビルドをクリックする
しばらく待つとbin/win32/フォルダにtvpwin32.exeができます

・移行する意味あるの?
新しい機能を使わないなら苦労するだけです。やめておきましょう。

最初に作ったWindowを閉じると終了してしまう

以下のスクリプトを実行すると、ウィンドウが一瞬表示されてもすぐに吉里吉里が終了してしまいます。吉里吉里ではメインウィンドウ(最初に作ったウィンドウ)が無効化されると自動的に終了する仕組みになっているからです。
この場合は先に作成したtemporaryWindowが無効化されているため終了してしまいます。

var temporaryWindow = new Window();
invalidate temporaryWindow;

var mainWindow = new Window();
mainWindow.visible = true;

System.exitOnWindowCloseをfalseにしておけばメインウィンドウが無効化されても終了しなくなります。なので以下のようにすればtemporaryWindowが無効化されても吉里吉里が終了しないようにできます。

// temporaryWindowが無効化されても吉里吉里が終了しないようにする
System.exitOnWindowClose = false;

var temporaryWindow = new Window();
invalidate temporaryWindow;

// mainWindowが無効化されたら吉里吉里が終了するようにtrueに戻しておく
System.exitOnWindowClose = true;

var mainWindow = new Window();
mainWindow.visible = true;

mainWindowが作成される時点で他のウィンドウがないので、mainWindowが新しいメインウィンドウとして機能します。よってSystem.exitOnWindowCloseをtrueに戻しておけば、mainWindowが無効化されたときに吉里吉里も終了するようになります。
falseのままだとウィンドウが全て消えても吉里吉里自体は起動したまま、ということになってしまうので戻しておくのが安全です。

実際に表示して使用するウィンドウを作成する前に、別の用途で一時的にウィンドウを使う場合などに便利だと思います。ちょっとウィンドウ作成したら勝手に終了するようになったので、この辺を覚えていないと微妙に詰まりました。

ちなみにですが、ウィンドウオブジェクトは右上の閉じるボタンやWindow.closeなどで閉じると自動的に無効化されるようです。無効化せず非表示にしたいだけの場合はWindow.visibleの方を使います。

Arrayに関数を追加しても使えない?

Array.assignStructやDictionary.assignStructで二次元配列などをコピーすると、中に入っている分の配列はシステム側で新しく作成されます。そのような配列には追加した分の関数が反映されません。

以下のコードのERRORという部分では「testFuncが見つかりません」というエラーがでます。
(Array.testFunc incontextof bar[0])(); のように直接呼ばない形なら使えます。

// Array に関数を追加
Array.testFunc = function() { Debug.message("testFunc called"); };

var foo = [];
foo.testFunc(); // OK

foo[0] = [];
foo[0].testFunc(); // OK

var bar = [];
bar.assignStruct(foo);
bar.testFunc(); // OK

bar[0].testFunc(); // ERROR

assignStruct以外にも(const)を付けた場合や関数の可変長引数としての配列の場合などにも駄目なようです。

Array.testFunc = function() { Debug.message("testFunc called"); };

var foo = (const)[];
foo.testFunc(); // ERROR

[].saveStruct("array.tjs");
var bar = Scripts.evalStorage("array.tjs");
bar.testFunc(); // saveStructで保存すると(const)が付くので ERROR

function func(args*) {
  args.testFunc(); // ERROR
}
func();

saveStruct.dllのArray.save2など、プラグインで追加されるものについても同様です。

KAGEX講座(35) – 動画再生(movie)

環境レイヤのmovieコマンドに動画ファイルを指定すると動画再生できます。fileで画像を読み込んだり、deleteでレイヤを削除したりすれば止まります。

@linemode mode=vn

@layer name=動画 movie=テスト.wmv
動画を再生しています。

@動画 delete
停止しました。

再生中の動画はwvタグで待てます。ただしlayer=allにしてください。

@linemode mode=vn

クリックすると動画を再生します。 クリックで再生を飛ばせます。

@layer name=動画 movie=テスト.wmv
@wv layer=all canskip=true

@動画 delete
動画を再生しました。

オープニング動画を再生するスクリプトは例えば次のようになります。

@linemode mode=vn

オープニング動画を再生します。

; スキップとオートモードを停止
@cancelskip
@cancelautomode

; BGM、効果音、ボイスを停止
@bgm stop=3000
@allse stop=3000
@allchar vcond=all stopvoice

; すべての画像を消去して暗転
@begintrans
@allimage hide
@msgoff
@endtrans fade=3000

; トランジションが飛ばされた時のために強制的に停止
@bgm stop
@stopse buf=all

; 再生前にもう一度スキップとオートモードを停止
@cancelskip
@cancelautomode

; 動画を再生
@layer name=動画 movie=オープニング.wmv

; 再生が終わるまで待つ
@wv layer=all canskip=true

; 再生が飛ばされた時のためにレイヤを動画終了時の色に塗る
;   最後が真っ白ならcolor=0xFFFFFFFF 真っ暗なら color=0xFF000000
;   widthとheightは動画のサイズに合わせます
@動画 color=0xFFFFFFFF width=1280 height=720

; 動画レイヤを隠す
@begintrans
@動画 delete
@endtrans fade=3000

; 少し待つ
@wait time=1000

オープニング動画を再生しました。

KAGEX講座(34) – 遅延実行4(ラベル指定)

delayrunコマンドでは数値の代わりにラベル名を指定できます。その場合はBGM、効果音、ボイスを再生中にその名前のラベルを通過すると実行されます。ラベルはループチューナであらかじめ入れておきます。

@linemode mode=vn

@layer name=カード file=card show
レイヤを表示

@se雨
@カード rotate=90 delayrun=ラベル0
効果音を再生し、ラベル0を通過するタイミングで90度回転します。

ボイスやBGMでも同じです。以下はボイスの例です。

@linemode mode=vn

@layer name=カード file=card show
レイヤを表示

@しおり playvoice=ai000001
@カード rotate=30 delayrun=ラベル0
@カード rotate=60 delayrun=ラベル1
@カード rotate=90 delayrun=ラベル2
【しおり】「ボイス再生中です。ラベル通過でレイヤが回転します」

nodelaydoneやdelaydoneも数値の時と同じように使えます。
delayrunコマンド自体はボイス再生中に立ち絵の表情を変えたりするのに使うことが多いです。数値で指定するよりはラベルを入れて調整するほうが楽だと思います。

Layer.type = ltBinder

吉里吉里のリファレンスには書いてませんがレイヤタイプにはltBinderというのがあります。


parentが違うレイヤ間の合成は見た目が変になったりしますがparentのレイヤタイプをltBinderにしておけば防止できたりします。
続きを読む

KAGEX講座(33) – 遅延実行3(delaycancel)

delaycancelタグでは実行待ちの遅延実行を全て消去します。delaydoneタグや改ページでの強制実行ではその時点で全ての遅延実行が実行されますが、delaycancelでは実行されません。よく注意して使わないとバグの元になります。一応紹介しましたがdelaycancelが必要なことはまず無いので使わないのがいいです。

@linemode mode=vn

@layer name=カード file=card show
レイヤを表示

@カード rotate=30
@カード rotate=60 delayrun=2000
@カード rotate=90 delayrun=4000
@カード rotate=120 delayrun=6000
@カード rotate=150 delayrun=8000
2秒ごとに30度ずつ回転[l]
@delaycancel
クリックした時点で残っている遅延実行は消去され、それ以上回転しません。

CentOSでTerrariaサーバ

Terraria(PC版)始めました。Terraria自体はまだWindows専用ですが、非公式のTShockを使うとLinux上でもマルチプレイ用のサーバを動かせるようです。

TShockは.NET上で動作するのでLinuxでMonoを動かしてその上で動作させます。Monoはクロスプラットフォームで動く.NET環境です。Unityでも利用されてたはず。

そのMonoですが最新版(3.8.0)ではTShock(4.2.3)がうまく動作しません。ゲーム本体からサーバに接続すると.NETの例外が発生してTShockが落ちます。なのでMonoのバージョンを2.10.2にバージョン下げると接続できるようになりました。

しかし地形が生成されておらずキャラが落下していきます。こちらはLD_LIBRARY_PATHにMonoのlibディレクトリを指定すると直りました。

なかなかうまく行かず少し大変でした。未確認ですが新しく立ち上げる時はここのページの通りにするのがよさそうです。外部から接続するにはポート(7777)を開けるのも忘れずに。

最初からTShockの公式フォーラムで調べていればもっと楽だったと思います。googleに頼りすぎるのも良くないですね。

KAGEX講座(32) – 遅延実行2(nodelaydone)

delayrunコマンドは改ページで強制的に全て実行されます。改ページしても継続したい場合はnodelaydoneをつけます。アクションでnowaitをつけるのと同じような効果です。
また、nodelaydoneをつけた遅延実行も「@delaydone all」で全て実行されます。

@linemode mode=vn

@layer name=カード file=card show
レイヤを表示

@カード rotate=30
@カード rotate=60 delayrun=2000 nodelaydone
@カード rotate=90 delayrun=4000 nodelaydone
@カード rotate=120 delayrun=6000 nodelaydone
@カード rotate=150 delayrun=8000 nodelaydone
2秒ごとに30度ずつ回転

次のページに進んでもそのままです

@delaydone all
全ての遅延実行コマンドを強制的に実行します

「@delaydone」は「all」を付けないとnodelaydoneが指定されていない遅延実行のみ強制的に実行できます。

@linemode mode=vn

@layer name=カード file=card show
レイヤを表示

@カード rotate=30
@カード rotate=60 delayrun=2000
@カード rotate=90 delayrun=4000
@カード rotate=120 delayrun=6000
@カード rotate=150 delayrun=8000
2秒ごとに30度ずつ回転[l]
@delaydone
改行で遅延コマンドを強制実行しました