2012/04/05
KAICHO: s_naray[at]yahoo[dot]co[dot]jp
※SPAM防止のため捻ってある

吉里吉里トランジションプラグインの作り方

■はじめに

本文書は、吉里吉里で、トランジションプラグイン(.dll)の作り方について述べる。

吉里吉里でのトランジションプラグインは少し独特だ。吉里吉里プラグインとも 違って、定型の「書き方」がある。中身を知らないと多分理解できないと思うので、 ここに知ってる限りの情報を書き出す次第ナリ。

■まずは環境を用意しよう

…は、吉里吉里プラグインの作り方と 同じなんでそっち参照してくだされ。

■プロジェクト作成と必要なファイル群

こちらも、用意するファイル以外は 吉里吉里プラグインの作り方と 同じなんでそっち参照。あらかじめ用意するファイルは以下二つだけ。
ファイル種別 ファイル名 吉里吉里ソースコード中の場所 備考
ソースファイル tp_stub.cpp kirikiri2/src/plugins/win32/ -
ヘッダファイル tp_stub.h kirikiri2/src/plugins/win32/ -

■トランジションプラグインの仕組み

トランジションプラグインでは、二つのクラスを定義する。

この二つのクラスを自前で定義し、あとはトランジションハンドラプロバイダを V2Link()で吉里吉里に登録すれば、トランジションは可能となる。その意味では 思いのほかシンプル。

KAGから[trans method=xxx ...]が実行されてトランジションが開始された時 (TJSではLayer.beginTransition()ね)、実際に上のクラスのどのメソッドが どういう順番で呼ばれるかというあたりの概要を以下に示す。 本当はこれら以外にも呼ばれるものはあるけれど、まぁ大まかに、ということで。

  1. トランジションハンドラプロバイダの StartTransition() が呼ばれる
    ここでTJSからの引数を展開して、トランジションハンドラのインスタンスを 作成しつつ引数を渡し、引数の handler に代入して吉里吉里に返す。
  2. トランジションハンドラのコンストラクタが呼ばれる
    これは 1. の延長で。
  3. トランジションの画面更新ごとに以下三つを繰り返す。
    「画面更新ごと」とは、例えば1/60秒ごとに画面更新して2秒かけてcrossfadeするなら 全体では120回呼ばれる中間画像を生成する各処理の一度ごと、ということ。

    1. トランジションハンドラのStartProcess()が呼ばれる
      中間画像生成の開始時に一度だけ呼ばれる
    2. 必要な数だけトランジションハンドラのProcess()が呼ばれる
      吉里吉里が画面を分割してマルチスレッド動作する場合は複数回呼ばれる。 そうでなければ一度だけ呼ばれる。引数の画像サイズには分割後のものが 渡されてくるので、単純に引数に従えばよい
    3. 最後にトランジションハンドラのEndProcess()が呼ばれる
      この時点ではトランジションはまだ終了していない。中間画像を 一枚作ったに過ぎない。

  4. トランジションハンドラのMakeFilnalImage()が呼ばれる
    最後の最後、中間画像を全て生成し終えてトランジションが完了したら 呼ばれる。この関数内で最終画像(=背景画像)を生成する。といっても 実際には代入するだけ。
  5. トランジションハンドラプロバイダ中で、1.で作成したインスタンスを破棄する
    つまり、トランジションハンドラのインスタンスは、トランジションごとに 作成・破棄される。

上のような大まかな流れがわかっていれば、何をどう作るのかがわかり易いと思う。 というかこのくらいどっかにサマリで書いといて欲しいです。一応ソースコード上に 書いてあるんだけど、我輩頭悪いからソースコード見てもよくわかんなかったよ!

で、文章だけだとやっぱ判りづらいので、こんなカンジという図解を以下に示す。 ごめんけど我輩にはこれ以上わかりやすく書くのはムリのムリムリだったわいな。 プラグイン登録とトランジション開始にのみ絞って書いてみたので、他の細かいことは 書いてないでありますよ。

■実際にクロスフェードプラグインを作ってみる

