はじめに

 hgimg4では、gppbind命令を実行するだけで簡単に物理挙動をさせることができます。 ある程度の初期値が設定されるので、あとは結果に合わせて適当にパラメータを微調整すればいいと思います。 物理的な正確さよりも、一番気持ちいいところを探して調整してください。ゲーム的にはそれが正解で全てです。これで物理関係の説明は終わりです。

 以下は蛇足ですが、hgimg4が用意している命令や初期値の物理的な意味について調べてみました。お暇があればご一読ください。

重力加速度と単位

 hgimg4が初期値で想定している単位系を確認したいので、重力加速度を確認してみます。地球基準で設定されているはずなので、数値がわかれば単位がわかります。 9.8なら[m/s^2]、1なら[G]、それ以外ならインチやフィートを使っているかもしれません。 国際単位系(SI単位)の[m/s^2]だとは思いますが、確認は大事です。

 同じ重さの物体を、重力の方向を逆にしてぶつけてみます。 同じ重力加速度を設定できていれば、2つは釣り合って空中で静止するはずです。


#include "hgimg4.as"

title "HGIMG4 test"

	;	物理エンジンテスト

	; 重力加速度(SI単位):9.80665 m/s^2
	grav = 9.8

	; 質量 [kg]
	mass = 1.0

	;	シーン作成
	gpreset
	setcls CLSMODE_SOLID, $808080		; 画面クリア設定

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

	;	床を作成
	gpfloor id_floor, 30,30, $404040		; 床ノードを追加
	gppbind id_floor, 0						; 床の物理設定を行なう


	;	箱を作成
	gptexmat id_texmat, "res/qbox.png"
	gpbox   id_box, 1, -1, id_texmat		; 箱ノードを追加
	setpos  id_box, 0, 1, 0
	gppbind id_box, mass
	gppset  id_box, GPPSET_DAMPING, 0,0,0
	; 重力を上方向に設定
	gppset  id_box, GPPSET_GRAVITY, 0, grav, 0


	;	重力のテスト
	; 箱の上に同じ重さの箱を置く。
	; 重力加速度がデフォルト設定と同じ値であれば釣り合うはず。
	gpclone id_boxc, id_box
	setpos  id_boxc, 0, 3, 0
	gppbind id_boxc, mass
	gppset  id_boxc, GPPSET_DAMPING, 0,0,0

*main
	stick key,15
	if key&128 : end

	;	描画
	redraw 0			; 描画開始
		gpdraw				; シーンの描画
	
		color 255,255,255
		pos 8,8:mes "HGIMG4 sample"
	redraw 1			; 描画終了
	await 1000/60		; 待ち時間
	goto *main

 重力加速度に「9.8」を設定すると完全にバランスをとることができました。 これよりも大きくても小さくても少し動いてしまいます。hgimg4の重力加速度初期値は「9.8」になっているようです。

 一方、SI単位では重力加速度を「9.80665 m/s^2」と定義しています。 小数点以下2ケタ目以降は切り捨てられてますが、hgimg4での重力加速度を地球の重力加速度に合わせていると仮定すれば、単位は[m/s^2]と考えていいでしょう。

 ということは、長さや質量もSI単位になっています。長さは「m(メートル)」、質量は「kg(キログラム)」、時間は「s(秒)」です。 普段の生活で使用するものと同じなので、このままSI単位として考えて作っていったほうがイメージしやすく便利そうですね。

 さて、この説明ではあたかもhgimg4内部で単位系が定義されているかのように聞こえるかもしれませんが、hgimg4内部で単位は定義されていません。 単位は自分で定義すればCGS単位系でもFPS単位系でも尺貫法でも好きなものを使って構いません。(ただし複数の単位を混ぜて使用してはいけません。必ず統一して使用します。例:mとcmを混ぜて使うのはNG。) 物理的な関係式は単位が変わっても変わらないので、単位さえ統一されていれば何も問題はありません。 ここで確認したのは、「重量加速度の初期値に地球のSI単位に相当する値が設定されている」ことだけです。

gppapply 力

 gppapplyの使い方を試してみます。HDLでは次のような物理的な力を入力できると説明されています。

GPPAPPLY_FORCE 移動しようとする力を加算する
GPPAPPLY_IMPULSE 瞬間的な衝撃を与える

 表現が曖昧で、実際のところ違いが何なのかがよくわかりません。SI単位での単位が何に相当するのかも気になります。

 まずは試してみました。実行すると空中に静止する2つの箱が出現します。左がGPPAPPLY_FORCE、右がGPPAPPLY_IMPULSEを使用しています。


#include "hgimg4.as"

