QuakeC入門

2.GRUNTを出世させよう!

 GRUNTちゃんはザコキャラです。プレイヤーのショットガン2発で息絶えてしまうという悲しさ。たまには彼らの力になってあげたい! そういうことで今回GRUNTちゃんをパワーアップさせてあげることにしました。
 GRUNTちゃんの主な処理はsoldier.qcに記述されています。grunt.qcじゃないんですね。この時点でこのキャラがどうでもいい存在だということが伺えます。 では早速soldier.qcの以下に赤で示した部分を追加してください。最初にvoid() army_fireの記述部分を探し出し、 その手前に関数Sol_MissileTouchSol_MissileFlyを追加、そしてarmy_fire自体にも手を加えます。Sol_MissileTouchには見覚えがありますね、 1章でシャンブラー殺しに使ったミサイル接触の処理部分を参考にしています。 まあ、打ち込むよりこのページのソースを根こそぎコピーしてarmy_fireとすげ替えちゃったほうが早いですね。


		.
		.
		.
void() Sol_MissileTouch =		//GRUNTのロケットが何者かに接触した際の処理
{
	local float	damg;		//ダメージ値計算のためにローカル変数damgを宣言

	if (other == self.owner)	//発射した本人に接触した場合は爆発させない
		return;

	if (pointcontents(self.origin) == CONTENT_SKY)	//ロケットが空へめりこんだらロケットを削除
	{
		remove(self);
		return;
	}

	damg = 50 + random()*10;	//random()は0.0〜1.0間の乱数を返す、ロケットのダメージは50に
						//乱数と10の積を加えたものである
	
	if (other.health)					//接触した相手に体力がある場合
	{
		if (other.classname == "monster_shambler")	//接触した相手がシャンブラーだった場合
			damg = damg * 0.5;				//ダメージ半減
		T_Damage (other, self, self.owner, damg );	//接触した相手にダメージを与える
	}

	// entity other(接触した相手)に対し、爆風による特定空間内ダメージ処理を行わない
	// 既にロケットが接触した際、ダメージを与えているからである
	T_RadiusDamage (self, self.owner, 60, other);	//特定空間内ダメージ処理(無視する相手にotherを指定)

	self.origin = self.origin - 8*normalize(self.velocity);		//爆発の表示位置を接触地点より8だけ後退

	WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);	//テンポラリentityのネットワークメッセージ送信
	WriteByte (MSG_BROADCAST, TE_EXPLOSION);	//ここで爆発音を指定している
	WriteCoord (MSG_BROADCAST, self.origin_x);	//爆発音発生地点を指定
	WriteCoord (MSG_BROADCAST, self.origin_y);
	WriteCoord (MSG_BROADCAST, self.origin_z);

	BecomeExplosion ();		//ロケットは爆発へ変身
};

void() Sol_MissileFly =			//ロケット飛行中の処理
{
	local	vector	dir;		//ローカルvectorオブジェクトdirの宣言
	dir = normalize( self.enemy.origin - self.origin ); //ロケットから見た敵の方向の単位ベクトル(長さ1)を得る

	self.velocity = dir * 100;		//ロケットの飛行スピードを指定
	self.angles = vectoangles(dir);		//ロケットの向き(グラフィックの)を指定
	self.nextthink = time + 0.1 ;		//次回thinkで指定された関数を呼び出すタイミングを指定
	self.think = Sol_MissileFly;			//nextthinkで指定したタイミングで呼び出す関数名
};

void() army_fire =			//GRUNTの発砲処理
{
	local	vector	dir;	//発砲する方向を示すローカルvectorオブジェクトdirの宣言
	local	entity	rocket , en;
				//ロケット、敵の処理のためローカルentityオブジェクトrocket、enの宣言
	ai_face();			//敵の方向を向く関数を呼び出す

//	sound (self, CHAN_WEAPON, "soldier/sattck1.wav", 1, ATTN_NORM);

//左右に避けている敵に対してGRUNTのショットガンはあたりにくくなっている
		en = self.enemy;	//enに敵の情報をコピー
	
	dir = en.origin - en.velocity*0.2;		//敵の移動方向に対しやや後退した地点の座標を得る
	dir = normalize (dir - self.origin);	//GRUNTから見た敵の方向の単位ベクトル(長さ1)を得る

//SHOT
	if( vlen( self.origin - self.enemy.origin ) < 250 )		//敵が近距離にいる場合
	{	
		sound (self, CHAN_WEAPON, "soldier/sattck1.wav", 1, ATTN_NORM);		//銃声を鳴らす
		FireBullets (4, dir, '0.1 0.1 0');		//ショットガン攻撃
	}

//ROCKET
	else			//敵との距離が離れている場合
	{
		sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM);	//ロケット発射音を鳴らす

		rocket = spawn ();		//新規entityの情報をmissileへ
		rocket.owner = self;		//ロケットのオーナーをGRUNT自身に
		rocket.movetype = MOVETYPE_FLYMISSILE;		//MOVEタイプにミサイルを指定
		rocket.solid = SOLID_BBOX;		//SOLIDタイプはBBOX(アイテム等もこのタイプ)
		rocket.enemy = self.enemy ;		//ロケットの敵にGRUNTの敵を指定(お前の敵はオレの敵)
		rocket.classname = "pmissile";		//ロケット弾の名前(適当)
		
	//ロケットの速度
		makevectors (self.v_angle);		//前方向のベクトル値v_forwardを得るため
		rocket.velocity = dir * 100;		//ロケットの飛行スピードを指定
		rocket.angles = vectoangles(rocket.velocity);		//ロケットの向き(グラフィックの)を指定
	
		rocket.touch = Sol_MissileTouch;	//ロケットが何者かに接触した場合に呼び出す関数名
	
	//ロケットの詳細
		rocket.nextthink = time + 0.1 ;		//次回thinkで指定された関数を呼び出すタイミングを指定
		rocket.think = Sol_MissileFly;		//nextthinkで指定したタイミングで呼び出す関数名

		setmodel (rocket, "progs/missile.mdl");		//ロケットのmodel指定
		setsize (rocket, '0 0 0', '0 0 0');		//ロケットのサイズ指定
		setorigin (rocket, self.origin + v_forward*8 + '0 0 16');
					//ロケット発射位置はGRUNT自身の位置よりも少し前方
	}
};
		.
		.
		.


