NPC登場(衝突判定)

NPC登場

左から、NPCとPC

ようやくNPC登場です。
まずは言葉の説明から。

NPCとは

PCプレイヤーキャラクター
プレイヤー(PL)が操作しているキャラクターのこと。
人物に限らず、車やロボットなども含む。
NPCノンプレイヤーキャラクター
プレイヤーが操作していないキャラクターのこと。
例:敵や自分で操作できない仲間、プレイヤーがいない村人など。

要するにPCとは自分のことで、NPCは敵やあるいは味方など自分以外のことです。
いままでPCは登場していましたが、今回はNPCにも登場してもらいます。


ところで、ネットゲームで「自分以外のプレイヤーが操作しているキャラクター」は、…やっぱりPCって呼んでいいんですよね?

NPCを登場させるには

 基本的にはPCとまったく同じ処理をすればいいわけです。
注意すべきなのは、IDは各キャラクターで発行されるので、混ぜてしまわないように注意することです。
ちゃんと分けて処理すれば問題ありません。

PCとNPCを同時に動かす


 キャラクターを2つ以上表示するのは、形状データIDをうまく使えばさほど難しいことではありません。
これについてはサンプルをゆっくりながめてみてください。
配列変数などで工夫して、各自うまいこと処理してください。

衝突判定

 壁やNPCにぶつかったのか、何にもぶつかっていないのかどうか?
これを判定する作業のことです。
これまでは地面との衝突判定はやってきていたので、今回はそれ以外です。

 問題は、ぶつかったと分かった後どうするかです。
何もしなければ、壁をすり抜け、敵キャラをすり抜け、降り注ぐ弾丸の雨をすり抜け、…こんな幽霊みたいなキャラクターじゃ ゲームらしくないですね。(だからといってこれでゲームが出来ないかといえばそうではありませんが…でもこれは別の話。ここではやらない。)
やっぱり壁や登場人物にぶつかったらそれ以上は前には進めないのが、それらしい動きです。

 今回はこの、それらしい動きをやってみます。

キャラクタどうしの当たり判定

 まず今回はPCがNPCにぶつかった場合だけを考えます。
衝突後の処理を必要とするのは1つのキャラクターだけだと複数の場合より簡単にいけそうです。
※ベクトルの計算を行いますので、ベクトルが分からない方はベクトルについて調べてきてください。

 まずは衝突の判定を行います。衝突判定だけなら結構簡単に出来ます。
衝突判定をする命令もいろいろありますが、今回はE3DChkConflict命令を使用します。

衝突判定関係の命令
E3DChkConfBillboard、E3DChkConfBillboard2、E3DChkConfBySphere、E3DChkConfBySphere2、E3DChkConfGround、E3DChkConflict、E3DChkConflict2、E3DChkConfLineAndFace、など

 PCとNPCが衝突したら、NPCからPCに向かうベクトルを取得します。

    (NPCからPCに向かうベクトル)=(PC座標ベクトル)-(NPC座標ベクトル)

このベクトルの方向にPCを移動すれば、PCはNPCから離れることが出来ますね。

 次にこのベクトルを、PCを移動する距離と同じ長さにします。
これにはE3DVec3Normalize命令を使用します。この命令は指定したベクトルを正規化した値を出力します。
ベクトルの正規化とは、ある長さを持つベクトルを、向きを維持したまま長さを1に変換することです。
長さを1にしたところに移動したい距離をかけてやれば、実際に移動させたいベクトルがでてきます。
E3DVec3Normalize命令の結果に掛けた値が、移動量ということになります。

 現在のPC座標に、計算したベクトルを足します。
