KAGEX拡張例(GenericFlip)

GenericFlipの機能を利用すると環境レイヤに独自の属性を追加できます。特殊な画像やエフェクトなどをtjsで描画したいときに便利です。初期状態のKAGEXでも動画を再生するGFX_Movie, フラッシュを再生するGFX_Flash, パーティクルを表示するGFX_Particleなどが用意されています。今回は自前でGenericFlipを作成してみます。

GenericFlipの実装例

今回作成するGenericFlipは、指定された画像をインターバルごとに繰り返し表示するものです。以下がそのスクリプトとその使用例です。

/**
 * 指定された画像を一定時間ごとに切り替えて表示するGenericFlip
 *     layerタグにswitch属性とinterval属性を追加します。
 *         switch   : 表示する画像をコンマで区切って指定します
 *         interval : 画像を切り替える間隔をms単位で指定します
 * 例) @layer name="テスト" switch="image1,image2" interval=1000
 */

// 必ずGenericFlipクラスを継承する
class ImageSwitcherGenericFlip extends GenericFlip 
{
    var interval; // 画像を切り替える間隔
    var files;    // 切り替えて表示する画像ファイルが入った配列

    var startTick;        // 開始時の時間
    var currentFileIndex; // 現在読み込んでいるファイルの番号

    var targetLayer; // 画像を読み込むレイヤ

    // コンストラクタにはKAGWindowオブジェクト(kag)が渡される
    function ImageSwitcherGenericFlip(window) {
        super.GenericFlip(window);
        targetLayer = new Layer(window, window.primaryLayer);
    }

    // フリップ開始時に呼ばれる
    // 第1引数には登録属性の属性値、第2引数には全ての属性が入った辞書が渡される
    function flipStart(files, elm) {
        // コンマで区切って配列に変換する
        var files = files.split(",");

        // 表示を開始する
        start((int)elm.interval, files);
    }

    // フリップ再生中に毎フレーム呼ばれる
    // 引数には現在のSystem.getTickCount()の返り値が渡される
    function flipUpdate(currentTick) {
        // 現在の時間から表示するファイル番号を計算する
        var elapsedTick = currentTick - startTick;
        var fileIndex = (elapsedTick \ interval) % files.count;

        // 現在表示している画像と同じなら何もしない
        if (currentFileIndex === fileIndex) { return; }
        currentFileIndex = fileIndex;

        // targetLayerに現在の画像を読み込み
        targetLayer.loadImages(files[fileIndex]);
        targetLayer.setSizeToImageSize();

        // flipAssignでtargetLayerを対象のレイヤにコピー
        flipAssign(targetLayer);
    }

    // フリップ停止時に呼ばれる
    function flipStop() {
        this.interval = 0;
        this.files = void;
        super.flipStop(); // 必ずsuper.flipStopを呼び出す
    }

    // セーブする際に呼ばれる
    // 引数には保存用の辞書が渡される
    function flipStore(dic) {
        dic.interval = interval;
        dic.files = files;
    }

    // ロードする際に呼ばれる
    // 引数にはflipStoreで保存した辞書が渡される
    function flipRestore(dic) {
        if (dic.files === void) { return; }
        // 画像の表示を再開
        start(dic.interval, dic.files);
    }

    // 画像の表示を開始する関数
    // 第1引数に画像を切り替える間隔、第2引数にファイル名が入った配列を渡す
    function start(interval, files) {
        this.interval = interval > 0 ? interval : 1000;
        this.files = files;
        this.startTick = System.getTickCount();
        this.currentFileIndex = void;
    }
}

// 定義したクラスをGenericFlipとして登録
GenericFlip.Entry(%[
    "class"    => ImageSwitcherGenericFlip, // GenericFlipクラス
    "type"     => "switch",                 // 登録属性
    "options"  => [ "interval" ],           // 使用する属性名
]);
@linemode mode=vn
; 環境レイヤにimage01, image02, image03を1秒ごとに繰り返し表示する例
@layer name=レイヤ switch="image01,image02,image03" interval=1000
画像を表示しています。

