Unityで基本的な数学・物理学を使った処理

今回はUnityで基本的な数学・物理学を使った処理を行ってみたいと思います。

わたくし自身は数学・物理学は得意ではないので理解を助ける為にUnityで実際に計算して試してみるというただのメモ書きになります。

距離、速さ、時間の計算と等加速度運動の式、三角関数を使ってみようと思います。

わたくしみたいな数学、物理が苦手な人の助けになる記事かも?しれません。

今回の記事を見て頂くと、

↑のような砲台から弾を発射し放物線上に移動して到達点に達するような機能を作成する事が出来ます。

スポンサーリンク

距離、速さ、時間を使った計算

距離、速さ、時間を使った計算は小学生の時にやりましたね。

距離 = 速さ × 時間

という式です。

距離、速さ、時間は単なる数値(スカラー値)なので方向を持っていません。

2Dや3Dのゲームではゲームオブジェクトは二次元ベクトルと3次元ベクトルで位置を表示しますので、距離を計算する場合は2点間のベクトル(大きさと方向を持つ)の長さ(スカラー値)を計算する必要があります。

3次元ベクトルpos1とpos2があったとしたら、その距離を計算するには

とVector3.Distanceを使うと2点間の距離を計算出来ます。

個々の要素の引き算の2乗をして足して、全体の平方根を取っても距離が計算が出来ます。

Mathf.Sqrtが平方根、Mathf.Powで累乗を計算出来ます。

試しにサンプルを作成してみます。

CalculateDistanceスクリプトを作成しMain Cameraに取り付けます。

pos1とpos2はインスペクタで設定出来るので、インスペクタでpos2のXを2とします。

2だけX軸で移動した点なのでコンソールには2が表示されます。

インスペクタでpos1とpos2の位置を他の値に変えて確認してみてください。

弾を横に一定速度で飛ばす

2点間の距離が計算出来たので、ある物体(球)をpos1からpos2に移動させるスクリプトを組んでみたいと思います。

球は一定の速さでpos1からpos2に移動させるとし、あらかじめ速さを与えてpos2に到達した時の時間を求めてみます。

速さを10としました。

Updateメソッドでゲームオブジェクトの位置を直接変更しています。

Unityでは1が1mで計算するとして速さが10だと1秒間で10m移動なので10(m/s)になります。

距離(100m) = 速さ(10m/s) * 時間(t)

で計算出来るので、pos1の位置を(0, 0, 0)としpos2を(100, 0, 0)にするとゲームオブジェクトがpos2に到達する時間は10秒程度になります。

Time.deltaTimeについて

位置を更新する際にspeedの値にTime.deltaTimeをかけたものを元のX軸の値に足しています。

これはspeedだけだとUpdateメソッドが呼ばれる度にspeedの値分X軸の値が変更されてしまうので、

距離 = 速さ × 時間

の式の通りに速さに時間をかける必要があります。

Updateメソッドは1秒間に何十回も呼ばれるので、1秒当たりの時間を計算する為にTime.deltaTimeをかけます。

Time.deltaTimeをUnityのスクリプトリファレンスで確認すると

最後のフレームを完了するのに要した時間(秒)

となっています。

つまり前回Updateメソッドが呼ばれてから今回のUpdateメソッドが呼ばれるまでの時間が求められます。

その為、

1回のUpdateでのX軸の移動距離 = speed × Time.deltaTime

になり、それを元のX軸の値に足すことでX軸の移動先の値になります。

ヒエラルキー上に右クリック→3D Object→Sphereを選択し、名前をBulletとしてSphere ColliderのIs Triggerのチェックを一応外しておきます。

CalculateDistanceスクリプトのインスペクタのbulletに設定します。

このように距離、速さ、時間の中の2点がわかればもう一つの値が求まりますね。

Rigidbodyを使って同じように飛ばす

自身で弾の位置を計算せずRigidbodyの機能を使って弾を飛ばし、先ほどと同じような速さで移動させたい事もあります。

そこでRigidbodyに力を加えて同じような速さで飛ぶようにスクリプトを組んでみます。

ヒエラルキー上にSphereを新しく作り名前をBullet2としてRigidbodyコンポーネントを取り付けます。