title "HGIMG4 test"

	;	物理エンジンテスト

	; 重力加速度(SI単位):9.80665 m/s^2
	grav = 9.8

	; 質量 [kg]
	mass = 1.0

	; 速度
	vel = 10.0

	;	シーン作成
	gpreset
	setcls CLSMODE_SOLID, $808080		; 画面クリア設定

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

	;	床を作成
	gpfloor id_floor, 30,30, $404040		; 床ノードを追加
	gppbind id_floor, 0						; 床の物理設定を行なう

	
	;	箱を作成
	gptexmat id_texmat, "res/qbox.png"
	gpbox   id_box0, 1, -1, id_texmat		; 箱ノードを追加
	setpos  id_box0, -1, 1, 0
	gppbind id_box0, mass
	gppset  id_box0, GPPSET_DAMPING, 0,0,0

	;	箱(クローン)を作成
	gpclone id_box1, id_box0		; 箱をクローン
	setpos  id_box1, 1, 1, 0
	gppbind id_box1, mass
	gppset  id_box1, GPPSET_DAMPING, 0,0,0
	gppset  id_box1, GPPSET_GRAVITY, 0,0,0	; 重力を解除
	; 速度を設定
	gppset  id_box1, GPPSET_LINEAR_VELOCITY, 0,vel,0

	;	GPPAPPLY_IMPULSE
	; 力積I = F*dt
	;       = m*v1 - m*v0
	; v1:変化後の速度
	; v0:元の速度
	impulse = mass * 0.0  -  mass * vel
	gppapply id_box1, GPPAPPLY_IMPULSE, 0, impulse, 0


*main
	stick key,15
	if key&128 : end

	;	GPPAPPLY_FORCE
	; 力F = m * a
	F = mass * grav
	gppapply id_box0, GPPAPPLY_FORCE, 0, F, 0

	;	描画
	redraw 0			; 描画開始
		gpdraw				; シーンの描画
	
		color 255,255,255
		pos 8,8:mes "HGIMG4 sample"

		; GPPAPPLY_FORCE
		gpcnvaxis x,y,z, -1, 0, 0.5, 0
		if (z>=0) & (z<=1) : pos x-20,y : mes "FORCE"

		; GPPAPPLY_IMPULSE
		gpcnvaxis x,y,z,  1, 0, 0.5, 0
		if (z>=0) & (z<=1) : pos x-27,y : mes "IMPULSE"


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

	goto *main

 左の箱には、重力を設定しています。落下しようとする箱にGPPAPPLY_FORCEを使用して、上方向に力を常に入力しています。 力の大きさと重力の大きさが釣り合っているため、箱は空中に静止しています。


	;	GPPAPPLY_FORCE
	; 力F = m * a
	F = mass * grav
	gppapply id_box0, GPPAPPLY_FORCE, 0, F, 0

 GPPAPPLY_FORCEは、「力」[N]を指定するようです。力の大きさは、ニュートンの運動法則 F=ma から算出しています。 重力加速度や質量を変更すると、バランスが崩れて箱が動き出すことが確認できます。


 次の説明に行く前に、力について内部の処理方法について説明しておきます。 まず、与えられる力(F = ma)[N]が決まると、質量[kg]は一定なので加速度[m/s^2]が決まります。

加速度 = 力 / 質量

 加速度[m/s^2]を時間で積分すると速度[m/s]が決まります。

速度 += 加速度 * 1フレームの時間

 速度[m/s]を時間で積分すると位置[m]が決まります。

位置 += 速度 * 1フレームの時間

 力を与えると、速度や位置には1フレームの時間が関係していることがわかります。 GPPAPPLY_FORCEで力を入力すると1フレーム経過するまでの間は、同じ力を入力し続けているとして計算されます。

 1フレームの時間は、内部で勝手に計算してくれるので我々使う側は気にする必要はありません。 試しにフレームレートを変えてみます。サンプルを次のように書き換えます。


	;await 1000/60		; 待ち時間
	await 1000/30		; 待ち時間

 およそ30fpsになりました。結果は変わりませんね。

 説明の参考にした資料へのリンクを置いておきます。この動画の説明のほうが分かりやすいと思います。

外力 【Unity道場 2018】物理シミュレーション完全マスター - Youtube
https://www.youtube.com/watch?v=Ju4ILgpuVHE&t=1115s