; レイヤを消したりfile属性などで他のファイルを読み込んだりするとGenericFlipは停止します
@layer name=レイヤ hide
画像を消去しました。

実装の詳細はコメントを参照してください。GenericFlipを作る際の一般的な注意点のみ解説します。

GenericFlipとして使うクラスは必ずGenericFlipクラスを継承してください。ここではImageSwitcherGenericFlipクラスをGenericFlipとして使います(10行目)。

クラスを定義したら、GenericFlip.Entryを使って登録する必要があります(88行目)。登録内容は辞書で渡します。classには先ほど定義したクラス、typeにはこのクラスを呼び出すのに使う属性、optionsにはそれ以外に使う属性を配列で渡します。ここでは[layer]タグでswitch属性が使われたときに呼び出されるように登録しています。switch以外にinterval属性を使うのでそれも登録します。

GenericFlipクラスとして機能させるにはいくつかの関数を実装します。ここではflipStart, flipUpdate, flipStop, flipStore, flipStore, flipRestoreを実装しています。それぞれフリップ開始時、フリップ実行中、フリップ停止時、セーブ時、ロード時に呼ばれます。

switch属性が使われるとまずImageSwitcherGenericFlipクラスのオブジェクトが作成され、その後すぐにflipStartが呼び出されます。第1引数には登録属性(ここではswitch属性)の属性値、第2引数にその他の属性が入った辞書が渡されます。

その後は停止されるまでずっとflipUpdateが呼び出され続けます。現在の時間が第1引数に渡されるのでそれに合わせてレイヤに描画すればいいです。ここで重要になるのがflipAssign()です。これはGenericFlipクラスにあらかじめ用意されている関数で、引数に渡したレイヤの内容を環境レイヤにコピーしてくれます。まずは自前のレイヤに描画してからflipAssignを使ってコピーするのが定石です。ここでもtargetLayerに画像を読み込み、それをflipAssignでコピーしています。

環境レイヤが非表示になったりするとGenericFlipは自動的に停止されます。このときに呼ばれるのがflipStopです。ここでは必ずsuper.flipStop()を呼ばなければなりません。忘れないように注意してください。

flipStore, flipRestoreはセーブ・ロードに対応させるために必要です。flipStoreで現在の状態を保存してflipRestoreで実行を再開できるようにしてください。

まとめ

GenericFlipの実装手順は以下のようになります。
・GenericFlipを継承したクラスを定義する
・定義したクラスにflipStart, flipUpdate, flipStop, flipStore, flipRestoreを実装する
・定義したクラスをGenericFlip.Entryで登録する

GenericFlipはtjsを使ってレイヤに描画できます。環境レイヤに機能を追加する形なので、rotateなどの変形やcontrastなどの色調補正、その他のコマンドと組み合わせて使えます。独自のLayerを用意するよりも、可能であればGenericFlipとして描画機能を実装する方が応用がきいて便利だと思います。

KAGEX拡張例(アクションの実行)

kag.beginActionを使うとtjsからアクションを実行できます。

レイヤを移動してみる

以下はlayerに画像を表示し、5000msかけてleft=300, top=300まで移動させる例です。

// レイヤを生成して"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,
    ],
    top :  %[
        handler : "MoveAction",
        value : 300,
        time : 5000,
    ],
]);

beginActionの第一引数にはアクションの対象となるオブジェクトを指定します。第二引数にはアクション定義を渡します。アクション定義はenvinit.tjsで定義するものと同じです。今回の場合は対象のleft, topプロパティを500msかけて300まで動かすアクションになっています。

アクションについて

アクションをtjs的に言うと、特定のプロパティに対して自動的に値を代入していく機能です。以下の例ではプロパティpropを持つtestオブジェクトに対してアクションを実行しており、propには1000msかけて0から1000の値が代入されます。

