射撃

射撃  格闘ゲームならあまり必要ないですが、シューティング系を作ろうとしたらやっぱり必要、射撃機能。
単発なら結構楽そうなのですが、気持ちよくガンガンばらまける射撃機能をつけようと思います。
マシンガンのような連続射撃とハンドガンのような単発射撃の機能です。

 今回はちょっと解説が長くなってしまいましたのでゆっくり読んでいって下さい。


素材

 弾丸の弾は画面上にたくさん表示されると動作が重くなる原因になります。
高性能なグラフィックボードを載せていればいいのですが、誰もが高性能ではないのが現実です。 できるだけ重くならないようにしたいですね。
しかし今回は弾丸の数はできるだけ多くしたい。そこで、弾も多く表示して動作も軽くなる方向で やっていこうと思います。

 まずは弾丸の素材ですが、「速度比較1」で比較してみた結果から必然的にビルボードでやることになります。
鉄砲弾ぐらいならビルボードで十分でしょう。ミサイルでやりたい場合は、今回の応用で画面に残る数を抑えてやれば 形状モデル(sig)を使用して形を作っても問題ないと思います。もちろんポリゴン数は抑えないといけないと思いますが。

 弾丸の画像はこんな感じで適当に作ってみました。
弾丸画像
黒(0,0,0)を背景にしたBMP画像です。


解説

弾丸の管理

 弾丸の動作を管理するために全部の弾丸一つ一つに配列変数を割り当てます。こんな感じ。

dim bbid, 6, SHOTMAX ;弾丸情報用配列変数(パラメータ、弾丸ストック)

使用する弾丸はSHOTMAX個です。
弾丸1発には6つのパラメータが保持できます。パラメータの内容はこんな感じです。
bbid.0.numビルボードID
配列の番号とビルボードIDは必ずしも一致しないため必要です。
bbid.1.num弾丸フラグ(0:OFF,1:ON)
弾丸の使用状況を記録します。
OFFの時は弾丸は発射されていません。ONのときは弾丸は発射されています。
bbid.2.num寿命カウンタ
射撃後から経過したフレーム数をカウントするのに使用します。
bbid.3.numx方向速度
射撃方向で変化するのでこれも必要。
bbid.4.numy方向速度
bbid.5.numz方向速度
 この配列変数を使った弾丸の処理は次のような感じになります。
弾丸が発射されたら初速度を設定、フラグをONにします。
フラグONのビルボードだけ選んで設定された速度で移動させます。
寿命が来たらフラグをOFFにして弾丸を消滅させます。
という流れです。


弾丸の寿命

 弾丸の寿命を設定しておかないと打った弾がメモリーに無駄に蓄積されていくばかりです。 地平線の彼方に飛んでいった弾丸は普通使われることはありませんし、実際の弾丸も無限に真っ直ぐ飛んでいくことはありません。(宇宙でも)
ということで、撃って使わなくなった弾丸は再利用するために寿命を設定して削除してやります。

 弾丸の寿命は、発射からの経過時間を使う方法や発射位置からの距離などを使う方法が考えられますが、 今回は前者、発射から一定時間経過したら消滅する方法をとります。

#define SHOTLIFECOUNT 120 ;弾丸寿命

弾丸の寿命を120フレームとしました。
FPSを60フレーム(1秒60ループ)にしています。弾丸は連続射撃で1秒あたり10発撃てるようにしようとすると1発あたり6フレームだから…。

(120フレーム) ÷ (6フレーム/発) = 20 発

ということで使用する弾丸の数は 20発あれば十分です。

#define SHOTMAX 20 ;弾丸予約数


連続射撃と単発射撃

連続射撃はトリガーボタンを押すと、押している間射撃しつづけるものです。(例:マシンガン)
単発射撃はトリガーボタンを1回押すと、1発弾がでるものです。押しっぱなしにしても1発しか弾は出ません。(例:拳銃)

 単発射撃は一撃必殺や威力や射程距離の長い武器でよく使いますし、連続射撃は弾幕張るのにほしいですね。
ということで両方実現してみましょう。

キーの状態を取得には、

E3DGetKeyboardState keybuf ;キー状態取得

としてkeybufにキーの状態が返るようにしています。
単発射撃用にキーが押しっぱなしかどうか分かるように変数を用意しました。

dim keybufon,256

変数keybufonは前回のループでのキーの状態を保存するために使用します。
押しっぱなしならkeybufkeybufonが一致するということになります。

