エフェクトについて



・えへくと
ゲーム中に武器を発砲すると銃口が光りますね(MUZZLE FLASH)、マシンガンを発砲 したら弾道が描かれ(TRACER)、着弾点には火花や水飛沫が上がります。
実際にエフェクトの発生&描画処理を行うのはクライアント側だけの処理ですが、 エフェクト発生を指示するのは主にサーバ側です。
これらエフェクト処理のしくみについて調べてみましょう。 でも、このドキュメントはサラっと読むだけでいいし、あんま分かんなくてもいいでしょう。 どうせ次で実際に作成してみるんですし。

・コールバック関数登録
エフェクトを発生させる方法の一つにコールバック関数登録の方法があります。
クライアント側であらかじめエフェクト名称に対するコールバック関数の登録を行っておき、 サーバ側からそのエフェクト名称指定でエフェクト発生を指示する方法です。

例えば下記の例のようにクライアント側でコールバック関数の定義を書き、DECLARE_CLIENT_EFFECTを 使ってエフェクト名称"MyEffect"とコールバック関数名を指定しておくと、client.dllがロードされた際に "MyEffect"へのコールバック関数割り当てを勝手にやってくれます。
void MyEffectCallback( const CEffectData &data )
{
	// 例だからなんもねーよ
}

DECLARE_CLIENT_EFFECT( "MyEffect", MyEffectCallback );

そんでもってサーバ側でDispatchEffectを呼び出す際に"MyEffect"を指定やりゃあいいんです (クライアント側の処理で必要な値をdataにはセットしとかないといけません)。
また、武器の発砲等、プレイヤー自らのアクションでエフェクトの発生が明白な物は サーバ、クライアント側両方でDispatchEffectを呼んでしまっていいようです。 自分の起こしたアクションなのにサーバからのDispatchを待ってると 「銃撃ってるとすげえラグいモタついた反応だぜ、氏ねやボケェ!」といった苦情がプレイヤーから出てしまうので、 事後承諾の形で先にクライアント側で処理しちゃえってことなんでしょう。
DispatchEffect( "MyEffect", data );

上の例ではコールバック関数内の処理は空っぽですからDispatchEffectされたって 当然何にも起こりません。

・フラグ変化検出
サーバ→クライアントへ更新のかかる変数の変更を検出して、エフェクトを発生させる方法です。

例としては、サーバ側でCBaseAnimating、クライアント側でC_BaseAnimatingから派生したクラスの エンティティで使用しているモデルが1個以上のアタッチメントを持っている物(qcファイルに"$attachment"指定を書いてコンパイルされてる)。 そのクラスに対してメンバ関数DoMuzzleFlash()を呼び出すとアタッチメントの示す相対座標と方向へこの色を0.5秒間発光させてくれます。

これはメンバ変数m_nMuzzleFlashParityのフラグ値をサーバ側、もしくはクライアント側で変更された ことをclient.dllで検出してエフェクトを発生させてます。

・アニメーションのイベント
アニメーションのイベントからエフェクトを発生させる方法です。

武器ビューモデルのMUZZLE FLASHを例に挙げると、仮にモデルのQCファイル中にこんな記述が あったとします。
$attachment "muzzle" "ValveBiped.muzzle" 0 0 0 
$sequence fire01 "Fire01" fps 30 snap activity ACT_VM_PRIMARYATTACK 1 node Fire {
{ event AE_MUZZLEFLASH 0 "SMG1 MUZZLE" }
}

$sequenceのイベント記述で指定されている"AE_MUZZLEFLASH"はMUZZLE FLASHのイベント指定です。 その後にディレイ指定があって、その後に"SMG1 MUZZLE"の記述があります。

クライアント側ソースでc_baseanimating.cppの2975行目を見ると AE_MUZZLEFLASHのイベントを検出してから2638行目のDispatchMuzzleEffectメンバ関数に飛んでます。

で、その飛んだ先では"SMG1 MUZZLE"の記述を"SMG1"と"MUZZLE"に分け、前者をMUZZLE FLASHのエフェクト種、 後者をアタッチメントの指定として扱います。
処理を見る限り、$attachmentの記述で"muzzle"と記述しているからイベントの方で"SMG1 MUZZLE"と書いてある だけで、仮に
$attachment "mogera" "ValveBiped.muzzle" 0 0 0 
と書いてあったらイベント記述には"SMG1 MOGERA"と書いてしまえるようです。つってもわざわざMOGERAなんて書かねーよ。


・どうやって画面に出すんよ?
上記どの手段を取ろうが最終的にはクライアント側のエフェクト発生&描画処理に行き着くわけですが、 銃口が光るのもエフェクト、銃口が火を噴くのもエフェクト、弾道が光るのもエフェクト、 着弾点で破片や火花が散るのもエフェクト、爆発もエフェクト・・・。色々あって腹立ってきます。