// プロパティpropを持つクラスを定義
class Test {
	var _prop = 0;
	property prop {
		setter(value) {
			_prop = value;
			Debug.message("現在の値: " + value); // 代入された値を表示
		}
		getter() {
			return _prop;
		}
	}
}

// Testクラスのオブジェクトを生成
var testObj = new Test();

// testObjに対してアクションを実行
kag.beginAction(testObj, %[
    prop : %[
        handler : "MoveAction",
        start :  0,
        value : 1000,
        time : 1000,
    ]
]);

/* Debug.messageでは以下のように表示されます
    22:03:52 現在の値: +0.0
    22:03:52 現在の値: 1
    22:03:52 現在の値: 2
    22:03:52 現在の値: 2
    22:03:52 現在の値: 3
    22:03:52 現在の値: 4
    22:03:52 現在の値: 5
    22:03:52 現在の値: 6
      (中略)
    22:03:53 現在の値: 997
    22:03:53 現在の値: 998
    22:03:53 現在の値: 999
    22:03:53 現在の値: 999
    22:03:53 現在の値: 1000
    22:03:53 現在の値: 1000
*/

この例のように、Layerのleftやtopなどだけでなくプロパティであれば何でも対象にできます。上手く使えば色々と応用できると思います。

アクションのさらに詳しい使い方は次回に続きます。

KAGEX拡張例(画像の変形)

KAGEXではLayerクラスを拡張したAffineLayerクラスを利用できます。AffineLayerクラスを使うと簡単に回転や拡大縮小した画像を表示できます。

画像を表示

以下はAffineLayerに”star.png”を読み込んで表示するだけの例です。Layerと使い方は同じなので、AffineLayerの部分をLayerに変えてもそのまま動きます。

// レイヤオブジェクトを生成
var layer = new AffineLayer(kag, kag.fore.base);

// 画像を読み込む
layer.loadImages("star.png");
layer.setSizeToImageSize();

// 表示
layer.visible = true;

画像を回転

次は回転させてみる例です。

// レイヤオブジェクトを生成
var layer = new AffineLayer(kag, kag.fore.base);

// 画像を読み込む
layer.loadImages("star.png");
layer.setSizeToImageSize();

// 回転原点を画像中央に設定
layer.afx = AffineLayer.AFFINEOFFSET_CENTER;
layer.afy = AffineLayer.AFFINEOFFSET_CENTER;

// 画面中央に移動
layer.left = kag.width / 2;
layer.top = kag.height / 2;

// 90度回転させてみる
layer.rotate = 90;

// 表示
layer.visible = true;

afx, afyで回転原点を設定できます。その機能はKAGEXコマンドのafx, afyと同じです。その点を中心として回転や拡大縮小などが実行されます。afx,afyは整数で設定することもできますが、あらかじめ定義された定数も使えます。使える定数は以下の様になります。

定数名 設定される値
AffineLayer.AFFINEOFFSET_DEFAULT 初期値(0)
AffineLayer.AFFINEOFFSET_CENTER 画像中央
AffineLayer.AFFINEOFFSET_LEFT 画像左端(afxのみ)
AffineLayer.AFFINEOFFSET_RIGHT 画像右端(afxのみ)
AffineLayer.AFFINEOFFSET_TOP 画像上端(afyのみ)
AffineLayer.AFFINEOFFSET_BOTTOM 画像下端(afyのみ)

また、Layerと同じようにleft, topで画像の位置を設定できます。ただしその機能は表示原点(KAGEXコマンドのorx, ory)と同じになります。つまりleft,topに指定した座標(回転原点)に回転原点が重なるようになります。KAGEXの回転原点と表示原点について忘れた方はKAGEX講座(12) – レイヤ表示位置(afx, afy, orx, ory)を参照してください。

その他の変形プロパティ

rotate以外にも様々なプロパティが用意されています。以下の表に主なものをまとめます。これ以外にopacityやwidth, heightなどLayerクラスと同じプロパティや関数なども利用できます。

