空を飛ぶ

地上走行が出来るようになったので次は空を飛んでみます。
実際に人間が飛ぶのは大変ですが、CGなので飛ぶのも簡単です!w

やり方は簡単です。
地面なしでキャラクターをぽつんと表示させるだけ。


…で空中に浮いてる(宇宙空間?)ってことになるので空を飛ぶ完成です。
そんなこといってても仕方がないので、真面目に行きます。

地面との衝突

 今回は地面の上を飛ぶ場合を考えます。
地面の上を飛ぶ以上は、地面にぶつかればそれ以上地下へ進むことはないし、山にぶつかっても めり込むことはありえません。
今回はこれの再現をやってみます。

 地面との衝突判定を検知すればいいのでE3DChkConfGroundを使うことにします。
基本的な使い方はキャラクターを移動するやり方のところで解説したので大丈夫だと思います。
空を飛ぶということで「飛ぶモード」を使うわけですが、ここでは地面を「這うモード」を使ったやり方も やってみようと思います。

 どちらも仕組みは簡単。地面に衝突したら地表に座標を移動します。
ただ多少考え方が違うのでそれぞれ説明しておきましょう。

飛ぶモード

  • 基本的に空中なので何もしない。
  • 地面に衝突したら地表に移動する。

這うモード

  • 地面の上にいる場合は空中なので何もしない。
  • 地面の下にいる場合は地面にめり込んでいることになるので地表に移動する。

というふうにします。
わかりやすいように基本的な動作だけを考えることにするので、重力などは完全に無視しています。

2つの方法はどちらも動作が若干異なります。どちらがいいかは使う用途によって使い分ける必要があるでしょうね。

キャラクターの操縦

 さて、空を飛行する場合もうひとつ問題になるのが、キャラクターの操縦方法です。

 方法はいくつかありそうなので、ちょっと考えてみましょう。

飛行機タイプ
 空を飛ぶと聞いてまず思いつくのが飛行機のような操縦方法です。
見た目は簡単なようで、いがいにも複雑な部分があったりします。
機体を傾けて飛ぶだけで曲がっていったり…とか、原理知ってれば簡単でしょうが、 ここでやるには説明が必要なのでこの方法はやめておくことにします。

ヘリコプタータイプ
 プログラム的に作りやすいというか、イメージしやすい?というのはこれじゃないでしょうか。
要するに上下左右前後に真っ直ぐ自在に進める方法です。
ゲームにするとRPGなどの操作感に似ていますね。ボタンを押したら押した分だけその方向に 進むのは、作りも単純で操作しても動きがわかりやすそうです。

 というわけで、今回の操縦は次のような感じで作っていきます。

  • 前進後退はキャラクターの向きに進む。
  • 左右旋回はワールド座標軸のY軸を使用する。
  • 上下を向く場合は、キャラクターのローカル座標軸のX軸を使用する。

最初の2つはこれまでと同じです。
左右旋回もローカル座標軸を使おうかと思ったんですが、意外と操縦しにくかったので止めました…。

ローカル座標

ローカル座標  さて、問題は3番目のローカル座標軸ですね。とりあえず簡単に説明しておきます。(詳細は自分で調べてください。)

ワールド座標:
 これは地球に座標軸があるようなイメージです。人間が動いてもその軸の位置や向きは変わりません。 人間が右や左を向くたびに「北」の方角が変わってしまうことがないようなものです。

ローカル座標:
 ここでは人間を基準にした座標軸です。人間が右を向けば右を向くし、人間が鉄棒をすればローカル座標もいっしょにくっついて回転します。
人が後ろを向いたからといって右手が左手になることがないというようなイメージです。


さて、ここで言うローカル座標系は、操縦対象となるキャラクター(PC)が持つ座標系です。

 この座標系は、キャラクターが地面に対してどこを向うとも、キャラクターの向きと位置に 一致しています。 キャラクターと座標系の関係ですが、デフォルトでは-X方向を正面、+Y方向を上としています。(右図参照)
 この座標軸の向きとキャラクターとの関係は、キャラクターが地面に対してどっちを向いても かわりません。

 キャラクターに上下方向を向かせるには、これを利用します。