では、スクリプトのほうを見てみます。まずは連続射撃から。
	;連射
	cnttrg++	;トリガーカウント
	if keybuf.'Z' = 1 {
		if cnttrg>=6 {		;6フレームごとに1回射撃(60フレームにつき10回射撃)
			gosub *make_shot
		}
	}
 cnttrgはトリガーを引くタイミングを制御する変数です。サブルーチンmake_shotで0にリセットされていて、 6ループごとに1回射撃するようになっています。
ここを修正すれば連射スピードが変えられます。
サブルーチンmake_shotでは、弾の作成とパラメータ設定(発射)を行なっています。

次に単発射撃を見てみましょう。
	;単発
	if keybuf.'X' = 1 {
		if keybufon.'X' = 0 {	;スペースキーが前回ループ時に押されていなかった場合
			gosub *make_shot
		}
		keybufon.'X' = 1
	} else {
		keybufon.'X' = 0
	}
keybufonは前回のループ時のkeybufの値です。これでトリガータイプで検出できるようになります。

ちょっと適当な気もしますが、とりあえずこんなもんいいでしょう。


弾丸の作成とパラメータ設定(発射)

 サブルーチンmake_shotの解説です。

 まずは発射する弾丸を作成します。
配列変数bbidで使っていないもの(フラグが0,OFFのもの)を探して、作成した弾丸のパラメータを設定します。
配列はSHOTMAX個あるので見つかるまで全部探します。
	;空いている配列を使用して弾丸を作成する
	repeat SHOTMAX
		if bbid.1.cnt = 0 {
			E3DCreateBillboard mediadir2, px*3, py*3, 1, bbid.0.cnt, 1, 1	;表示画像サイズの調整はここでやる。
			E3DSetBlendingMode -1, bbid.0.cnt,1
			E3DSetRenderState -1, bbid.0.cnt, D3DRS_LIGHTING, 0
			shotnum = cnt
			break
		} else {
			shotnum = -1
		}
	loop

アッドモード(アッドモードって何でしょうか?)にしてライトの方向を無効FALSE(0)にしてビルボードの向きで明るさが変わらないようにします。

E3DSetBlendingMode -1, bbid.0.cnt,1
E3DSetRenderState -1, bbid.0.cnt, D3DRS_LIGHTING, 0

これで弾丸っぽい雰囲気が出ます。
D3DRS_LIGHTINGは…DirectXのマニュアル見てください。
(※マニュアルにはフォグとか載ってますが現在のEasy3D(v1.1.2.0)ではフォグは使えません。)


 弾丸を作成したら弾丸のパラメータを設定します。
とりあえず自機の位置と方向から発射位置を設定して、弾丸を作成します。
	;弾丸発射位置調整
	E3DGetPos hsid1, saveposx1, saveposy1, saveposz1
	E3DMultQVec axisqid, 0, 600, -700, x, y, z
	E3DSetBillboardPos bbid.0.shotnum, saveposx1+x, saveposy1+y, saveposz1+z

弾丸の速度を設定し、フラグをonにします。発射したのでトリガータイミングを決めるcnttrgもリセットします。
	;初速度設定
	E3DMultQVec axisqid, 0, 0, -500, vlctysid1x, vlctysid1y, vlctysid1z	;弾丸速度
	bbid.3.shotnum = vlctysid1x	;初速度設定
	bbid.4.shotnum = vlctysid1y
	bbid.5.shotnum = vlctysid1z
	bbid.1.shotnum = 1	;弾丸フラグon
	cnttrg = 0	;トリガーカウントリセット


弾丸発射後の処理

 発射後は移動か消滅かですね。
移動行ってみましょう。
	if (bbid.1.cnt = 1)&(bbid.2.cnt <= SHOTLIFECOUNT) {
		;	弾丸移動
		bbid.2.cnt++
		E3DGetBillboardInfo bbid.0.cnt, x,y,z, texname,transparent,w,h		;ビルボードの座標取得
		E3DSetBillboardPos bbid.0.cnt, bbid.3.cnt+x, bbid.4.cnt+y, bbid.5.cnt+z		;現在位置と速度から移動後の座標を計算
	}
bbid.1.cntがフラグで、SHOTLIFECOUNTが寿命。bbid.2.cntで弾丸それぞれの寿命をカウントします。
移動は…、見たままですね。


	;	弾丸消滅
	if bbid.2.cnt > SHOTLIFECOUNT : E3DDestroyBillboard bbid.0.cnt	;弾丸削除
	bbid.1.cnt = 0	;弾丸フラグoff
	bbid.2.cnt = 0	;寿命カウンタリセット
	;速度ゼロ
	bbid.3.cnt = 0
	bbid.4.cnt = 0
	bbid.5.cnt = 0