プロパティ名 初期値 説明
flipx false trueなら横方向に反転
flipy false trueなら縦方向に反転
rotate 0 回転角度
zoomx 100 横方向の拡大率
zoomy 100 縦方向の拡大率
zoom 100 zoomx, zoomyを一括指定
slantx 0 横方向の剪断
slanty 0 縦方向の剪断
afx 0 回転原点のx座標
afy 0 回転原点のy座標
orx 0 表示原点のx座標
ory 0 表示原点のy座標
raster 0 ラスター振幅量
rasterLines 100 ラスター行数
rasterCycle 1000 ラスター周期

それぞれのプロパティはKAGEXでも同じ属性名のコマンドとして用意されています。ここで改めて機能の説明はしません。

KAGEX拡張例(kag.insertTag)

前回までkag.addTagについて紹介しましたが、似たような関数にkag.insertTag()があります。

kag.addTagとkag.insertTagの違いは登録されたタグの実行される順番です。addTagでは一番最後に登録されますがinsertTagは一番最初に登録されます。

function flashMessage() {
    /* 以下のタグを登録
     * @msgoff time=500
     * @msgon time=500
     * @waittrig name=flash_message
     */
    kag.insertTag("waittrig", %[ name:"flash_message" ]);
    kag.insertTag("msgon", %[ time:500 ]);
    kag.insertTag("msgoff", %[ time:500 ]);
    
    // triggerでコンダクタの実行を再開
    kag.trigger("flash_message");
}

前回のflashMessageとほぼ同じですがaddTagの代わりにinsertTagを使っています。insertTagの場合はタグが先頭に追加されていくので後から登録したものが先に実行されます。なのでwaittrig, msgon, msgoffの順に登録しています。

タグの機能を呼び出したいだけならkag.addTagで十分です。先に登録したものが先に実行される方がわかりやすいと思います。何か複雑な機能を作る場合にはinsertTagを使うこともありそうなので一応覚えておいた方がいいかもしれません。

KAGEX拡張例(コンダクタの停止)

前回の続きです。

kag.addTagを使うとコンダクタに次のタグを登録できます。登録するだけなので[s]タグなどでコンダクタが止まっている間はタグは実行されません。

タグが実行されない例

以下の例では[msgoff][msgon]タグを登録していますが、コンダクタが停止しているため実行されることはありません。

function flashMessage() {
    /* 以下のタグを登録
     *   @msgoff time=500
     *   @msgon time=500
     */
    kag.addTag("msgoff", %[ time:500 ]);
    kag.addTag("msgon", %[ time:500 ]);
}
@linemode mode=vn

コンダクタを停止します。

@click exp="flashMessage()"
@s

[click]タグを使って左クリックされた時にflashMessage()が呼ばれるように設定しています。flashMessage()は[msgoff][msgon]タグを登録する関数です。[msgoff][msgon]を連続的に実行してメッセージレイヤを点滅させようとしていますが、これでは動作しません。

[s]タグでコンダクタの動作が停止されており、登録されたタグが実行されないためです。動作させるにはflashMessage()でタグを登録するだけでなくコンダクタの動作を開始しなければなりません。

実行されるように修正した例

以下が実際に動作するように修正したスクリプトです。クリックするたびにメッセージレイヤが点滅します。

function flashMessage() {
    /* 以下のタグを登録
     *   @msgoff time=500
     *   @msgon time=500
     *   @waittrig name=flash_message
     */
    kag.addTag("msgoff", %[ time:500 ]);
    kag.addTag("msgon", %[ time:500 ]);
    kag.addTag("waittrig", %[ name:"flash_message" ]);
    
    // triggerでコンダクタの実行を再開
    kag.trigger("flash_message");
}
@linemode mode=vn

コンダクタを停止します。

@click exp="flashMessage()"
@waittrig name=flash_message