上の説明を踏まえつつ、中身を解説しながらクロスフェードプラグインを作ってみる。 既に存在するやん、とか言わないで! 解説付きなところが違うと考えて頂ければ 幸いですホントに。なぜクロスフェードなのかというと、一番簡単そうだから。 それ以外に理由はないッ!

今回作成する自前トランジションdllの名前を、MyCF.dllとする。My Cross Fadeの 略なんだ、と思ってください是非。長い名前だと書くの面倒だから…。

上で作成したビルド環境に加え、今回は以下のファイルを作成する。これらの ファイルが揃えば、トランジションプラグイン(.dll)はビルドできる。

ファイル種別 ファイル名 定義物
ソースファイル MyCF.cpp トランジションハンドラ及びトランジションハンドラプロバイダ
main.cpp トランジションハンドラプロバイダを登録するためのV2Link()とV2Unlink()
ヘッダファイル MyCF.h トランジションハンドラプロバイダをmain.cppから利用するためのプロトタイプ宣言
リソースファイル MyCF.rc dllのバージョン情報等を追加するリソースファイル

●ヘッダ MyCF.h を作成する

MyCF.h は、まぁ言えば(ファイルを纏めれば)無くてもいいんだが、 トランジションハンドラプロバイダを吉里吉里に登録するための関数をプロトタイプ 宣言している。もちろんこの他に便利そうなマクロ・インライン関数や使用する 定数を定義してもよい。以下は最低レベルのもの。

登録関数は RegisterMyCFTransHandlerProvider()、削除関数は UnregisterMyACFTransHandlerProvider() として定義する。実体はこの後、 main.cppの中で定義。

#ifndef MyCF_H
#define MyCF_H

// トランジションハンドラプロバイダの登録関数
void RegisterMyCFTransHandlerProvider();

// トランジションハンドラプロバイダの削除関数
void UnregisterMyCFTransHandlerProvider();
#endif

●main.cppを作成する

このファイルはほぼ定型。吉里吉里に外部DLLを登録するためには、V2Link()と V2Unlink()という二つの関数が必要になる。これは実際には TJSの Plugins.link('dllファイル') を実行した時に吉里吉里が呼び出す。 そのため、この関数は外部に対してexportされて いなければならない。昔は .def ファイルを作ってそこでexportしていたが、 VC++2008なら関数定義の頭に __declspec(dllexport) を付加すればexportされるので、 .defファイルは不要になった。 DLLからのエクスポート(MSDNドキュメント)を参照。DllEntryPoint()は リンカに指定された「最初に実行される関数」。ちうても return 1 だけでよろし。

V2Link()中で、上で定義したトランジションハンドラプロバイダの登録関数を呼び、 V2Unlink()中で、トランジションハンドラプロバイダの削除関数を呼ぶだけ。 スタブの初期化・使用終了というのは必要なものらしいので、何も考えずに くっつけておけばいい、そうな。

#include <windows.h>
#include "tp_stub.h"
#include "MyCF.h"


//---------------------------------------------------------------------------
//#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
	// DLL エントリポイント
	return 1;
}
//---------------------------------------------------------------------------
// V2Link は DLL がリンクされるときに実行される
extern "C" HRESULT __declspec(dllexport) V2Link(iTVPFunctionExporter *exporter)
{
	// スタブの初期化
	TVPInitImportStub(exporter); // 必ずこれは記述
	/*
		TVPInitImpotStub を実行した後は吉里吉里内部の各関数や tTJSVariant 等の
		基本的な型が使用可能になる。
	*/

	// トランジションハンドラプロバイダの登録
	RegisterMyCFTransHandlerProvider();

	return S_OK;
}
//---------------------------------------------------------------------------
// V2Unlink は DLL がアンリンクされるときに実行される
extern "C" HRESULT __declspec(dllexport) V2Unlink()
{
	// トランジションハンドラプロバイダの登録削除
	UnregisterMyCFTransHandlerProvider();

	// スタブの使用終了
	TVPUninitImportStub(); // 必ずこれは記述
	/*
		TVPUninitImpotStub は TVPInitImportStub で確保したメモリを解放する
		ので必ず記述する。
	*/
	return S_OK;
}