一番お手軽なのはFX関数を呼ぶことですかね。いくつか紹介しましょう。これらの関数を使用する場合にはclientsideeffects.hfx_quad.hをインクルードすんのを忘れずに。

・FX_AddCube/FX_AddCenteredCube
// こっちはいいや
void FX_AddCube( const Vector &mins,
	const Vector &maxs, const Vector &vColor, float life, const char *materialName );

// center		位置
// size		サイズ
// vColor		色指定(RGBそれぞれ0.0〜1.0で指定)
// life		破棄されるまでの時間(秒)
// materialName	テクスチャの指定(スプライト)
void FX_AddCenteredCube( const Vector &center,
	float size, const Vector &vColor, float life, const char *materialName );
箱を描いてくれます。centerに中心座標、sizeに箱のサイズを指定するだけのFX_AddCenteredCubeの方が便利です。
でもそれ以前にこれって使い道がねーよ。

例:
FX_AddCenteredCube( position, 10.0f, Vector(1.0f, 1.0f, 1.0f), 5.0f, "sprites/ar2_muzzle3");

なんだかパっとしねえな、どうでもいいや

・FX_AddDiscreetLine
// start		スタート地点
// direction	線の移動方向
// velocity	移動の速さ
// length		線の長さ
// clipLength	線を描画可能な範囲の長さ
// scale		線のスケール(太さ)
// life		破棄されるまでの時間(秒)
// shader		シェーダ指定(スプライト)
void FX_AddDiscreetLine( const Vector& start, const Vector& direction,
	float velocity, float length, float clipLength, float scale, float life, const char *shader );
移動する線を描いてくれます。

例:
FX_AddDiscreetLine( start, direction, velocity, 50.0f, 1000.0f, 5.5f, 10.0f, "effects/gunshiptracer" );
こんにちは"effects/gunshiptracer"です。

ひゅ〜ん!

・FX_AddQuad
// origin		描画地点
// normal		法線ベクトル
// startSize	開始時のサイズ
// endSize		終了時のサイズ
// sizeBias	サイズの補完値にバイアスをかける、flagsにFXQUAD_BIAS_SCALEが
//		指定されている場合のみ有効。範囲は0 < bias < 1.0、
//		0.0に値が近づくにつれて初期の変化量がゆるく、次第に急になってゆく。
//		1.0に値が近づくとその逆になる。
// startAlpha	開始時のアルファ値、1.0で透過なし、0.0で完全に透過っつーか見えねえ
// endAlpha	終了時のアルファ値
// alphaBias	アルファ値の補完値にバイアスをかける、flagsにFXQUAD_BIAS_ALPHAが
//		指定されている場合のみ有効。設定値の意味についてはsizeBiasに同じ
// yaw		元のスプライトを指定度数分回転させた物を初期の状態とする
// deltaYaw	1秒あたりに回転させる度数を指定
// color		色指定(RGBそれぞれ0.0〜1.0で指定)
// lifeTime	破棄されるまでの時間
// shader		シェーダ指定(スプライト)
// flags		サイズ、アルファ値のバイアス指定を有効にするために
//		FXQUAD_BIAS_SCALEやFXQUAD_BIAS_ALPHAを指定する
//		指定しないと補完値は単純な線形補完になる。
void FX_AddQuad( const Vector &origin, 
	const Vector &normal, 
	float startSize, 
	float endSize, 
	float sizeBias,
	float startAlpha, 
	float endAlpha,
	float alphaBias,
	float yaw,
	float deltaYaw,
	const Vector &color, 
	float lifeTime, 
	const char *shader, 
	unsigned int flags );
normalを法線ベクトルとしてスプライトの平面を描画します。lifeTimeで指定した時間内にサイズやアルファ値を指定範囲内で変化させることができます。
vmtに$nocull 1が指定されているスプライトじゃないと裏側から見えないので注意 (分かりやすく言うと、プレイヤー視点からoriginを見た方向とnormalの指す方向の角度差が90度未満なら裏側から見てるんだと思ってください、90度ピッタシなら真横から見てることになるから$nocull 1に指定されてても描画面は見えねえ・・・たぶん)。

例:
FX_AddQuad( position,
	normal,
	100.0f,
	0.0f,
	0.75f,
	1.0f,
	0.0f,
	0.4f,
	random->RandomInt( 0, 360 ),
	random->RandomInt( -90, 90 ),
	Vector( 1.0f, 1.0f, 1.0f ),
	5.0f,
	"effects/combinemuzzle1_nocull",
	(FXQUAD_BIAS_SCALE|FXQUAD_BIAS_ALPHA) );
こんばんは"effects/combinemuzzle1_nocull"です。

くるくる回りながらちっちゃくなって消えてっちゃう♪


(´・ω・`)つもどる