[s]タグの代わりに[waittrig]タグを使っています。このタグもコンダクタの動作を停止しますが、name属性に指定したトリガが発動されるとコンダクタの動作を再開します。この場合は”flash_message”が発動すれば動作を再開します。

flashMessage()ではタグを登録してからkag.trigger(“flash_message”);としてトリガを発動します。これによってコンダクタの動作が再開され、登録したタグを順番に実行できます。

最初の例と異なり、[msgoff][msgon]タグだけでなく[waittrig]タグも登録していることに注意してください。[s]や[waittrig]などのタグでコンダクタを停止しない場合は当然そのまま実行し続けます。この例では登録したタグを実行し終わった後、first.ks最後の @waittrig name=flash_message の次の行から実行されます。続きを実行したくない場合はしっかり止めるようにしてください。

まとめ

kag.addTagを使うときはコンダクタの動作を意識するようにしてください。注意して使わないと思わぬバグの元になりかねません。

KAGEX拡張例(kag.addTagの仕組み)

前回紹介したkag.addTagの動作をもう少し詳しく説明します。

function playBgm() {
    // @bgm play=bgm01 というタグを登録
    kag.addTag("bgm", %[ play:"bgm01" ]);

    System.inform("System.informが実行されました");
}
@linemode mode=vn

@eval exp="playBgm()"
bgmを再生開始しました。

kag.addTagは「コンダクタに次のタグを登録する」ための関数です。この例では、”System.informが実行されました”と表示された後にbgmが再生されます。addTagで登録されたタグが実行されるのは、playBgm()の実行が終わって[eval]タグの次のタグを実行するタイミングです。

コンダクタの動作については簡単なコンダクタの話を参照してください。この記事で言われている「; 次のタグを探す」の部分では、まずkag.addTagなどで登録されたタグがないか探します。登録されたタグが無ければksスクリプト上で次のタグに進みます。このような仕組みのため、上の例では[eval]タグの実行が終わって次のタグを探し実行されるタイミングにbgmが再生されます。

コンダクタの動作ついてもっと詳しい説明は次回の記事に続きます。

KAGEX拡張例(kag.addTag)

前回はdoCommand()を使ってワールド拡張のタグを呼び出す方法を紹介しましたが、もっといい方法を教えてもらったので紹介します。

kag.addTag()という関数を使うとコンダクタに次のタグを直接登録できます。まずは実際の使用例です。

function showStage() {
    // @stage stage=道路 stime=昼 fade=1500 sync というタグを次のタグとして登録
    kag.addTag("stage", %[ stage:"道路", stime:"昼", fade:"1500", sync:"true" ]);
}

function showStage2() {
    // @道路 夜 fade=1500 nosync というタグを次のタグとして登録
    kag.addTag("道路", %[ 夜:"true", fade:"1500", nosync:"true" ]);
}

function flashStage() {
    // @stage hide fade=1000 sync というタグを次のタグとして登録
    kag.addTag("stage", %[ hide:"true", fade:"1000", sync:"true"]);

    // @stage show fade=1000 sync というタグを次のタグとして登録
    kag.addTag("stage", %[ show:"true", fade:"1000", sync:"true"]);
}
@linemode mode=vn

@eval exp="showStage()"
道路(昼)の背景を表示しました。

@eval exp="showStage2()"
道路(夜)の背景を表示しました。

@eval exp="flashStage()"
背景を点滅しました。

Override.tjsで定義した関数(showStage, showStage2)を[eval]タグで呼び出しています。関数の定義は[iscript][endscript]を使っても問題ありません。

kag.addTagには第1引数としてタグ名、第2引数として属性が入った辞書配列を渡します。

辞書配列の値はすべて文字列になるので注意してください。つまり、 show:true ではなく show:”true” です。大抵の場合問題になりませんが、稀に文字列でないと駄目な事もあるので必ず文字列にしておくといいです。