●トランジションハンドラプロバイダ(in MyCF.cpp)を作成する

※ここだけ、説明のためにソースコードの記述順が違うので要注意。

上で述べたように、トランジションハンドラプロバイダは トランジションハンドラを吉里吉里に登録するためにラッピングするものだ。 大事な概念が以下三つある。

  1. Addref()とRelease()という二つのメソッドを定義し、参照カウンタを管理する必要があること
  2. GetName()メソッドで返す文字列が、トランジション名となること。ここでは "MyCF" を返し、KAG上から [trans method=MyCF ...] で使えるようにする
  3. StartTransition()メソッドがすなわちKAGの[trans ...]と TJSのLayer.beginTransition() の実体であること

特に見て欲しいのは StartTransition() メソッド。ここでは以下の動作を行う。

  1. トランジションハンドラのインスタンスを作成する
  2. 指定されたパラメータを、トランジションハンドラに渡す(ここではコンストラクタ経由で渡している)
  3. トランジションハンドラのインスタンスを、引数の *handler に代入して呼び出し元に返す
この三つがあれば、トランジションハンドラプロバイダとしては十分だ。
ここまでで判るように、つまりトランジションハンドラプロバイダは、 基本的に「トランジションハンドラのインスタンスを作って引数渡す」ところまで しか実行しない。あとは吉里吉里本体が勝手に実行してくれる。

正直、上で書いている『参照カウンタ』がイマイチよくわかんないんだけど。 レイヤごとに独立して同じトランジションを実行した時の話かしら。 でも、だとすると ローカル変数持っちゃいけないことになっちゃうし。吉里吉里がキャッシュみたいな 形で掴むのかな。

(トランジションハンドラ定義からの続き)

// トランジションハンドラプロバイダのクラス定義
// 必ず iTVPTransHandlerProvider から派生させること。
class tTVPMyCFTransHandlerProvider : public iTVPTransHandlerProvider
{
	tjs_uint RefCount; // 参照カウンタ

public:
	// コンストラクタ
	tTVPMyCFTransHandlerProvider()
	{
		RefCount = 1;
	}

	// デストラクタ
	~tTVPMyCFTransHandlerProvider()
	{
		// 特にすることなし
	}

	// 参照された時に呼ばれる
	tjs_error TJS_INTF_METHOD AddRef()
	{
		// iTVPBaseTransHandler の AddRef
		// 参照カウンタをインクリメント
		RefCount ++;
		return TJS_S_OK;
	}

	// 参照が解除された時に呼ばれる
	tjs_error TJS_INTF_METHOD Release()
	{
		// iTVPBaseTransHandler の Release
		// 参照カウンタをデクリメントし、0 になるならば自身を delete
		if(RefCount == 1)
			delete this;
		else
			RefCount--;
		return TJS_S_OK;
	}

	// トランジションの名前を返す。[trans method=xxx] のxxx部。
	tjs_error TJS_INTF_METHOD GetName(
			/*out*/const tjs_char ** name)
	{
		if(name) *name = TJS_W("MyCF");
		return TJS_S_OK;
	}