ShotBulletスクリプトを作り取り付けます。

先ほどはMain Cameraに取り付けたスクリプトでBulletゲームオブジェクトを操作していましたが、今度はBullet2ゲームオブジェクト自体にスクリプトを取り付け操作する事にします。

pos1から見たpos2の方向は

pos2 – pos1

で計算出来ます。

RigidbodyのAddForceメソッドを使ってBullet2オブジェクトに力を加えます。

力を加える時に力を加える方向のベクトルが必要ですが、pos2 – pos1は大きさを持ったベクトルとなっている為、方向のみのベクトルではありません。

そのまま速さをかけてしまうとちゃんとした計算が出来ません。

そこでpos2 – pos1で得られた方向のベクトルを.normalizedで正規化し大きさが1の方向のベクトルを計算し、それにspeedをかけます。

力を加えるモードはForceMode.Impulseで即座にゲームオブジェクトに力を加えていますが、これは単位が

質量 × 距離 / 時間

なのでRigidbodyのMassをデフォルト値の1としていた場合、距離 / 時間が加える力になります。

距離 / 時間は速さなのでこの速さをAddForceの第1引数に与えればいいことになります。

ですが3次元ベクトルの場合は

方向 × 速さ = 速度

を計算する必要があるので、先ほどの方向のベクトルを求めてそれに速さをかけ速度を求め、それをAddForceの第1引数に与える必要があります。

自前で計算して移動させたものとRigidbodyのAddForceで移動させたものは

一定速度で弾を動かしたサンプル

↑のような感じで同じような動きをします。

実際にBulletとBullet2の到達時間を比較するとだいたい同じ時間になると思います。

自前で移動させたものとRigidbodyで移動させたものの比較

等加速度運動

次に等加速度運動の式を使った処理をやってみます。

重力を働かせる

距離、速さ、時間を使って横に一定速度でゲームオブジェクトを移動させる事が出来ました。

次はゲームオブジェクトに重力を働かせてみます。

重力に関しては

Unityのキャラクター等に重力を働かせる事は多いですが、重力を働かせる処理が実は間違っているかもしれません。

の記事で行っていますが、CharacterControllerのMoveメソッドの引数にはキャラクターの速度を指定する必要があるので、キャラクターのY軸の速さを

速さ = 重力加速度 × 時間

という式で求める為、

速さ = Physics.gravity.y × Time.deltatime

という式を当てはめます。

Physics.gravity.yはUnityメニューのEdit→Project Settings→Physicsで設定されている定数でY軸の数値で-9.81で定義されています。

なのでオブジェクトに自由に重力加速度を設定したい場合は定数を使わず

速さ = -5.5f * Time.deltatime

としても大丈夫です。

ゲームオブジェクトの落下速度はこれで計算出来ますが、Main Cameraに取り付けたCalculateDistanceスクリプトのように直接ゲームオブジェクトの位置を移動させる場合は移動した変位(距離)を計算したいところです。

そこで等加速度運動の式を使います。

距離 = 初速度 × 時間 + 1/2 × 加速度 × 時間²

となります。

自由落下すると仮定して初速度を0にすれば

のようにスクリプトで書けます。

1/2を0.5fとしてかけているのは、プログラムでは割り算より掛け算の方が計算が早いみたいだからです。

CalculateDistanceスクリプトを少し改良しAcceleratingBulletスクリプトを作成しBulletに取り付けます(Main Cameraに取り付けたTrigonometrixFunctionTestスクリプトは外しておきます)。

落下する距離は

距離 = 0.5f × 重力加速度 × 時間

なので落下している時間をUpdateが呼ばれる度に計算します。

重力で落下する距離を計算した時間を使って求めます。

gravityYは重力で落下した距離を計算したものなので、元々の位置にこの落下距離を足すことで移動先のY軸の値を計算します。

これで重力による落下の処理が出来ました。

Rigidbodyで重力を働かせる

Bullet2ゲームオブジェクトに取り付けたShotBulletスクリプトではRigidbodyのAddForceメソッドを使ってX軸に力を加えていました。

重力を働かせたい場合は先ほどのように自分で計算式を作ることも出来ますが、インスペクタのRigidbodyの設定を変えるだけで重力を働かせることが出来るのでそちらを使ってみます。

