tetu式

ゲームと音楽・作曲の自己満足と悩みどころの多いプログラムのブログ。

Unity 三角関数とゲームの関係。 MathfのSinとCos

今回は三角関数の話。

Unityの数学的な関数を利用するのに Mathf というクラスがあります。
前の8方向フリックの時にもAtan2というXの長さとYの長さを元にラジアンという角度単位を求める関数を使いました。

今回はもっと身近な三角関数であるSinとCosについて。


さて、まず三角関数と聞いて苦手意識が出た人から、名前しか知らないって人もいるかもしれません。
自分も名前は知ってるし、学校の授業で習った覚えもあるのですが・・・

どの時期に習ったか覚えてないのです。

調べてみたら義務教育で教えてた時期と高校の時に教えてた時期ってのがあるみたいで割と曖昧な数学知識のようでした。
まぁ実生活で使う機会がかなり限られているので習っても忘れてるって人も多いかと思います。

三角関数についての愚痴は置いといて、MathfのSin関数とCos関数について。

そもそもどんな時に使うかと言ったらオブジェクトの移動方向を決める時に使ったりします。
初期状態で真っ直ぐ前を向いてる時にZ軸正方向(例: Vector3(0,0,1) )に移動する物体があったとします。
その物体が横に90度回転したあともZ軸正方向移動になると同じ入力なのにカニ歩きになってしまいますね。
右に90度向いたらX軸正方向(例: Vector3(1,0,0) )に移動する様にしなければなりません。

そこで使うのが三角関数

大半の3D空間で右に90度方向転換、というのはY軸を中心に+90度という操作になります。
三角関数を学校の授業で習ったことがある人には見覚えがあると思いますが、各角度のsin、cos、tanの結果を表にしたものがあります。
これを見ていくと0を含めた90度刻みのsinθ、cosθは-1、0、1が入っています。
もうちょっと言うとどちらかが1か-1の時、もう片方には0が入ります。
このsinθとcosθの結果をそのままX軸移動、Z軸移動に置き換えると移動方向が入ります。
簡単に90度刻みを例にすると・・・

0°  :X = 0 , Z = 1 (前移動)
90° :X = 1 , Z = 0 (右移動)
180°:X = 0 , Z = -1 (後移動)
270°:X = -1 , Z = 0 (左移動)

sinθの結果がXに、cosθの結果がZにそのまま入る形になります。
なのでY軸中心の角度を元にsinやcosを使うと直進時の向きを決めることができます。

ちなみに45°は1/√2 。小数で言ったらおよそ0.71程度です。
一昔前の俯瞰型2Dゲーム(スーファミゲームボーイゼルダとか)なんかで斜め移動の方が早く動いてるように見えるのは三角関数が使われてないからです。
本来斜め45°は縦軸横軸とも移動量0.71程度なのが自然なのですが、
縦軸の移動量1と横軸の移動量1が同時に入力されているため、その分早く見えるってわけですね。

さて、ここからUnityの内容。

Mathf.Sin(float r)やMathf.Cos(float r)に入れる引数はfloat型の変数ですが、入れる変数の中身というのは8方向フリックの時にも話題にしたラジアンという単位。
およそ57.3°を1とする角度の単位です。
なんでこんな半端なのか調べたら、分かりやすく、納得のいく回答として180度をπとする単位のこと。
57.3°に円周率の3.14を掛ければほぼ180°になります。
これを知ったら中学校の時に盲目的に覚えた円周の長さの計算式「2πr」の意味が分かるってもんです。

で、この引数に入れるべき数値について。
Unityの GameObject には Postion と Rotation と Scale という3つの Vector3 を持っています。
Positionは物体の位置、Rotationは物体の角度、Scaleは物体の拡大率です。

Inspector上ではRotationに入れる数値は0~360の通常の角度。
360をオーバーしてる場合は再生した時に360以下になるまで360ずつ減算してくれます。

Script上でそれっぽい変数として見られているのが

this.transform.rotation

このrotationという変数、Vector3ではなくてQuaternionという四元数と呼ばれる物が入ってます。
検索するとwikipediaの説明を見れますが、軽く寝れるほど見た目が難しい内容です。

簡単に説明するとこれも角度を求められる単位ではあります。
が、最終値が合えばいいらしく、数字の符合をちゃんと見ない性質があります。
ある点が10°傾いたのか、370°傾いたのか、-350°傾いたのか、という部分は見てくれません。

実際、このrotationというのをDebug.Logで見た時、最初は
(0.0 , 0.0 , 0.0 , 1.0)
と表示されていたものが、360度回転する動作をした後にもう一度表示すると
(0.0 , 0.0 , 0.0 , -1.0)
と、回転後の向きは同じでも返ってくる値に差が出てきます。

なのでこれをSinやCosに使うのはハズレです。

で、本当に使うべき角度の変数は

this.transform.eulerAngles
もしくは
this.transform.localEulerAngles

というVector3の変数。オイラーアングルズ(オイラー角)と読みます。
これも検索するとややこしそうな式が出てきますが、UnityにおいてはInspectorの角度をそのまま持ってこれる変数という認識で問題ないと思います。

これを使ってSinとCosの引数に入れますが、もうひと手間。
この変数はラジアン単位ではないので変換しなければなりません。

変換するのがMathf.Deg2Rad という定数的なもの。
8方向フリックの時もちょっと出てきた Mathf.Rad2Degの逆版です。

これを掛け算することで通常の角度をラジアン単位にできます。

Vector3 moveDirection;

moveDirection = new Vector3(Mathf.Sin(this.transform.euler.y * Mathf.Deg2Rad) ,
                            0 ,
                            Mathf.Cos(this.transform.euler.y * Mathf.Deg2Rad) );

this.transform.position = this.transform.position + moveDirection;

これでオブジェクトの向きを求め、その向きどおりに移動してくれます。




・・・とまぁ、ここまで書いたはいいのですが・・・

this.transform.Translate(0,0,0.1f);

実はこれだけで前方向に移動するというのは実現できます。
これは物体そのものが持っている軸を元にその方向に移動する関数であり、物体に対して相対的にその方向に移動してくれます。


まぁ、書いてきた内容は内容でUnity以外のツールでも使える共通の考え方になるので知っておいて損はないと思います。



追記2015/06/10
なにやらこのブログ内でこの記事が一番人気らしいので見つけた誤字や記法ミスを訂正。