	// KAGの[trans ...]、TJSのLayer.StartTransition()の実体
	tjs_error TJS_INTF_METHOD StartTransition(
			/*in*/iTVPSimpleOptionProvider *options,	// option provider
			/*in*/iTVPSimpleImageProvider *imagepro,	// image provider
			/*in*/tTVPLayerType layertype,				// destination layer type
			/*in*/tjs_uint src1w, tjs_uint src1h,		// source 1 size
			/*in*/tjs_uint src2w, tjs_uint src2h,		// source 2 size
			/*out*/tTVPTransType *type,					// transition type
			/*out*/tTVPTransUpdateType * updatetype,	// update typwe
			/*out*/iTVPBaseTransHandler ** handler		// transition handler
			)
	{
		if(type) *type = ttExchange; // transition type : exchange
		if(updatetype) *updatetype = tutDivisible;
			// update type : divisible
		if(!handler) return TJS_E_FAIL;
		if(!options) return TJS_E_FAIL;

		if(src1w != src2w || src1h != src2h)
			return TJS_E_FAIL; // src1 と src2 のサイズが一致していないと駄目

		// KAG→TJSで渡されたオプションを得る
		tTJSVariant tmp;
		// [trans method=MyCF time=2000] なら ↓では tmp には 2000 が入る
		if (TJS_FAILED(options->GetValue(TJS_W("time"), &tmp)) || tmp.Type() == tvtVoid)
			return TJS_E_FAIL; // time 属性が指定されていない
		tjs_int64 time = (tjs_int64)tmp;
		if(time < 2) time = 2; // あまり小さな数値を指定すると問題が起きるので

		// トランジションハンドラのインスタンスを作成
		try {
			*handler = new tTVPMyCFTransHandler(time, src1w, src1h);
		} catch(...) {
			throw;
		}

		return TJS_S_OK;
	}

};

(トランジションハンドラプロバイダの登録・削除へ続く)

●トランジションハンドラ(in MyCF.cpp先頭)を作成する

トランジションハンドラは、トランジションハンドラプロバイダから渡された 引数と、吉里吉里から渡された時間情報を元に、トランジション中の中間画像を もりもり作る。まずはクラス定義と、その前にヘッダをincludeしている部分を示す。

また例によって『参照カウンタ』の意味が我輩よくわかんないんですがさておき、 やってることはトランジションハンドラプロバイダと同じなので説明は省略する。

メンバ変数として、First、StartTick、Time、CurRatio を定義している。 それぞれの説明はソースコード中に書いたので参照して欲しい。吉里吉里は 時間をTickという単位(1/1000秒単位)で管理している。 System.getTickCount()あたりを見れば判るかも。StartTickを コンストラクタ中で代入しておらず、StartProcess()中で代入しているのは、 コンストラクタで現在のTickを取得する方法がないため。…じゃないかなぁ…。
本当は、TVPGetTickCount()なんてグローバルな関数を実行すれば現在のTickは 得られるし、そのほうがFirstなんてヘンなメンバ変数が要らなくなって助かるけれど。 でもTVPStartTickCount()は呼べないんだよなー。
──うーんとね、多分、 コンストラクタが呼ばれた後にStartProcess()が呼ばれるまでに時間がかかると、 その分StartTickと現在のTickとの間に差ができてしまい、 トランジションの出だしが「カクッ」となるのを防ぐためじゃないかな!(妄想)

SetOption()とか、やることないんなら定義しなきゃいいのに、と思うかもしれないが、 親クラスで virtual って書いてあるのでここで定義しないとダメなのでした。 キビシーッ!

#include <windows.h>
#include "tp_stub.h"
#include "MyCF.h"

// MyCF(My CrossFade)トランジションハンドラクラスを定義
class tTVPMyCFTransHandler : public iTVPDivisibleTransHandler
{
	tjs_int RefCount; // このハンドラの参照カウンタ

protected:
	bool          First;		// 一番最初の呼び出しかどうか
	tjs_int64     StartTick;	// トランジションを開始した tick count
	tjs_int64     Time;			// トランジションに要する時間
	double        CurRatio;		// 現在の変化率(動的に変化させる)

public:
	// コンストラクタ
	tTVPMyCFTransHandler(tjs_uint64 time, tjs_int width, tjs_int height)
	{
		RefCount    = 1;		// 新規なので参照カウンタは1
		First       = true;		// 「一番最初である」フラグ
		Time        = time;		// トランジションにかける時間を保存
	}

	// デストラクタ
	~tTVPMyCFTransHandler()
	{
	}

	tjs_error TJS_INTF_METHOD AddRef()
	{
		// iTVPBaseTransHandler の AddRef
		// 参照カウンタをインクリメント
		RefCount++;
		return TJS_S_OK;
	}