E3DDestroyBillboardで削除して配列変数bbidの中身をリセットします。


サンプル

 タコモデルを操作して射撃を行なうサンプルです。
最近スクリプトも長くなってきたので、解説のためあんまり関係なさそうなところをいくつか削除しています。
配布スクリプトは実行可能なように全スクリプト掲載していますのでご安心下さい。
操作はキーボードを使用します。操作方法はつぎのとおり。
キー入力 動作
カーソルキー(↑↓) 前後移動
カーソルキー(←→) 方向転換
スペース ジャンプ
Escキー 終了
Zキー 連続射撃
Xキー 単発射撃
スクリプトと必要なファイルはこちらです。→[e3d018.zip}
タコモデルのファイルtako.sigとタコ用テクスチャファイルtako01.bmpを用意しておいてください。


;
;	射撃するサンプル
;

#include "e3dhsp.as"

	;/////////////////
	;
	;	初期化
	;
	E3DInit
	dim keybuf, 256		;キー入力
	E3DCreateFont 24,12,400,,,,"MS ゴシック",fontid	;フォントの設定
	fkabe = 0	;壁データとの当たり判定(0:off / 1:on)
	g100 = -100	;重力加速度g×100 [単位/s^2](1.00/60 [単位/コマ^2])
	dim keybufon,256	;キーをトリガータイプで検出するためのフラグ(0:前回ループ時に押されていなかった、1:前回ループ時に押されていた)


	;/////////////////
	;	カメラの初期化
	;	ライトの設定
	;/////////////////
	【いつもどおりなので省略】


	;/////////////////
	;
	;	形状データのロード
	;
	sdim mediadir, 2048
	mediadir = curdir + "\\tako.sig"
	E3DSigLoad mediadir, hsid1
;	E3DSetPos hsid1, 5000, 0, 5000
	E3DSetPos hsid1, 5000, 1000, 5000
	E3DSetBeforePos hsid1

	E3DCreateQ axisqid

	;ビルボード作成
	sdim mediadir2, 2048
	mediadir2 = curdir + "\\shot01.bmp"		;適当なサイズのbmpファイルを指定してください。
	;画像サイズ取得
	buffer 2 : picload mediadir2
	px = winx : py = winy
	gsel 0, 1

	;モデル配置
	#define SHOTMAX 20			;弾丸予約数(120フレーム/6フレーム毎発=20発)
	#define SHOTLIFECOUNT 120	;弾丸寿命
	dim bbid, 6, SHOTMAX	;弾丸情報用配列変数(パラメータ、弾丸ストック)
		;bbid.0.id	ビルボードID
		;bbid.1.id	弾丸フラグ(0:OFF,1:ON)
		;bbid.2.id	寿命カウンタ(射撃後から経過したフレーム数をカウントする)
		;bbid.3.id	x方向速度
		;bbid.4.id	y方向速度
		;bbid.5.id	z方向速度


	;/////////////////	
	;
	;	地面の作成
	;
	【いつもどおりなので省略】


;////////////////////////////////////////////////////////////////

;/////////////////
;
;	メインループ
;
*main

	E3DGetKeyboardState keybuf	;キー状態取得
	if keybuf.VK_ESCAPE = 1 : goto *bye ; [ESC]で終了

	gosub *MoveChara		;キャラクター移動
	gosub *GravChr	;重力制御

	;バックバッファへの書き込み作業を行う
	E3DBeginScene	;-----シーンスタート
		;モデルが、視野内にあるか判定
		E3DChkInView hsid1	;キャラクター
		E3DChkInView hsid0	;地面
		E3DChkInView hsid2	;壁

		gosub *ChkConf	;地面との当たり判定(E3DChkInViewより後で呼ぶ)
		gosub *MoveCamera		;カメラ移動

		;バックバッファにレンダリングする。
		E3DRender hsid1, 0
		E3DRender hsid0, 0
		E3DRenderBillboard	;全ビルボードの描画

	E3DEndScene		;-----シーン終了
	E3DPresent		;バックバッファの内容を、プライマリバッファに転送。描画する。

	E3DSetBeforePos hsid1

	E3DWaitbyFPS 60 : await 0
goto *main

;////////////////////////////////////////////////////////////////
;
;	サブルーチン
;

;/////////////////
;
;	終了処理
;
*bye
	E3DDestroyLight lid1	;ライトを破棄
	E3DBye
	end