どうなったでしょうか?近距離にいる場合GRUNTちゃんは通常の攻撃をしてきますが、少し離れると誘導ロケット弾を発射してくるようになったはずです。
 追加された関数Sol_MissileFlyは関数army_fireでロケットが発射されてからQuakeの時間単位で0.1経過後に呼び出され、 ロケット自らの移動速度と向きを敵へ定めた後、0.1経過後に再び関数Sol_MissileFlyを呼び出すように設定して処理を返しています。 ですから次回関数Sol_MissileFlyが呼び出される時間指定を行っている

self.nextthink = time + 0.1 ;

の0.1の値を大きくすればロケットの方向転換の頻度は下がり、それだけ追尾が甘くなることになります。
 もう一つ追加された関数Sol_MissileTouchは関数army_fireでロケットが発射された後、ロケットが何か(壁や生物)に接触するまで呼び出されません。 ロケットが何かに接触した場合、関数Sol_MissileTouchは呼び出されて接触相手へダメージを与えたりロケットを爆発させ消滅させる等の処理を行っているのです。 よって、前述の関数Sol_MissileFlyは関数Sol_MissileTouchが呼び出されてロケットが爆発後消滅するまで繰り返し0.1間隔で呼び出されることになります。

 しかし、GRUNTちゃんは自ら発射したロケットが障害物へ接触した際の爆発にダメージを受けて自滅してまくり。これではあまりにも馬鹿を通り越して可哀相なので、 自分の発射したロケットが生き物以外に接触した際の爆風には影響を受けないようにしてあげましょう。上記ソースの関数Sol_MissileTouch中にある


	if (other.health)					//接触した相手に体力がある場合
	{
		if (other.classname == "monster_shambler")	//接触した相手がシャンブラーだった場合
			damg = damg * 0.5;				//ダメージ半減
		T_Damage (other, self, self.owner, damg );	//接触した相手にダメージを与える
	}

	// entity other(接触した相手)に対し、爆風による特定空間内ダメージ処理を行わない
	// 既にロケットが接触した際、ダメージを与えているからである
	T_RadiusDamage (self, self.owner, 60, other);	//特定空間内ダメージ処理(無視する相手にotherを指定)


の部分を以下のように書き換えてみましょう。


	if (other.health)					//接触した相手に体力がある場合
	{
		if (other.classname == "monster_shambler")	//接触した相手がシャンブラーだった場合
			damg = damg * 0.5;				//ダメージ半減
		T_Damage (other, self, self.owner, damg );	//接触した相手にダメージを与える

		T_RadiusDamage (self, self.owner, 60, other);	//特定空間内ダメージ処理(無視する相手にotherを指定)
	}
	else 	T_RadiusDamage (self, self.owner, 60, self.owner);	//特定空間内ダメージ処理(無視する相手に自分のownerを指定)


わーい、少しだけGRUNTちゃんが死ににくくなってるー。こういった細かい改造を一個一個積み重ねていけば色々覚えられそうな気がしてきたでしょう?もしも自分でいじれそうな個所があったら試しにいじってコンパイルしてみてもオーケー!いじり過ぎたらエラーばりばりで取り返しがつかなくなっちゃったー、 って時はバックアップしておいた元のsoldier.qcを上書きしちゃえ!ノープロブレム!
 とりあえず「GRUNTを出世させよう」はこれで終わりです。上記改造内容の簡単な説明だけではまだ何言ってんだかさっぱりわかんねー!という方もいるはず。GRUNTは「続・GRUNTを出世させよう」でさらに凶悪化させるとして、次の章では「覚えておくとよいこと」を述べますです。

e-mail:ponpoko@axcx.com