地表を歩かせよう

 起伏がある複雑な地形を表示できたのはいいのですが、今のままでは地形に合わせてキャラクターが地面を登ったり下ったりできません。 水平移動しかできないので、山があれば斜面に突っ込み、川を無視して空中歩行するだけです。

 そこでhgimg4では、物理挙動を再現する機能が実装されています。 この機能を有効にすれば地面に沿っての移動が難なく実装できます。 今回はそんな物理エンジンに頼り、屈することなく、アヒルには凹凸のある地表を移動してもらうことにしました。 ほら、その…物理エンジン使ったら摩擦とか色々をちゃんと考えないと横滑りしたり、凹凸で跳ねたりして思うように動かなくって困るとかあるだろうし…。 何だか負けた気もするし。

サンプル

 地形データも必要なのでサンプルファイルを用意しました。ダウンロードしてご利用ください。
サンプル(1.2MB)
地形データのファイルサイズ軽量化のため、ポリゴン数を減らしています。削減要領はこちら→「3D地図データを軽量化する

サンプル実行画面


#include "hgimg4.as"

title "HGIMG4 起伏がある地形"

	gpreset

	setcls CLSMODE_SOLID, $404040

	setcolor GPOBJ_LIGHT, 1,1,1		; ライトカラーを設定
	setdir GPOBJ_LIGHT, 0.5,0.5,0.5		; アンビエントカラーを設定

	;	アヒル読み込み
	gpload id_model,"res/duck"		; モデル読み込み
	if id_model < 0 {
		dialog "3Dモデルの読み込みに失敗しました。"
		end
	}
	setpos id_model, 0,0,5


	;	地面読み込み
	gpload id_floor,"res/fuji/dem"	; モデル読み込み
	if id_floor < 0 {
		dialog "地面モデルの読み込みに失敗しました。"
		end
	}
	z = 1.0 / 500.0
	setscale id_floor, z,z*2,z	; 起伏をわかりやすくするために高さを2倍
	setpos id_floor, 15,0,-10
	; gppraytest での接触判定のため、モデルを反映した物理設定を行う
	gppbind id_floor, 0, ,GPPBIND_MESH


	;	作業用のヌルノード
	; gppraytestを使ってアヒル位置の地面高さを調べるために使用する。
	; gppraytestは-Zベクトルを接触判定に使用する。
	; 地面との接触を見るため、ベクトルはY軸方向を向いている必要がある。
	; このため、-zベクトルが-Y軸方向を向くようにヌルノードを回転している。
	gpnull id_null
	setang id_null, -M_PI/2.0, 0, 0


	;	カメラ
	setpos GPOBJ_CAMERA, 0,20,30		; カメラ位置を設定
	gplookat GPOBJ_CAMERA, 0,0.3,0		; カメラから指定した座標を見る

	;	初期値
	; ジャンプ速度
	fvset fv_jump, 0,0,0
	; 地面の高さ(標高)
	height = 0.0
	