	tjs_error TJS_INTF_METHOD Release()
	{
		// iTVPBaseTransHandler の Release
		// 参照カウンタをデクリメントし、0 になるならば delete this
		if(RefCount == 1) {
			delete this;
		} else {
			RefCount--;
		}
		return TJS_S_OK;
	}

	tjs_error TJS_INTF_METHOD SetOption(
			/*in*/iTVPSimpleOptionProvider *options // option provider
		)
	{
		// iTVPBaseTransHandler の SetOption
		// とくにやることなし
		return TJS_S_OK;
	}

	// トランジション中の中間画像一枚生成開始
	tjs_error TJS_INTF_METHOD StartProcess(tjs_uint64 tick);

	// トランジション中の中間画像一枚生成完了
	tjs_error TJS_INTF_METHOD EndProcess();

	// トランジション中の中間画像一枚生成(画像は分割され、本関数が複数同時に実行されることがある)
	tjs_error TJS_INTF_METHOD Process(
			tTVPDivisibleData *data);

	void Blend(tTVPDivisibleData *data);

	// 最終画像にする
	tjs_error TJS_INTF_METHOD MakeFinalImage(
			iTVPScanLineProvider ** dest,
			iTVPScanLineProvider * src1,
			iTVPScanLineProvider * src2)
	{
		*dest = src2; // 常に最終画像は src2
		return TJS_S_OK;
	}
};

(実際のメソッド定義に続く)

このクラスの実際のメソッド定義は以下の通り。上で説明したとおり、 一枚の絵を生成するために、StartProcess()x1→Process()x分割数回→EndProcess()x1 を延々繰り返す。 下の関数定義のキモは、Process()→Blend()。この中で、実際の画像を 経過時間に応じてクロスフェードしている。

Process()に渡される引数data(tTVPDivisibleData型、 tp_stub.hで定義されている)には、 主に三種類の値が入っている。詳細はヘッダの中を見て欲しい。

  1. data->Src1*: トランジション開始前の画像、つまり現在の表画面の画像のデータ
  2. data->Src2*: トランジション終了後の画像、つまり現在の裏画面の画像のデータ
  3. data->Dest*: トランジション中の画像を生成する先の画面の画像のデータ
つまり、Blend()では、現在の時間(StartProcess()に渡されるtick)に応じて data->Src1とdata->Src2の画面を重ね合わせて data->Dest に出力しているわけ。 これがトランジションの正体だ。

画像が分割されていた場合、画像サイズとその位置は、以下のようになっている。

data->Dest/Src1/Src2などは iTVPScanLineProvider クラスであり、 これは tp_stub.hの 5845行あたり(吉里吉里3.32r2の場合)で定義されている。 ここで使っているGetScanLine()メソッドは、名前の通りその画像のY座標を指定すると そのライン先頭を返してくれる。ただし読み込みのみで、書き込みの時は別の GetScanLineForWrite()を使う。理由は説明難しいが、吉里吉里は内部で画像の バッファを共有できるものは共有しているため、「書き込みます」というときは この共有を解除しないと、「こっちの画像変更したら別の画像も変わっちゃった!」 ということになったりするため。
…とかそういう情報ってソースコード以外のどこにも書いてないし。キィッ!

(トランジションハンドラクラス定義からの続き)

tjs_error TJS_INTF_METHOD tTVPMyCFTransHandler::StartProcess(tjs_uint64 tick)
{
	// トランジションの画面更新一回ごとに呼ばれる
	// トランジションの画面更新一回につき、まず最初に StartProcess が呼ばれる
	// そのあと Process が複数回呼ばれる ( 領域を分割処理している場合 )
	// 最後に EndProcess が呼ばれる

	// 最初の実行なら、StartTick を設定
	if (First) {
		StartTick = tick;
		First = false;
	}
		
	// CurRatio(現在のトランジション進行度(0〜1.0))を求める
	CurRatio = (double)(tick-StartTick)/Time;
	CurRatio = (CurRatio < 0.0) ? 0.0 : (CurRatio > 1.0) ? 1.0 : CurRatio;

	return TJS_S_TRUE;
}