RigidbodyのUse Gravityにチェックを入れ重力を働かせる

↑のようにBullet2ゲームオブジェクトのRigidbodyのUse Gravityにチェックを入れます。

ShotBulletスクリプトを取り外しAcceleratingBullet2スクリプトを作成し取り付けます。

力を加える方向はX軸の方向にしました。

ShotBulletスクリプトと同じように2点間からその移動先の方向を求めてそちらに力を加える方がゲームで使う時に便利ですが、今回はX軸に力を加えればいいだけなので書き換えました。

このスクリプトでは横に移動するだけですが、RigidbodyのUse Gravityにチェックを入れたのでそちらで重力の計算が勝手に行われます。

自身で計算した重力を働かせた物体とRigidbodyの設定で重力を働かせた物体の動きを確認するとほとんど同じ動きをするのがわかります。

Bullet2ゲームオブジェクトを少し横にずらして確認してみます。

自前重力計算とRigidbodyの重力の比較

↑のようになりました。

同じ位置からスタートさせるとほぼ同じ動きになっているのがよりわかりやすくなります。

三角関数を使う

次は三角関数を使ってみようと思います。

三角関数は辺と角度の関係を表した関数です。

三角関数

sinθ = b / c
cosθ = a / c
tanθ = b / a

となります。

詳しい事は数学の本やサイトをご覧ください。

弾を放物線上に飛ばす

ここまでで弾を横に等速度運動させ、重力を使って下に加速度運動をさせることが出来ました。

横と下に移動する弾

しかし弾を砲台から発射し上向きに弾を飛ばしてから自然に落下するような処理を作りたい時もあります。

UnityではRigidbodyのAddForceメソッドを使って砲台の向いている向きに力を加え放物線上に飛ばすということも出来ます。

ですが弾が狙いを定めた位置で丁度落下するように砲台の角度を設定したいこともあります。

そういった時は三角関数を用いて砲台をどの程度上向きにするかという計算をする必要があります。

直角三角形で計算出来る位置に到達点がある

砲台から弾を飛ばす場合に砲台と飛ばす先のY軸の値が同じであれば直角三角形を用いて砲台の角度をどれだけ上向きにすればいいかがわかります。

まずは砲台をCubeを使って作成します。

ヒエラルキー上にCubeを作成し名前をButteryにし、Transformの値を変更します。

ButteryのTransformの設定

Butteryの子要素に空のゲームオブジェクトを作成し名前をBulletHoleとし、位置をButteryのローカル軸の青の方向の先に移動させます。

BulletHoleの位置

BulletHoleは↑のように弾を発射する位置に移動させます。

自分で式を計算して弾を飛ばす

砲台の角度を計算し、自分で弾の次の位置を計算し到達点に飛ばす機能を作成してみます。

新しいスクリプトTrigonometricFunction1スクリプトを作成し、Butteryに取り付けます。

ゲームオブジェクトが登場した時にShotメソッドを呼び出します。

最初にShotメソッドを見ていきます。

弾の放射位置と到達点の距離を計算し変数xに入れます。

その距離xをspeedで割って弾が到達点に到達する時間を計算しarrivalTimeに入れます。

等加速度運動の式を使ってarrivalTimeで落下する距離を計算しfallingDistanceに入れます。

アークタンジェントのメソッドを使って落下距離とX軸の距離から角度thetaを計算します。

ここで砲台の向きを到達点の方向に向けます。

これは上向き以外の向きを到達点に向けさせるためです。

transform.Rotateを使って砲台を計算した角度分上向きにします。

弾のプレハブをインスタンス化します。

speedは弾がX軸を進む速さなので、斜めに進む速さを求めます。

斜辺 × cosθ = 底辺

なのでこれを使って斜辺の長さを求めます。

となります。

次にUpdateメソッドを見ていきます。

Updateメソッドでは弾のインスタンスが存在していれば弾の位置を計算し移動させます。

経過時間はtotalTimeに足していきます。

弾に働く重力で移動する距離は自由落下運動の式にあてはめ移動値を求めます。

斜辺方向の距離は