NPCと衝突したPCは、NPCから距離をとるように(押しのけられるように)動きます。

 衝突した後の移動量ですが、これは大きすぎればあまりに不自然だし、小さすぎても効果はあまりありません。マイナスにすると… これはこれで面白いかもしれません。(^ ^;
 移動量が小さいと例えば…勢いよくぶつかれば減速するもののすり抜けることが出来てしまいます。
また、すり抜けないまでも一時的にぶつかったキャラクター同士が重なってしまうことがあります。
これがこの手法の欠点であり、数値の調整が必要な部分です。

サンプル

タコが地上を移動して山を登ったり出来るサンプルです。
一番近い山を越えるとNPCが見えると思います。

衝突したときに押し戻されるのはPCだけで、NPCはぶつかっても強引に動きつづけます。
操作は、キーボードの上下左右キーで操作します。

タコモデルのファイルtako.sigとタコ用テクスチャファイルtako01.bmpを用意しておいてください。
実行に必要なファイル:e3d3004.zip

解凍に成功すると、Mediaフォルダとe3d3004.hspが作成されます。
作成されたMediaフォルダにタコのファイルをコピーするので次の作業を行ってください。

HSP3のインストールフォルダからsample→easy3d→Mediaとフォルダを開いてください。
tako.sigとtako01.PNGをコピーします。
e3d3004.zipを解凍してできたMediaフォルダにコピーしたファイルを貼り付けます。
これで必要なファイルはそろいました。

次に、下のスクリプトをコピーして、HSPのスクリプトファイルを作成してください。
作成したファイルは、e3d3004.hspがある場所と同じ場所におきます。

これでサンプルを実行することが出来ます。

サブルーチン*ChkConfAxis内のコメントを外すとNPCも押し出すことができるようになります。

;
;	NPC登場(衝突判定)
;

#include "e3dhsp3.as"

	;/////////////////
	;
	;	初期化
	;
	wid = 0		;ウインドウID
	objid = -1	;オブジェクトID
	fullscreenflag = 0		;フルスクリーンフラグ
	bits = 16	;色数ビット数(fullscreenflag = 1 のときのみ有効)
	multisamplenum = 0		;アンチエイリアスのマルチサンプル数(0,2~16)
	;scid	スワップチェインID
	E3DInit wid, objid, fullscreenflag, bits, multisamplenum, scid
	dim keybuf, 256		;キー入力
	fcolibound = 1	;PCとNPCが衝突したとき重ならないようにする(0:off / 1:on)

	;フォントの設定
	E3DCreateFont 24,, 400, , , , msgothic, fontid


	;/////////////////
	;
	; ライトの設定
	;
	E3DCreateLight lid1 ;光源を作成
	lightdirx1 = 1 ;平行光の向き
	lightdiry1 = -1
	lightdirz1 = 0
	lightr1 = 255	;平行光の色
	lightg1 = 255
	lightb1 = 255
	E3DSetDirectionalLight lid1, lightdirx1, lightdiry1, lightdirz1, lightr1, lightg1, lightb1 ;光源を平行光源に設定

	;/////////////////
	;
	;	カメラの初期化
	;
	camposx = 0 : camposy = 1000 : camposz = -4200
;	camposx = 0 : camposy = 0 : camposz = 4200	;デフォルト値
	E3DSetCameraPos camposx, camposy, camposz	;カメラ座標を設定
	targetx = 0		;注視点座標
	targety = 0
	targetz = 0
	upvecx = 0		;カメラの上方向のベクトル
	upvecy = 1
	upvecz = 0
	E3DSetCameraTarget targetx, targety, targetz, upvecx, upvecy, upvecz	;カメラの注視点を設定
	;プロジェクションの設定
	proj_near = 100.0 : proj_far = 50000.0 : proj_fov = 45.0
	E3DSetProjection proj_near, proj_far, proj_fov


	;/////////////////
	;
	;	地面の作成
	;
	sdim pathbuf, 2048, 4
	pathbuf.0 = dir_cur + "\\Media\\yama.bmp"	;地面の座標情報
	pathbuf.1 = dir_cur + "\\Media\\michi.bmp"	;地面の道の情報
	pathbuf.2 = dir_cur + "\\Media\\kawa.bmp"	;地面の川の情報
	pathbuf.3 = dir_cur + "\\Media\\bazou.bmp"	;地面、道、川の模様を決める、BMPファイル

	;地面作成用の値
	mapsize = 60000.0		;X,Z座標の最大値
	mapdiv = 60			;座標の分割数
	mapheight = 3000.0	;高さの最大値

	E3DLoadGroundBMP pathbuf.0, pathbuf.1, pathbuf.2, pathbuf.3, mapsize, mapsize, mapdiv, mapdiv, mapheight, hsid0
	posx0 = 0.0 : posy0 = 0.0 : posz0 = 0.0
	degx0 = 0.0 : degy0 = 0.0 : degz0 = 0.0
	E3DSetPos hsid0, posx0, posy0, posz0	;地面表示位置を設定
	E3DSetDir hsid0, degx0, degy0, degz0	;地面の向きを設定
	frameno0 = 0	;モーションは使用しない。


	;/////////////////
	;
	;	形状データのロード
	;
	sdim mediadir, 2048
	mediadir = dir_cur + "\\Media\\tako.sig"
	;PC
	E3DSigLoad mediadir, hsid1
	E3DSetPosOnGround hsid1, hsid0, mapheight,, 13000, 16000
	E3DRotateY hsid1, 180
	frameno1 = 0	;モーションは使用しない。
	E3DSetBeforePos hsid1

	;NPC
	E3DSigLoad mediadir, hsid3
	E3DSetPosOnGround hsid3, hsid0, mapheight,, 15000, 25000
	E3DSetBeforePos hsid3

	E3DCreateQ axisqid



;/////////////////
;
;	メインループ
;
*main
	E3DGetKeyboardState keybuf	;キー状態取得


	E3DBeginScene	;-----シーンスタート
		E3DChkInView scid, hsid0	;モデルが、視野内にあるか判定
		E3DChkInView scid, hsid1
		E3DChkInView scid, hsid3	;キャラクター(NPC)

		if keybuf.VK_ESCAPE = 1 : goto *bye ; [ESC]で終了
		gosub *MoveChara		;キャラクター移動
		gosub *MoveNPChara
		gosub *MoveCamera		;カメラ移動


		;不透明部分の描画をする
		E3DRender scid1, hsid0, 0, frameno0, 0	;バックバッファにレンダリングする。(地面描画)
		E3DRender scid1, hsid1, 0, frameno1, 0	;バックバッファにレンダリングする。(タコ描画)
		E3DRender scid1, hsid3, 0, frameno1, 0	;バックバッファにレンダリングする。(タコ描画NPC)

		gosub *ChkConfAxis	;キャラクター同士の当たり判定
		gosub *ChkConf	;地面との当たり判定

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

	E3DWaitByFPS 60, chkfps1 : await 0
;	title ""+chkfps1 + " FPS"

goto *main


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


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

	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	;矢印右
	return

;	NPC
*MoveNPChara
	forwardstep = 50	;移動速度(前進)
	degstep = 5.0/3
	mdegstep = -degstep

	E3DRotateY hsid3, mdegstep
	E3DPosForward hsid3, forwardstep
	return


;/////////////////
;
;	カメラ移動(キャラクター追跡型)
;
*MoveCamera
	;キャラクターを後ろに2500移動して座標を取得。backpos
	E3DGetPos hsid1, saveposx1, saveposy1, saveposz1
	E3DPosForward hsid1, -2500
	E3DGetPos hsid1, backposx, backposy, backposz
	E3DSetPos hsid1, saveposx1, saveposy1, saveposz1
	;カメラをキャラクタ後方に設置
	E3DSetCameraPos backposx, backposy + 1500, backposz
	E3DSetCameraTarget saveposx1, saveposy1 + 800, saveposz1, 0, 1, 0
	return


;/////////////////
;
;	キャラクタの地面との当たり判定
;
*ChkConf
	;	地面データとキャラクター
	mapminy = -100	;地面データのY座標の最小値。実際の最小値より、少し小さな値を入れる。
	repeat 2	;キャラ2つ
		if cnt = 0 : hsid_c = hsid1
		if cnt = 1 : hsid_c = hsid3
		E3DChkConfGround hsid_c, hsid0, 1, mapheight, mapminy, result, adjustx, adjusty, adjustz, nx, ny, nz
		if ( result != 0 ) : E3DSetPos hsid_c, adjustx, adjusty, adjustz	;地面の上に移動
	loop

	return


;/////////////////
;
;	キャラクタどうしの当たり判定
;
*ChkConfAxis
	E3DChkConflict hsid1, hsid3, confflag, inviewflag
	if confflag!0 {
		if fcolibound ! 0 {
			;衝突したときにキャラクターが重ならないように移動する
			;PCとNPCをそれぞれ衝突した方向と反対方向に40移動させる
			r = 40.0
;			r = 50.0
			E3DGetPos hsid1, posx1, posy1, posz1
			E3DGetPos hsid3, posx3, posy3, posz3
			vposx = posx1 - posx3	;NCPからPCに向かうベクトル
			vposy = posy1 - posy3
			;ベクトルを正規化(長さ1に)する。
			E3DVec3Normalize vposx, vposy, vposz, posx, posy, posz
			posx1 += posx*r	;移動ベクトルに現在位置を加算
			posy1 += posy*r
			posz1 += posz*r
			E3DSetPos hsid1, posx1, posy1, posz1
;			posx3 -= posx*r	;移動ベクトルに現在位置を加算
;			posy3 -= posy*r
;			posz3 -= posz*r
;			E3DSetPos hsid3, posx3, posy3, posz3
		}
		E3DDrawTextByFontID scid, fontid, 10,10,"NPCと激突しました!", 255,255,255,255	;激突したときのメッセージ。
	}
	return