showStage2()のように省略記法も使えます。ksスクリプトで属性値を省略したときはtrueになるので、タグで省略されている部分は辞書配列上で”true”を渡しています。

flashStage()のように2つ以上続けて登録もできます。先に登録されたものから順に実行されます。

解説が長くなってしまうので、kag.addTagのもう少し詳しい解説は次回の記事に続きます。

KAGEX拡張例(doCommand)

KAGではkag.tagHandlers.ch(%);のようにkag.tagHandlersを使ってTJSからタグが実行できました。KAGEXの場合、同じ方法で呼び出せるタグもありますが、最もよく使うであろうワールド拡張系のタグは呼び出せません。いくつか例を挙げます。

キャラクタオブジェクト

[char]タグのコマンドはキャラクタオブジェクト(world_object.env.characters[“キャラ名”])を経由して呼び出します。

// @しおり face=怒 と同じ動作をさせる例
world_object.env.characters["しおり"].doCommand("face", "怒", %[]);
world_object.env.updateAll();

world_object.env.characters[“キャラ名”].doCommand(“属性名”, “属性値”, %[]);という形で大体は呼び出せます。その次の行のworld_object.env.updateAll();も必須です。一行目の段階では内部の状態が変更されただけで画面上には反映されません。updateAll()で初めて画面上の表情も変更されます。

2つ以上のコマンドを実行したい場合はupdateAllは最後に付けておけばいいです。

// @しおり face=怒り xpos=100 と同じ動作をさせる例
world_object.env.characters["しおり"].doCommand("face", "怒", %[]);
world_object.env.characters["しおり"].doCommand("xpos", "100", %[]);
world_object.env.updateAll();

背景オブジェクト

[stage]タグのコマンドは背景オブジェクト(world_object.env.stage)を経由して呼び出します。

// @stage stage=道路 と同じ動作をさせる例
world_object.env.stage.doCommand("stage", "道路", %[]);
world_object.env.updateAll();

world_object.env.stage.doCommand(“属性名”, “属性値”, %[]);という形になります。その他は[char]の場合と同様なので説明は省略します。

イベントオブジェクト

[evant]タグのコマンドはイベントオブジェクト(world_object.env.event)を経由して呼び出します。

// @event file=event1 と同じ動作をさせる例
world_object.env.event.doCommand("file", "event1", %[]);
world_object.env.updateAll();

world_object.env.event.doCommand(“属性名”, “属性値”, %[]);という形になります。その他は[char]の場合と同様なので説明は省略します。

環境レイヤオブジェクト

[layer]タグのコマンドは環境レイヤオブジェクト(world_object.env.layers[“レイヤ名”])を経由して呼び出します。

// @レイヤ hide と同じ動作をさせる例
world_object.env.layers["レイヤ"].doCommand("hide", "true", %[]);
world_object.env.updateAll();

world_object.env.layers[“レイヤ名”].doCommand(“属性名”, “属性値”, %[]);という形になります。その他は[char]の場合と同様なので説明は省略します。

環境オブジェクト

[env]タグのコマンドは環境オブジェクト(world_object.env)を経由して呼び出します。

// @env contrast=-255 と同じ動作をさせる例
world_object.env.doCommand("contrast", "-255", %[]);
world_object.env.updateAll();

world_object.env.doCommand(“属性名”, “属性値”, %[]);という形になります。その他は[char]の場合と同様なので説明は省略します。

BGMオブジェクト

[bgm]タグのコマンドはBGMオブジェクト(world_object.env.bgm)を経由して呼び出します。

// @bgm play=bgm01 と同じ動作をさせる例
world_object.env.bgm.doCommand("play", "bgm01", %[]);
world_object.env.updateAll();

world_object.bgm.doCommand(“属性名”, “属性値”, %[]);という形になります。その他は[char]の場合と同様なので説明は省略します。

効果音オブジェクト

[se]タグのコマンドはSEオブジェクト(world_object.env.ses[バッファ番号])を経由して呼び出します。