X軸方向に回転させれば、キャラクターが上下方向を向くことができますね。(ワールド座標だとうまくいかない理由は大丈夫ですね。)


※回転方向と軸との関係は別途各自で調べてください。
※ローカル座標とワールド座標(絶対座標)については、各自調べてください。
参考資料:HSP開発Wiki - 座標とピクセル

クォータニオン

 では、形状データの向き(キャラクターの向き)を取得してみます。
形状データの向きを3軸の回転ベクトルで扱うのは面倒なのですが、Easy3Dはクォータニオンを使った向き(姿勢情報)を扱うための便利な機能をもっています。

クォータニオン(クオタニオン、quaternion)
クォータニオとは計算・記述方法の一種で、ゲームではキャラクターを滑らかに回転させるために使用されるものです。
Easy3Dではクォータニオンを使用して、形状データの姿勢情報(回転情報)を書き換えることが出来ます。

 といっても、Easy3Dではクォータニオンを使用した計算部分は内部処理してくれるので、ユーザーはクォータニオンの計算方法について 気にする必要はありません。クォータニオンIDとは姿勢情報(回転情報)を格納できる便利なもの、ぐらいの理解で十分でしょう。

姿勢情報の書き換えは次のような流れになります。

  1. クォータニオンIDを作成します。(E3DCreateQ
  2. 形状データの現在の姿勢情報をクォータニオンIDに格納します。(E3DGetDirQ2
  3. 形状データをローカル座標軸に関して回転します。(E3DRotateQLocalX,Y,Z
  4. 姿勢情報をモデルに反映させます。(E3DSetDirQ2

クォータニオンIDは1回作ればOKなのでループの外で作成してください。

サンプル

タコが地上を移動して山を登ったり空を飛んだり出来るサンプルです。
操作は、
矢印キー(左右):左右へ向きを変更。
矢印キー(上下):上下へ向きを変更。
Z:キャラクターが向いている方向へ、前進
X:キャラクターが向いている方向へ、後退
となっています。
実行に必要なファイル:e3d3004.zip

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

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

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

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


スクリプトには「這うモード」と「飛ぶモード」両方を記述しています。
今は「這うモード」になっているので、「飛ぶモード」を使うときはコメント対象を変えて使ってください。

;
;	キャラクターを移動する(空を飛ぶ、クォータニオン)
;

#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		;キー入力
	fkabe = 1	;壁データとの当たり判定(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	;モーションは使用しない。

	;	壁の作成
	;道のマップをそのまま壁の生成に使います。
	E3DSetMovableArea pathbuf.1, mapsize, mapsize, mapdiv, mapdiv, mapheight+100, hsid2

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

	E3DCreateQ axisqid
	E3DCreateQ axisqid_cam


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


	E3DBeginScene	;-----シーンスタート
		E3DChkInView scid, hsid0	;モデルが、視野内にあるか判定
		E3DChkInView scid, hsid1
		E3DChkInView scid, hsid2	;壁

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

		;地面との当たり判定
		gosub *ChkConf_m1		;キャラクタの地面との当たり判定(這うモード)
;		gosub *ChkConf_m0		;キャラクタの地面との当たり判定(飛ぶモード)

		gosub *MoveCamera		;カメラ移動

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

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

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

goto *main


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


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

	E3DGetDirQ2 hsid1, axisqid

	if keybuf.VK_UP    = 1 {
		E3DRotateQLocalX axisqid, -1	;矢印上
	}
	if keybuf.VK_DOWN  = 1 {
		E3DRotateQLocalX axisqid, 1		;矢印下
	}
	if keybuf.VK_LEFT  = 1 {
		E3DRotateQY axisqid, mdegstep	;矢印左
;		E3DRotateQLocalZ axisqid, mdegstep	;矢印左	(ちょっぴり飛行機風)
	}
	if keybuf.VK_RIGHT = 1 {
		E3DRotateQY axisqid, degstep	;矢印右
;		E3DRotateQLocalZ axisqid, degstep	;矢印右	(ちょっぴり飛行機風)
	}
	E3DSetDirQ2 hsid1, axisqid

	if keybuf.'Z'    = 1 : E3DPosForward hsid1, forwardstep	;z(前進)
	if keybuf.'X'    = 1 : E3DPosForward hsid1, backstep	;x(後退)

	return


;/////////////////
;
;	カメラ移動(キャラクター追跡型)
;
*MoveCamera
	;キャラクターを後ろに2500移動して座標を取得。backpos
	E3DGetPos hsid1, saveposx1, saveposy1, saveposz1
	E3DGetDirQ2 hsid1, axisqid
	E3DGetDirQ2 hsid1, axisqid_cam

	;カメラ座標の決定
	E3DRotateQLocalX axisqid_cam, -30
	E3DSetDirQ2 hsid1, axisqid_cam
	E3DPosForward hsid1, -2500
	E3DGetPos hsid1, cmpposx, cmpposy, cmpposz
	E3DSetPos hsid1, saveposx1, saveposy1, saveposz1	;戻す
	E3DSetDirQ2 hsid1, axisqid

	;カメラ注視点座標の決定
	E3DRotateQLocalX axisqid_cam, -90
	E3DSetDirQ2 hsid1, axisqid_cam
	E3DPosForward hsid1, -800
	E3DGetPos hsid1, cmtposx, cmtposy, cmtposz
	E3DSetPos hsid1, saveposx1, saveposy1, saveposz1	;戻す
	E3DSetDirQ2 hsid1, axisqid
	;カメラをキャラクタ後方に設置
	E3DSetCameraPos cmpposx, cmpposy, cmpposz
	E3DSetCameraTarget cmtposx, cmtposy, cmtposz, cmtposx-saveposx1, cmtposy-saveposy1, cmtposz-saveposz1
	return


;/////////////////
;
;	キャラクタの地面との当たり判定(這うモード)
;
*ChkConf_m1
	; 壁(hsid2)とキャラクタ(hsid1)のあたり判定
	resultwall = 0
	E3DChkConfWall hsid1, hsid2, 1.0, resultwall, adjustx1, adjusty1, adjustz1, nx1, ny1, nz1
;	E3DChkConfWall hsid1, hsid2, -1.0, resultwall, adjustx1, adjusty1, adjustz1, nx1, ny1, nz1
	if resultwall!0 {
		E3DDrawTextByFontID scid, fontid, 10,10,"壁に激突しました!", 255,255,255,255	;壁に激突したときのメッセージ。
		E3DSetPos hsid1, adjustx1, adjusty1, adjustz1
	}

	;	地面データとキャラクター
	mapminy = -100	;地面データのY座標の最小値。実際の最小値より、少し小さな値を入れる。
	E3DChkConfGround hsid1, hsid0, 1, mapheight, mapminy, result, adjustx, adjusty, adjustz, nx, ny, nz
	E3DGetPos hsid1, posx, posy, posz
	if posy>adjusty {
		E3DSetPos hsid1, posx, posy, posz	;空中
	} else {
		E3DSetPos hsid1, adjustx, adjusty, adjustz	;地面の上に移動
	}
	return

;/////////////////
;
;	キャラクタの地面との当たり判定(飛ぶモード)
;
*ChkConf_m0
	; 壁(hsid2)とキャラクタ(hsid1)のあたり判定
	resultwall = 0
	E3DChkConfWall hsid1, hsid2, 1.0, resultwall, adjustx1, adjusty1, adjustz1, nx1, ny1, nz1
;	E3DChkConfWall hsid1, hsid2, -1.0, resultwall, adjustx1, adjusty1, adjustz1, nx1, ny1, nz1
	if resultwall!0 {
		E3DDrawTextByFontID scid, fontid, 10,10,"壁に激突しました!", 255,255,255,255	;壁に激突したときのメッセージ。
		E3DSetPos hsid1, adjustx1, adjusty1, adjustz1
	}

	;	地面データとキャラクター
	mapminy = -100	;地面データのY座標の最小値。実際の最小値より、少し小さな値を入れる。
	E3DChkConfGround hsid1, hsid0, 0, mapheight, mapminy, result, adjustx, adjusty, adjustz, nx, ny, nz
	if result = 1 {
		a = 10.0
		px = adjustx + nx * a
		py = adjusty + ny * a
		pz = adjustz + nz * a
		E3DSetPos hsid1, px, py, pz
	}
	return