サンプル

概要

『雨』パーティクルをサンプルとして用意しました。
プロジェクトは[particle_plugin\sample_rain]にあります。
このプロジェクトは Microsoft Visual Studio 2005 用のものです。
VS2008 でもコンバートを行うことでコンパイルする事が出来ます。

雨パーティクルといっても真上から真下に垂直落下させるだけでは単純すぎるので、ある程度の角度を指定出来るようにしてあります。

cs2Particleクラス

sample_rain.cpp の cs2Particle クラスを編集してパーティクルを作成します。
追加のメンバ変数は sample_rain.h で定義しています。
今回は各パーティクルの落下方向を保持するVEC3のポインタを定義しています。

class cs2Particle : public cs2ParticleBase
{

public:
	cs2Particle( );
	~cs2Particle( );

	VOID Init( LPINITPARAM p );
	VOID Exec( LPEXECPARAM p, DWORD before, DWORD frame );
	VOID Stop( LPSTOPPARAM p );

private:
	// ポジションリセット
	void ResetPos( long no, float w, float h, float tx, float ty );

	// 移動ベクトル取得
	VEC3 cs2Particle::SetDir( long no, float spd, float r1, float r2 );

private:
	// 移動ベクトル
	LPVEC3	lpDir;
};

コンストラクタ(cs2Particle)

cs2Particleクラスが作成されるときに呼ばれる関数です。
プライベート変数[lpDir]を初期化します。
この段階ではまだパーティクル数は分からないので、ここではメモリ確保は行っていません。

/********************************************
Public Function	cs2Particle
	コンストラクタ
input  :
output :
*********************************************/
cs2Particle::cs2Particle( )
{
	lpDir = NULL;
}

デストラクタ(~cs2Particle)

cs2Particleクラスが破棄(delete)されるときに呼ばれる関数です。
プライベート変数[lpDir]にセットされたメモリ領域を破棄しています。

/********************************************
Public Function	~cs2Particle
	デストラクタ
input  :
output :
*********************************************/
cs2Particle::~cs2Particle( )
{
	if( lpDir != NULL )
	{
		free( lpDir );
		lpDir = NULL;
	}
}

初期化(Init)

パーティクルが実行される直前に呼ばれる関数です。
ここで初めてパーティクル数を知ることが出来ます。
その為、lpDirにパーティクル数分のメモリを割り当てます。

また、各パーティクルの初期位置と移動方向を設定しています。
このように、全てのパーティクルを初期化します。

/********************************************
Public Function	Init
	初期化
input  :	LPINITPARAM	p	初期化パラメータ
output :
*********************************************/
void cs2Particle::Init( LPINITPARAM p )
{
	float* reg = p->param;

	if( lpDir == NULL )
	{
		// メモリが確保されていない場合は確保
		lpDir = (LPVEC3)malloc( p->count * sizeof(VEC3) );
	}

	if( lpDir != NULL )
	{
		for( DWORD i = 0; i < p->count; i++ )
		{
			// 初期位置の設定
			ResetPos( i, p->screen_width, p->screen_height, p->width, p->height );
			SetDir( i, reg[0], PPF->Deg2Rad(reg[1]), PPF->Deg2Rad(reg[2]) );
		}
	}
}

パーティクル数は p->count で得ることが出来ます。
また、パーティクルを実行する際に指定されたパラメータは p->param で得ることが出来ます。
この値に基づき、パーティクルを初期化します。
関数 ResetPos(初期座標の算出)と SetDir(移動方向の設定)については後ほど説明します。

実行(Exec)

パーティクルが実行されている間、毎フレーム呼ばれる関数です。
この関数内でパーティクルを動かします。