// @se buf=0 play=se01 と同じ動作をさせる例
world_object.env.ses[0].doCommand("play", "se01", %[]);
world_object.env.updateAll();

world_object.ses[バッファ番号].doCommand(“属性名”, “属性値”, %[]);という形になります。その他は[char]の場合と同様なので説明は省略します。

inno setupでデータを分ける方法

inno setupを使うと簡単にインストーラが作れます。詳しい説明は以下のページを参照してください。
吉里吉里/KAGゲームのインストーラの作り方(Windows 7対応)
今回の記事はこのページの説明を前提としています。

Flags: external

「おわりに」の部分に

同人レベルだったら、できればゲームファイルは纏めずにそのままCD-ROM上に配置して おけた方が、「最後の手段で手でコピーする」というのが使えてハッピーだと 思うが、まぁうん…そのくらいはいいか。

という記述がありますがinno setupでもファイルを纏めずにインストーラと別々にできます。KAICHOさんが知らなかったのか説明を省いたのか分かりませんが、特に理由がない限り別々にした方がいいです。

setup.exeのサイズが大きければ大きいほどインストーラの起動時間が長くなります。ひどい例だとsetup.exeをダブルクリックしてから一分待たないとインストール画面が出てこないこともあるようです。理由はsetup.exeにセキュリティソフトのウィルススキャンが入ったりするためです。他にもwindows側でも何かやっていた気がしますが詳細は忘れました。誰か知ってたら教えてください・・・・・・。

setup.exeとゲームデータを分ける方法はこのページに日本語で書いてあります。

前提の記事では[Files]が以下のようになっています。

[Files]
Source: "D:\同人ゲーム\我輩ゲーム\我輩ゲーム.eXe"; DestDir: "{app}";
Source: "D:\同人ゲーム\我輩ゲーム\data.xp3"; DestDir: "{app}";
Source: "D:\同人ゲーム\我輩ゲーム\plugin\*"; DestDir: "{app}\plugin";
Source: "D:\同人ゲーム\我輩ゲーム\savedata\*"; DestDir: "{app}\savedata";
Source: "D:\同人ゲーム\我輩ゲーム\readme.txt"; DestDir: "{app}";

これを例えば以下のようにします。Flags: external; を付けるのと、ファイルの場所を{src}を使ってsetup.exeからの相対パスで指定するのがポイントです。

[Files]
Source: "{src}\data\我輩ゲーム.eXe"; DestDir: "{app}"; Flags: external;
Source: "{src}\data\data.xp3"; DestDir: "{app}"; Flags: external;
Source: "{src}\data\我輩ゲーム\plugin\*"; DestDir: "{app}\plugin"; Flags: external;
Source: "{src}\data\我輩ゲーム\savedata\*"; DestDir: "{app}\savedata"; Flags: external;
Source: "{src}\data\我輩ゲーム\readme.txt"; DestDir: "{app}"; Flags: external;

この例の場合は、setup.exeと同じフォルダにdataフォルダを作り、その中に我輩ゲーム.eXeなどを入れておけばいいです。インストール時にはそれらのファイルがインストール先フォルダにコピーされます。

この記事で言いたいことはここまでです。以下はお好みでどうぞ。

AppId

[Setup]でAppIdを指定した方がいいかもしれません。アプリケーションごとの固有のIDを指定します。デフォルトではAppNameと同じになっています。ゲーム名だけでは他と被る可能性が0ではありません。どこかに表示されたりはしないので適当に被らなそうな物を指定しましょう。ホームページがあればそのURLを使うのがいいです。
例えば以下のようにすると良いと思います。http://www.biscrat.com/の部分は自分のサイトのURLを使ってください。
AppId=http://www.biscrat.com/我輩ゲーム/

AllowRootDirectory

デフォルトの状態ではC:\やD:\にはインストールできないようになっています。[Setup]にAllowRootDirectory=yesという行を書いておくとできるようになります。