repeat
	stick key,15
	if key&128 : end

	redraw 0			; 描画開始

	; ==========================================================================
	
	;------------------------------
	;	キー入力による移動とジャンプ
	;------------------------------
	fvset fv_vel, 0, 0, 0	; 進行方向ベクトル
	if key&1 : addang id_model, 0,  0.02	; ←左回転
	if key&4 : addang id_model, 0, -0.02	; →右回転
	if key&8 : fvset fv_vel, -0.2, 0, 0		; ↓進行方向ベクトル(後ろに進む)
	if key&2 : fvset fv_vel,  0.2, 0, 0		; ↑進行方向ベクトル(前に進む)
	if key&16 {
		if flgJump = 0 {			; ジャンプ中のジャンプを防止
			fvset fv_jump, 0, 1, 0	; ジャンプ初速
			flgJump = 1				; 0=着地中、1=ジャンプ中(空中)
		}
	}

	;------------------------------
	;	アヒルの顔が向いている方向に進む
	; アヒルの向きを変えて、アヒルが向いている方向にアヒルを移動します。
	; アヒルは+X方向が正面方向です。
	;------------------------------
	
	; アヒル(ノード)の向きを取得します。
	; 初期値状態に比べてどのくらいの角度を向いているかを取得。
	getang id_model, xr, yr, zr
	fvset fvr, xr, yr, zr
	;mes "向き   : " + rad2deg(fvr(0)) + ", " + rad2deg(fvr(1)) + ", " + rad2deg(fvr(2))

	; 進行方向ベクトルを現在のアヒル(ノード)の向きに回転します。
	fvmul fvr, -1,-1,-1		;fvdir のバグ対策
	fvdir fvr, fv_vel(0), fv_vel(1), fv_vel(2)
	;mes "進行方向 : " + fvr(0) + ", " + fvr(1) + ", " + fvr(2)

	; 現在の向きに合わせた進行方向ベクトルを現在値に加算
	addpos id_model, fvr(0), fvr(1), fvr(2)


	;------------------------------
	;	現在位置の標高を取得
	; アヒルの上下方向に線分を作成し、線分と地面との交点座標を調べます。
	; height : 標高
	;------------------------------
	
	; アヒルの位置をヌルノードにセット
	getpos id_model, px, py, pz
	setpos id_null,  px, py+50, pz

	; アヒルの少し(50)上から100下までの線分と地面との交点を計算
	; 地面との接触がある場合は、アヒルの高さを地面に合わせる。
	gppraytest objid, id_null
	if objid > 0 {
		getwork id_null, wx, height, wz
	}

	;------------------------------
	;	ジャンプ
	;------------------------------
	if flgJump {
		; ジャンプ中(空中)
		; 重力加速度の分だけジャンプ速度を減速
		addpos id_model, fv_jump(0), fv_jump(1), fv_jump(2)
		fvsub fv_jump, 0, 9.8/60, 0

		; ジャンプ終了条件
		getpos id_model, px, py, pz
		if py < height {
			setpos id_model, px, height, pz
			fvset fv_jump, 0,0,0
			flgJump = 0
		}
	} else {
		; ジャンプしていない(着地中)
		; 接地したまま地面高さに沿って上り下りしている状態。
		; そのままでは地面にめり込んだり、空中散歩してしまうため
		; アヒルの位置を地面高さに移動する。
		getpos id_model, px, py, pz
		setpos id_model, px, height, pz
	}
	; ==========================================================================

	; シーンの描画
	gpdraw

	color 255,255,255
	pos 8,8:mes "HGIMG4 方向キー:移動 スペース:ジャンプ"
	; 編集・加工等を行った場合は、出典とは別に記載が必要です。
	mes "※ 起伏を強調するため、地形の高さを2倍にしています。"
	; アヒルの位置
	getpos id_model, px, py, pz
	mes strf("位置:%3.2f, %3.2f, %3.2f", px, py, pz )

	; 出典
	; 出典の記載 https://www.gsi.go.jp/LAW/2930-meizi.html
	; 原則としては、このように表示画面に明示するようです。
	; 技術的に困難だったりやむを得ない場合は、readmeファイル等への記載でも大丈夫とのことです。
	pos 10, ginfo_winy-30
	mes "出典:地理院地図(https://maps.gsi.go.jp/)"
	

	redraw 1			; 描画終了
	await 1000/60		; 待ち時間

loop

大まかな手順

 アヒルの現在位置にある地面の凹凸の高さ(標高)を調べるには、gppraytestを使用します。

 手順としては、アヒルの位置から地面に垂直な線分を作成し、線分と地面モデルとの交点座標を取得します。 交点座標のY座標が標高です。後はアヒルのY座標を標高と同じ値にすれば、アヒルは地面にめり込むことなく、常に地面の表面にいることができます。