/********************************************
Public Function	Exec
	実行
input  :	LPEXECPARAM	p	実行パラメータ
		DWORD		before	前回実行時のフレーム値
		DWORD		frame	フレーム値
output :
*********************************************/
void cs2Particle::Exec( LPEXECPARAM p, DWORD before, DWORD frame )
{
	float	f;
	float*	reg;

	if( lpDir != NULL )
	{
		f = (float)(frame - before);
		reg = p->param;
		
		for( DWORD i = 0; i < p->count; i++ )
		{
			lpParticle[i].pos.x += lpDir[i].x * f;	※1
			lpParticle[i].pos.y += lpDir[i].y * f;
			lpParticle[i].pos.z += lpDir[i].z * f;

			if( lpParticle[i].pos.y > p->screen_height + 200.f )
			{
				// 下まで落ちたので、再度位置を初期化
				ResetPos( i, p->screen_width, p->screen_height, p->width, p->height );
				SetDir( i, reg[0], PPF->Deg2Rad(reg[1]), PPF->Deg2Rad(reg[2]) );
			}
		}
	}
}

引数の before と frame は共にフレーム値になります。
before は前回 Exec 関数が呼ばれたときのフレーム値、frame は現在のフレーム値です。
フレーム落ちが無ければ、frame - before = 1 となりますが、フレーム落ちが発生するとこの差はより大きくなります。
パーティクルを動かすときは、frame - before 分のフレームを動かす必要があります。
※1 で f を掛けているのは、この為になります。
これを行わないと、フレーム落ちすればするほどゆっくりとした動作になってしまいますので、注意して下さい。

停止(Stop)

パーティクルが停止される時に呼ばれる関数です。
今回は特に行うことは無いので、何も処理していません。

/********************************************
Public Function	Stop
	停止
input  :	LPSTOPPARAM	p	停止パラメータ
output :
*********************************************/
void cs2Particle::Stop( LPSTOPPARAM p )
{

}

ResetPos 関数

各パーティクルの座標の初期化を行っている関数です。

/********************************************
Private Function	ResetPos
	パーティクル位置を初期化
input  :	long	no	初期化するパーティクルNo
		float	w	スクリーンの幅
		float	h	スクリーンの高さ
		float	tx	パーティクル画像の幅
		float	ty	パーティクル画像の高さ
output :
*********************************************/
void cs2Particle::ResetPos( long no, float w, float h, float tx, float ty )
{
	lpParticle[no].pos.x = PPF->frand( ) * w * 2.f - w * 0.5f;
	lpParticle[no].pos.y = -(PPF->frand( ) * h * 2.f);

	lpParticle[no].base.x = tx * 0.5f;
	lpParticle[no].base.y = ty * 0.5f;
}

lpParticle[no].pos.x / lpParticle[no].pos.y に数値を設定する事でパーティクルの表示座標を設定します。
今回は斜めに動くことを考慮して、X座標は2画面分(-0.5〜1.5)の間をランダムで設定しています。
Yは表示エリアより上2画面分の中に納まるようにランダムで設定しています。

また今回はパーティクルの中心位置を、真ん中に設定しています。
これで画像の中心を原点に回転するようになります。

PPF->frand( ) 関数は、不動少数 0.0 〜 1.0 の間のランダムな値を返す関数です。
システムで用意している関数は、このように PPF からアクセスすることが出来ます。

SetDir 関数

パーティクルの移動方向をセットする関数です。

/********************************************
Private Function	SetDir
	パーティクル移動方向をセット
input  :	long	no		パーティクルNo
		float	spd		スピード(パラメータ0)
		float	r1		角度1(パラメータ1)
		float	r2		角度2(パラメータ2)
output :	VEC3	移動ベクトル
*********************************************/
cs2Particle::VEC3 cs2Particle::SetDir( long no, float spd, float r1, float r2 )
{
	VEC3 v;
	float r;

	v.x = 0.f;
	v.y = spd;
	v.z = 0.f;

	r = PPF->frand( ) * (r2 - r1) + r1;
	PPF->VecRotateZ( &v, r );
	lpDir[no] = v;

	lpParticle[no].rot.z = -r;

	return v;
}

lpDir に角度を指定しつつ、パーティクル自体もその方向に回転させています。

さいごに

簡単ではありますが、パーティクルプラグインの説明をしました。
パーティクルプラグインは、パーティクル個々の表示座標などを連続的に操作する事で全体の動きを制御します。
言い換えると、それだけ行えば、あとの事はcs2システムが行ってくれます。

これを元にオリジナルのパーティクルにチャレンジしてもらえれば幸いです。