;/////////////////
;
;	キャラクター移動
;
*MoveChara
	forwardstep = 100	;移動速度(前進)
	backstep = -forwardstep	;移動速度(後退)
	degstep = 5
	mdegstep = -degstep

	E3DGetDirQ2 hsid1, axisqid


	;	射撃
	;連射
	cnttrg++	;トリガーカウント
	if keybuf.'Z' = 1 {
		if cnttrg>=6 {		;6フレームごとに1回射撃(60フレームにつき10回射撃)
			gosub *make_shot
		}
	}

	;単発
	if keybuf.'X' = 1 {
		if keybufon.'X' = 0 {	;スペースキーが前回ループ時に押されていなかった場合
			gosub *make_shot
		}
		keybufon.'X' = 1
	} else {
		keybufon.'X' = 0
	}

	;弾丸にパラメータの内容を反映させる
	repeat SHOTMAX
		if (bbid.1.cnt = 1)&(bbid.2.cnt <= SHOTLIFECOUNT) {
			;	弾丸移動
			bbid.2.cnt++
			E3DGetBillboardInfo bbid.0.cnt, x,y,z, texname,transparent,w,h		;ビルボードの座標取得
			E3DSetBillboardPos bbid.0.cnt, bbid.3.cnt+x, bbid.4.cnt+y, bbid.5.cnt+z		;現在位置と速度から移動後の座標を計算
		} else {
			;	弾丸消滅
			if bbid.2.cnt > SHOTLIFECOUNT : E3DDestroyBillboard bbid.0.cnt	;弾丸削除
			bbid.1.cnt = 0	;弾丸フラグoff
			bbid.2.cnt = 0	;寿命カウンタリセット
			;速度ゼロ
			bbid.3.cnt = 0
			bbid.4.cnt = 0
			bbid.5.cnt = 0
		}
	loop

	;	ジャンプ
	if keybuf.VK_SPACE = 1{
		;地表にいるときと一番高くジャンプした点で加速できる。
		;地表にいるときだけジャンプできるようにしたかったが、うまく地表が検出できなかったので苦肉の策。
		if vely =0 : vely = 50
	}

	;	移動と方向転換
	if keybuf.VK_UP    = 1 : E3DPosForward hsid1, forwardstep	;矢印上
	if keybuf.VK_DOWN  = 1 : E3DPosForward hsid1, backstep	;矢印下
	if keybuf.VK_LEFT  = 1 : E3DRotateY hsid1, mdegstep	;矢印左
	if keybuf.VK_RIGHT = 1 : E3DRotateY hsid1, degstep	;矢印右

	;速度による移動計算
	E3DGetPos hsid1, posx, posy, posz
	E3DSetPos hsid1, posx+velx, posy+vely, posz+velz
;	title "速度ベクトル:"+velx+", "+vely+", "+velz

	return


;/////////////////
;
;	カメラ移動(キャラクター追跡型)
;
*MoveCamera

	【いつもどおりなので省略】

	return


;/////////////////
;
;	キャラクタの地面との当たり判定
;
*ChkConf

	【いつもどおりなので省略】

	return


;/////////////////
;
;	重力(gravity)
;
*GravChr
	;速度を計算
	;重力中心は-y方向の無限遠にあるとする。
	;重力加速度を1/100と表記している。
	vely = vely *100 + g100 /100

return

;/////////////////
;
;	弾丸作成
;
*make_shot
	;	弾丸作成
	;空いている配列を使用して弾丸を作成する
	repeat SHOTMAX
		if bbid.1.cnt = 0 {
			E3DCreateBillboard mediadir2, px*3, py*3, 1, bbid.0.cnt, 1, 1	;表示画像サイズの調整はここでやる。
			E3DSetBlendingMode -1, bbid.0.cnt,1
			E3DSetRenderState -1, bbid.0.cnt, D3DRS_LIGHTING, 0
			shotnum = cnt
			break
		} else {
			shotnum = -1
		}
	loop
	;弾丸の初期設定
	if shotnum ! -1 {
		;弾丸発射位置調整
		E3DGetPos hsid1, saveposx1, saveposy1, saveposz1
		E3DMultQVec axisqid, 0, 600, -700, x, y, z
		E3DSetBillboardPos bbid.0.shotnum, saveposx1+x, saveposy1+y, saveposz1+z
		;初速度設定
		E3DMultQVec axisqid, 0, 0, -500, vlctysid1x, vlctysid1y, vlctysid1z	;弾丸速度
		bbid.3.shotnum = vlctysid1x	;初速度設定
		bbid.4.shotnum = vlctysid1y
		bbid.5.shotnum = vlctysid1z
		bbid.1.shotnum = 1	;弾丸フラグon
		cnttrg = 0	;トリガーカウントリセット
	}
	return






- HOME -

GHP(仮)