で弾が向いている方向(弾のインスタンス化時にBulletHoleの向きに合わせた)に斜辺の速さをかけ、それに経過時間をかけることで移動先の位置が求められます。

弾の位置はこの位置に重力での落下距離を足した位置になるのでそれをbulletIns.transform.positionに入れます。

これで弾が到達点に放物線上に発射されます。

ヒエラルキー上にCubeを作成し名前をArrivalPointとしTransformのPositionのXに50を設定してみます。

TrigonometricFunction1のインスペクタにArrivalPointを設定します。

bulletには弾のプレハブを設定します。

それでは実行してみましょう。

砲台から発射する弾を自分で計算して放物線上に動かしたサンプル

↑のように到達点に向けて放物線上に弾が移動しています。

Rigidbodyで放物線上に弾を飛ばす

自分で弾の位置を計算する事が出来ましたが、Rigidbodyを使った処理でもやってみましょう。

TrigonometricFunction2スクリプトを作成し砲台(Buttery)に取り付けます。

こちらは一定時間が経過したら弾をインスタンス化しどんどん飛ばすようにしています。

Shotメソッド内はTrigonometricFunction1スクリプトのものとほとんど同じですが、この中で弾のRigidbodyコンポーネントを取得しAddForceを使って力を加えています。

力を加える方向は弾の向きに斜辺の速さをかけたものです。

重力の計算は弾のプレハブに取り付けたRigidbodyコンポーネントのUse Gravityにチェックを入れて働かせます。

これでRigidbodyを使った処理が出来ました。

砲台と到達点で直角三角形にならない場合

さて、先ほど砲台と到達点と落下距離から直角三角形になる場合の処理が出来ました。

しかし到達点のY軸の値を砲台と違う値にした場合は弾がうまく到達点に達しません。

そこで余弦定理を使って直角三角形にならない場合でも弾がうまく飛ぶように処理を変更していきます。

下のような三角形があり、hypotenuse(斜辺)、adjacent(隣接辺)、opposite(対辺)の3辺がわかっている場合に

余弦定理

cosθ = (hypotenuse)² + (adjacent)² – (opposite)² / (2 × hypotenuse × adjacent)

↑のような式が成り立ち、これを余弦定理と呼びます。

角度を求めるにはアークコサインを計算すればいいので、

θ = cos⁻¹((hypotenuse)² + (adjacent)² – (opposite)² / (2 × hypotenuse × adjacent))

を計算します。

今回はこの余弦定理を使って砲台の角度を計算したいと思います。

自分で式を計算して弾の位置を計算する

まずは弾の位置を自分で計算するバージョンを作成していきます。

TrigonometricFunction3スクリプトを作成しButteryに取り付けます。

Shotメソッドでやっていることは砲台の位置と到達点の距離でadjacentを求めます。

さらに、重力での落下距離を計算しoppositeを求め、到達点の上側に落下距離分を足した位置を求め、砲台の位置と到達点の上の位置との距離でhypotenuseを求めます。

これで3辺が求まりました。

3辺の長さが求められたので余弦定理でcosθを求め、そこからθの値を求めます。

あとは弾の位置を計算する時に必要な斜辺方向の速さを求める必要があるので、斜辺の長さ / 時間で速さを求めます。

それを行っているのが、

の部分です。

これで機能が出来上がりました。

サンプルはRigidbody版の方で試した方がわかりやすいのでRigidbody版を作ってから紹介します。

Rigidbodyで弾の動きを作成する

次はRigidbodyの機能で動かすバージョンを作成していきます。

TrigonometricFunction4スクリプトを作成しButteryに取り付けます。

やっていることはTrigonometricFunction3スクリプトとほぼ同じでShotメソッド内で斜辺方向に力を加えているだけです。

インスペクタのbulletにRigidbodyのUse Gravityにチェックを入れた弾のプレハブを設定します。

実行すると、

↑のように到達点を移動させても弾が丁度当たるように飛んでいるのがわかります。

記事の最初に示したサンプルと同じです。

基本的な数学、物理を使った計算で弾を飛ばしてみましたがなかなか使えそうですね(^^)v

スポンサーリンク

記事をシェアして頂ける方はこちら

フォローして頂くとやる気が出ます