//---------------------------------------------------------------------------
tjs_error TJS_INTF_METHOD tTVPMyCFTransHandler::EndProcess()
{
	// トランジションの画面更新一回分が終わるごとに呼ばれる

	if(CurRatio >= 1.0) return TJS_S_FALSE; // トランジション終了
	return TJS_S_TRUE;	// トランジション継続
}


//---------------------------------------------------------------------------
tjs_error TJS_INTF_METHOD tTVPMyCFTransHandler::Process(
			tTVPDivisibleData *data)
{
	// トランジション画像(分割されている場合はその各領域)ごとに呼ばれる
	// 吉里吉里は画面を更新するときにいくつかの領域に分割しながら処理する
	// ことがあり、このメソッドは画面更新一回につき複数回呼ばれる場合がある
	// data には領域や画像に関する情報が入っている

	if (CurRatio == 0.0) {
		// 一番最初の呼び出しなら、最初の画像(=data->Src1)にする
		data->Dest     = data->Src1;
		data->DestLeft = data->Src1Left;
		data->DestTop  = data->Src1Top;
	} else if (CurRatio >= 1.0) {
		// 一番最後の呼び出しなら、最後の画像(=data->Src2)にする
		data->Dest     = data->Src2;
		data->DestLeft = data->Src2Left;
		data->DestTop  = data->Src2Top;
	} else {
		//中間画像を作る
		Blend(data);
	}

	return TJS_S_OK;
}

//---------------------------------------------------------------------------
// CurRatioに応じて data->Src1 と data->Src2 をブレンドして data->Dest を作成する
void tTVPMyCFTransHandler::Blend(tTVPDivisibleData *data)
{
	// 中間画像作成先(=dest)、元画像(src1)、先画像(src2)のバッファアドレスを得る
	tjs_uint8 *dest;
	const tjs_uint8 *src1, *src2;
	data->Dest->GetScanLineForWrite(data->DestTop, (void**)&dest);
	data->Src1->GetScanLine(data->Src1Top, (const void**)&src1);
	data->Src2->GetScanLine(data->Src2Top, (const void**)&src2);

	// 各画像のピッチ(縦1ドット移動した時のメモリ上の移動バイト数)を得る
	tjs_int destpitch, src1pitch, src2pitch;
	data->Dest->GetPitchBytes(&destpitch);
	data->Src1->GetPitchBytes(&src1pitch);
	data->Src2->GetPitchBytes(&src2pitch);

	// 画像の分割状態に合わせ、dest/src1/src2の開始位置を変更する
	dest += data->DestLeft * sizeof(tjs_uint32);
	src1 += data->Src1Left * sizeof(tjs_uint32);
	src2 += data->Src2Left * sizeof(tjs_uint32);
	
	// data->Width x data->Height の画像データをブレンドする
	// 単純なクロスフェードならこう。他のトランジションならここがもっと複雑になる
	tjs_int h = data->Height;
	for (tjs_int y = 0; y < data->Height; y++) { 
		for (tjs_int x = 0; x < data->Width; x++) {
			// 以下は非常に効率が悪いので注意。自前で作るときはもっと最適化してくだされ
			tjs_int idx = x*sizeof(tjs_uint32);
			dest[idx+0] = (tjs_uint32)(src1[idx+0]*(1.0-CurRatio) + src2[idx+0]*CurRatio);
			dest[idx+1] = (tjs_uint32)(src1[idx+1]*(1.0-CurRatio) + src2[idx+1]*CurRatio);
			dest[idx+2] = (tjs_uint32)(src1[idx+2]*(1.0-CurRatio) + src2[idx+2]*CurRatio);
			dest[idx+3] = (tjs_uint32)(src1[idx+3]*(1.0-CurRatio) + src2[idx+3]*CurRatio);
		}
		dest += destpitch;
		src1 += src1pitch;
		src2 += src2pitch;
	}
}

(トランジションハンドラプロバイダのクラス定義へ続く)