gppraytest

 基本方針として、gppraytest命令を使用します。

 gppraytest命令は、レイ(線分、有限な長さの直線)とオブジェクトノードとの交点の有無と、交点座標、交点位置の法線ベクトルを調べることができます。 しかし、接触を調べる対象となるノードオブジェクトは、gppbind命令で物理特性を設定したオブジェクトノードに限定されています。 対象は地面のオブジェクトだけなので、次のように設定しています。


	gppbind id_floor, 0, ,GPPBIND_MESH

 地面が落下すると困るので、重さを0にして静的剛体(衝突はするが、動かない)に設定しています。 またモデルの形状そのものを衝突検出に使いたいので、GPPBIND_MESH オプションを設定。

 これでgppraytest命令が使えるようになりました。

地面に垂直な線分

 「地面に垂直な線分」の長さは、gppraytest命令で指定できます。 しかし方向については、ノードオブジェクトで渡す必要があります。 アヒルのノードを使う事もできますが、実装が複雑になるのでなるべく触りたくありません。 また、gppraytest命令を使うと判定結果がワーク値に出力されてしまうということもあり、今回はヌルノードを作成して対応しました。ヌルノードを使えば、アヒルのワーク値も汚されません。

 gppraytest命令はノードが向いている方向にレイ(線分)を伸ばして衝突判定を行ってくれます。 この命令での「ノードが向いている方向」は -Z 方向を指しています。 そこで作成したヌルノードの -Z方向を地面に垂直な方向に向けるため、X軸方向に90度(π/2ラジアン)回転しています。


	gpnull id_null
	setang id_null, -M_PI/2.0, 0, 0

 作成した塗るノードは、常にアヒルと同じ位置に配置します。 しかしアヒルの上下について地面の有無を調べる範囲としたいので、正確にはアヒルの少し上に配置しています。 線分長さは、ヌルノードを起点に地面方向に伸ばします。長さはgppraytest命令のデフォルト値 100。


	getpos id_model, px, py, pz
	setpos id_null,  px, py+50, pz

 gppraytest命令で説明されている「オブジェクトの向いている方向」は、アヒルが向いている方向とは異なるので注意が必要ですね。

線分と地面モデルとの交点座標

 gppraytest命令に準備したヌルノードを与えてやれば、ヌルノードから伸びる線分に接触するノードオブジェクトのIDを調べることができます。 接触するノードオブジェクトが存在する(>0)場合(地面が線分と接触する場合)は、ワーク値を使って線分とノードオブジェクトとの交点を調べます。


	gppraytest objid, id_null
	if objid > 0 {
		getwork id_null, wx, height, wz
	}

 ヌルノードのワーク値に交点座標が入っています。これで地面の高さ、標高(height)が取り出せます。 なお、ノードワーク値2には交点位置の面の法線ベクトルが入っていますが、今回は使用しません。

 ちなみに、線分がアヒルに衝突しているという結果は検出されません。 アヒルは、gppbind命令による物理特性が設定されていないためです。 もし物理特性が設定された岩や植物が地形上に転がっていた場合、衝突を検出するのでアヒルは岩や植物の上を歩くことになります。

概念図

アヒルを地表に置く

 アヒルの位置の標高がわかったので、アヒルを地面の高さに置きます。


	getpos id_model, px, py, pz
	setpos id_model, px, height, pz

 ジャンプ中は少し工夫が必要ですが、2Dゲームを作るときと変わらないので難しくはないと思います。

他にも応用できそう

 物理挙動の機能を使えば簡単だったと思うのですが、今回はあえて避けてみました。 お陰でgppraytestの使い方がよくわかりました。

 レイとオブジェクトの衝突機能は、使いこなすといろいろできそうですね。 レーザー攻撃の衝突判定や、障害物を考慮した索敵機能など。あたった部分の法線ベクトルもわかるので、レーザーの反射軌道も計算できます。 アイデア次第で広く使えそう。

 ヌルノードも最初は何に使うのかわからなかったのですが、他のノードオブジェクトを汚さないのでこれは便利です。良い機能ですね。 今後も使う機会が増えるかもしれませんね。

出典

このページで掲載している地図画像データについて。

出典:地理院地図(https://maps.gsi.go.jp