gppapply 力積

 「瞬間的な衝撃を与える」と説明されているGPPAPPLY_IMPULSEについての実験です。 爆発やパンチなどでふっとばされる際に使用されることを想定しているようですが、少し具体的に見ていきます。

 サンプルの右の箱には、重力を設定していません。無重力状態です。代わりに速度velを設定しています。


	; 速度を設定
	gppset  id_box1, GPPSET_LINEAR_VELOCITY, 0,vel,0

 この速度velをちょうど打ち消す力を、箱に対してGPPAPPLY_IMPULSEを使って入力しています。


	;	GPPAPPLY_IMPULSE
	; 力積I = F*dt
	;       = m*v1 - m*v0
	; v1:変化後の速度
	; v0:元の速度
	impulse = mass * 0.0  -  mass * vel
	gppapply id_box1, GPPAPPLY_IMPULSE, 0, impulse, 0

 「瞬間的な衝撃を与える」と説明されていますが、いろいろと試したところGPPAPPLY_IMPULSE力積[N・s]を入力するもののようです。 力積と力の関係は次のような式になります。

(力積)=(力)×(力の作用時間)  …力を時間で積分したもの

 力積の計算式は、質量と速度に書き換えることができます。

(力積)=(変化後の質量)×(変化後の速度)-(元の質量)×(元の速度)

 ここでは後者の式から力積を算出しています。gppsetで与えられた速度を打ち消して、速度0にするための力積を算出してgppapplyGPPAPPLY_IMPULSEを使って与えています。

 速度を打ち消す(ゼロにする)ことに成功しているため、箱は空中で静止しています。 静止後は、他に外力が加わっていないので静止したままになります。実験成功。どうやらGPPAPPLY_IMPULSEは、力積で正解のようです。

 ここまでの説明で、おそらく2つの疑問が湧いていると思います。

  • ノードの速度変化をさせたいだけなら、gppsetGPPSET_LINEAR_VELOCITYを使えばいいのでは?
  • 1回だけ力を加えているだけなら、GPPAPPLY_FORCEでいいんじゃね?GPPAPPLY_IMPULSEいらなくね?

 そうですね。速度を指定するならGPPSET_LINEAR_VELOCITYでいいと思います。 速度変化は、変化前の速度が必要です。そこで実際に1フレームごとに速度計測をやってみると速度が安定しません。1フレームあたりの時間がバラバラで測定精度が出ません。 この状態で「+?m/s加速」しようとすると、変更前の速度の精度が悪いので結果にばらつきが出てしまいます。 任意の速度に変更するのならGPPSET_LINEAR_VELOCITYが適していますが「+?m/s加速」のような場合は、GPPAPPLY_IMPULSEを選択すべきだと思います。

 また、GPPAPPLY_FORCEの場合は、1フレームの間だけ力を入力し続けるため1フレームあたりの時間にバラツキがあると結果も安定しません。 また例えば「フレームレートを60fps→30fpsに変更して作り直そう!」などのような事があった場合、1フレームあたりの時間が2倍になるので力を加える時間も長くなってしまい速度変化も2倍になります。 となるとパラメータの再調整が必要になります。手間ですね。

 長時間力を加え続ける場合はGPPAPPLY_FORCEを使って、GPPAPPLY_FORCE×フレーム数の力(力積)をノードに入力する方法が使用できます。時間が長ければ相対的に1フレームあたりの時間は小さいため、十分精度よく計算することができます。 しかし1フレーム以下などの短時間で力を加える場合は、GPPAPPLY_FORCE×1フレームとしてしまうと計算精度がよくありません。実際には1フレームの間にも加える力の大きさが変化しているためです。力×時間では近似することができません。

 そこでGPPAPPLY_IMPULSEを使用すると、すでに「力の作用時間」を含んだ値であるため1フレームの時間に関係なく物理的に正しい量の力を入力することができます。 またフレームレートの変動に影響しない安定した結果も得ることができます。

 参考にした資料へのリンクを置いておきます。図などもあるため、リンク先のほうが分かりやすいと思います。

力積 【Unity道場 2018】物理シミュレーション完全マスター - Youtube
https://www.youtube.com/watch?v=Ju4ILgpuVHE&t=1465s

0から始めるUnity物理演算⑤衝突と力積
https://nexusyuumilo.hateblo.jp/entry/unity_physics_5

まとめ

  • 重力加速度の初期値は、9.8。
  • 重力加速度を基準に考えると単位はSI単位となる。ただし単位が内部で定義されているわけではないので、好きな単位系を使ってもいい。
  • GPPAPPLY_FORCE(移動しようとする力を加算する)は、力[N]を与える。
  • GPPAPPLY_IMPULSE(瞬間的な衝撃を与える)は、力積[N・s]を与える。

 物理的な意味は説明したとおりですが、実在するものと同じ質量や力を設定してもゲーム的には気持ちよくはなりません。 実在する物体の値は参考程度にし、ゲーム的に気持ちのいいパラメータを設定することをおすすめします。

 とはいえ設定する数値の意味がわかっていれば、パラメータ調整がしやすくなるはずですし、動きにも説得力が出てくると思います。