●トランジションハンドラプロバイダの登録・削除関数(in MyCF.cpp末尾)を作成する

ここまできたらラストスパート、あとはトランジションハンドラプロバイダを 登録・削除する RegisterMyCFTransHandlerProvider()と UnregisterMyCFTransHandlerProvider()を定義するのみ。 この二つは、static に定義してあるトランジションハンドラプロバイダの ポインタを使って作成したインスタンスを、TVPAddTransHandlerProvider()で 吉里吉里に登録、TVPRemoveTransHandlerProvider()で削除するだけだ。 参照カウンタがあるので、単純に delete できないことに注意。

(トランジションハンドラプロバイダのクラス定義からの続き)

// 登録・削除の際に使用する静的変数
static class tTVPMyCFTransHandlerProvider *MyCFTransHandlerProvider;

//---------------------------------------------------------------------------
void RegisterMyCFTransHandlerProvider()
{
	// TVPAddTransHandlerProvider を使ってトランジションハンドラプロバイダを
	// 登録する
	MyCFTransHandlerProvider = new tTVPMyCFTransHandlerProvider();
	TVPAddTransHandlerProvider(MyCFTransHandlerProvider);
}
//---------------------------------------------------------------------------
void UnregisterMyCFTransHandlerProvider()
{
	// TVPRemoveTransHandlerProvider を使ってトランジションハンドラプロバイダを
	// 登録抹消する
	TVPRemoveTransHandlerProvider(MyCFTransHandlerProvider);
	MyCFTransHandlerProvider->Release();
}
//---------------------------------------------------------------------------

●リソースファイル(MyCF.rc)を書く

DLLにバージョン入れたり作成者の名前入れたりするために、リソースファイルを 作っておこう。詳細は 吉里吉里プラグインの作り方の 『リソースファイルを書く』項に譲る。ここでは「こういうの書きました」 という例を貼っておく。

#include <winver.h>

VS_VERSION_INFO    VERSIONINFO 
FILEVERSION        0,1,0,0
PRODUCTVERSION     0,1,0,0
FILEFLAGSMASK      VS_FFI_FILEFLAGSMASK 
FILEFLAGS          0x00000000L
FILEOS             VOS_NT_WINDOWS32
FILETYPE           VFT_DLL
FILESUBTYPE        VFT2_UNKNOWN
BEGIN
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x0409, 1200
    END

    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904b0"
        BEGIN
            VALUE "CompanyName",      "KAICHO Soft"
            VALUE "FileDescription",  "MyCF.dll for 吉里吉里2"
            VALUE "FileVersion",      "0, 1, 0, 0\0"
            VALUE "InternalName",     "MyCF\0"
            VALUE "LegalCopyright",   "Copyright (C) 2012 KAICHO\0"
            VALUE "OriginalFilename", "MyCF.dll\0"
            VALUE "ProductName",      "MyCF\0"
            VALUE "ProductVersion",   "0, 1, 0, 0\0"
            VALUE "SpecialBuild",     "\0"
        END 
    END
END

// 0.1.0.0 新規作成

■おわりに

これで一通りクロスフェードトランジションができるトランジションプラグインが 完成した。buildして、first.ks の先頭くらいでPlugin.link('MyCF.dll')して、 確かに[trans method=MyCF time=2000][wt]とかで トランジションできることを 確認してほしい。
今回はレイヤモードによって処理を変える、のような高級なことは全然してない。 そのあたりはご容赦。吉里吉里デフォルトのクロスフェードトランジションでは そういうことちゃんとやってるけどね!(ダメじゃん)。

最初に『思いのほかシンプル』って書いたけど、これだけ並べるとやっぱり やることに対して書かなきゃいけないことが多いね。もうちょっとシンプルに なるように、テンプレートでも書こうかしらん。

さて、こっちでも知ってることは全部書いた! 間違いがあれば是非正してくだされ。 公開→指摘→修正→再公開のループで、コンテンツがよりよくなることを 望むよ!

■超与太話

…を、ここに書いてたんだけど、長くなったので こちらに独立させた。