AllowNoIcons

[Setup]でAllowNoIcons=yesとすると、インストーラの中で「プログラムグループを作成しない」というチェックボックスが出てくるようになります。このチェックボックスにチェックを入れるとスタートメニューにショートカットが作られません。つまり、[Icons]のなかで{group}から始まるショートカットが無視されます。

デスクトップのショートカット

少し手間がかかりますが、デスクトップのショートカットの有無も選べるようにできます。

まず以下を追加します。これでインストーラの中で「デスクトップにショートカットを作成」というチェックボックスが出てくるようになります。

[Tasks]
Name: DesktopShortcut; Description: デスクトップにショートカットを作成;

さらに、[Tasks]の中のデスクトップにショートカットを作る行にはTasks: DesktopShortcut;を付けます。前提の記事の例では、以下のようになっている行です。

Name: "{commondesktop}\我輩ゲーム"; Filename: "{app}\我輩ゲーム.eXe";

これを以下に変更します。

Name: "{commondesktop}\我輩ゲーム"; Filename: "{app}\我輩ゲーム.eXe"; Tasks: DesktopShortcut; 

UninstallDelete

アンインストール時にはインストール時にコピーしたファイルのみが削除されます。よってゲーム起動後に新規作成されたセーブデータや吉里吉里設定ツールが作成するcfuファイルなどが削除されません。アンインストール時にそれらも削除するには[UninstallDelete]で削除するファイルを登録しなければなりません。

以下はセーブデータの保存先を %APPDATA%我輩ゲーム とした場合の例です。

[UninstallDelete]
Name: {commonappdata}\我輩ゲーム\krkr.console.log; Type: files;
Name: {commonappdata}\我輩ゲーム\krenvprf.kep; Type: files;
Name: {commonappdata}\我輩ゲーム\savecheck; Type: files;
Name: {commonappdata}\我輩ゲーム\*.bmp; Type: files;
Name: {commonappdata}\我輩ゲーム\*.ksd; Type: files;
Name: {commonappdata}\我輩ゲーム\*.kdt; Type: files;     
Name: {commonappdata}\我輩ゲーム\我輩ゲーム.cfu; Type: files;
Name: {commonappdata}\我輩ゲーム; Type: dirifempty;
Name: {userappdata}\我輩ゲーム\krkr.console.log; Type: files;
Name: {userappdata}\我輩ゲーム\krenvprf.kep; Type: files;
Name: {userappdata}\我輩ゲーム\savecheck; Type: files;
Name: {userappdata}\我輩ゲーム\*.bmp; Type: files;
Name: {userappdata}\我輩ゲーム\*.ksd; Type: files;
Name: {userappdata}\我輩ゲーム\*.kdt; Type: files;               
Name: {userappdata}\我輩ゲーム\我輩ゲーム.cfu; Type: files;
Name: {userappdata}\我輩ゲーム; Type: dirifempty;
Name: {app}\我輩ゲーム.cfu; Type: files; 

これでも削除されないファイルを見つけたら教えてください。

KAGEX拡張例(自動検索パスの追加)

KAGではカスタマイズ用にOverride.tjsとAfterinit.tjsが用意されています。

KAGEXではこれに加えてStorages.tjsがあります。これは独自の検索パスを追加するためのスクリプトです。初期状態では存在しないので作成してください。

以下の例ではseフォルダとscenario/openingフォルダを検索パスに追加しています。

// Storages.tjs
Storages.addAutoPath("se/"); // seフォルダを追加
Storages.addAutoPath("scenario/opening/"); // scenario/openingフォルダを追加

KAGEXの初期状態で使えるフォルダ名はbgimage, bgm, evimage, face, fgimage, image, init, main, others, rule, scenario, sound, sysscn, system, thum, uipsd, video, voiceになります。それ以外のフォルダを使ったり、フォルダの中にフォルダを作りたいときはStorages.tjsで追加します。