今回はUnityのTransformのワールド空間とローカル空間の違いについて見ていきたいと思います。
UnityのゲームオブジェクトはインスペクタのTransformでPosition、Rotation、Scaleの値を見る事が出来ます。
Unityを使い始めの頃はPositionで言うと(0, 0, 0)を基準点とした数値だと思っていました。
しかしゲームオブジェクトは他のゲームオブジェクトの子要素として配置する事が出来、子要素のゲームオブジェクトは(0, 0, 0)を基準としたPositionにはなっていません。
子要素のゲームオブジェクトのPositionは親のゲームオブジェクトのPositionからの相対値が表示されています。
そこで今回は親のゲームオブジェクトと子のゲームオブジェクトを使って実験し、違いを確認してみたいと思います。
親と子のTransform情報の確認
ヒエラルキー上で右クリックし3D Object→Cubeを作成し、名前をParentとします。
その子要素にCubeを作成し名前をChildとします。
ChildをParentの子要素にドラッグ&ドロップし移動させます。
親と子のゲームオブジェクトのインスペクタでPosition、Rotation、Scaleを↑のようにまったく同じ数値を設定します。
すると同じ数値を設定したにもかかわらず↑のように位置や角度、大きさが変わりました。
右上が子要素であるChildゲームオブジェクトですが、ChildのTransform情報はParentからの相対値になっている事がなんとなくわかると思います。
ワールド空間とローカル空間
子のゲームオブジェクトのTransform情報は親のゲームオブジェクトからの相対値という事が確認出来ました。
親を持たないゲームオブジェクト(親)のPositionやRotation、Scaleはワールド空間になります。
親を持つ子のゲームオブジェクトは親からの相対値で表示されるのでローカル空間になります。
ワールド空間とローカル空間を例えると
説明臭い文章だと分かり辛いですが、単純に考えるとすぐにわかります。
例えば現実の世界では方向(方角)や位置等が決まっています。
そこに玄関が向いている方向を西側にして、ある人の家を世界のどこかに建てます。
現実の世界の方向は決まっているので、家のワールド空間の方向は西側を向いていて、位置は建てた場所の位置になります。
家の中にあるタンスや冷蔵庫は家が移動式の家で移動出来るとしても一緒に動くのでタンスや冷蔵庫のTransformの方向や位置の値は変わりません(家からの相対値で家の方向や位置から見た値)。
これがローカル空間の値です。
仮に家の玄関の方向を北側にしたとしてもタンスや冷蔵庫のTransformの値は変わりません。
これは家の玄関の方向(玄関の方向を家の向きと考えた場合)や位置からの相対値なので元の家を動かしたり回転させても家の中の物は相対的に同じになります。
ただし、タンスや冷蔵庫のワールドの方向や位置は世界全体から見たものなのでTransformのワールド空間の値をスクリプト等で取得する(またはヒエラルキー上で親子関係を解消し、タンスや冷蔵庫を一番の親とする)と値はローカル値とは変わります。
これがワールド空間の値です。
ワールド空間とローカル空間の表示の仕方
Unityで位置や角度、サイズを扱う時にもワールド空間でのデータ、ローカル空間でのデータと取得の仕方を変更する事が出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // ワールド空間位置情報 transform.position // ワールド空間角度情報 transform.rotation // ワールド空間サイズ情報 transform.lossyScale // ローカル空間位置情報 transform.localPosition // ローカル空間角度情報 transform.localRotation // ローカル空間サイズ情報 transform.localScale |
↑のようにゲームオブジェクトのワールド空間での情報、ローカル空間での情報を取得出来ます。
ワールド、ローカル値を両方表示してみる
さきほどの親と子のゲームオブジェクトの親にスクリプトを取り付けどのようなデータが出力されるか確認してみます。
スクリプトの名前はWorldLocalという名前にします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using UnityEngine; using System.Collections; public class WorldLocal : MonoBehaviour { public Transform parent; public Transform child; // Use this for initialization void Start () { // ワールド空間でのデータ Debug.Log("ParentWorld:" + parent.position + "|" + parent.eulerAngles + "|" + parent.lossyScale); Debug.Log ("ChildWorld:" + child.position + "|" + child.eulerAngles + "|" + child.lossyScale); // ローカル空間でのデータ Debug.Log("ParentLocal:" + parent.localPosition + "|" + parent.localEulerAngles + "|" + parent.localScale); Debug.Log ("ChildLocal:" + child.localPosition + "|" + child.localEulerAngles + "|" + child.localScale); } } |
↑のようにpublicで親のゲームオブジェクトと子のゲームオブジェクトをインスペクタで設定出来るようにし、それぞれのワールド、ローカルでの値を表示しています。
親のゲームオブジェクトは自身の親がないのでワールドでもローカルでも表示は同じです。
子のゲームオブジェクトはワールドの場合はインスペクタで表示されているデータではなくワールド値が表示されています。
ローカルの場合はインスペクタで表示されているデータと同じですね。
ワールド空間とローカル空間の変換
ワールド空間とローカル空間の表示の仕方がわかりました。
次に、ワールド空間の値とローカル空間の値を変換出来るようにしてみましょう。
例えば子のゲームオブジェクトが向いている方向に移動させたいと思います。
親のゲームオブジェクトであればtransform.forwardが前方を指しますのでこのプロパティを使えば済みます。
実は子のゲームオブジェクトでもtransform.forwardを使用すればワールド空間での方向が取得出来るので問題はありませんが・・・、
場合によっては子のゲームオブジェクトのローカルの前方を取得したい事があります。
例えが浮かばない・・・・(^_^;)
そんな時にワールドとローカルの相互変換が出来ると便利です。
1 2 3 4 5 6 7 8 9 10 11 12 | // 方向ベクトルをワールド→ローカルへと変える transform.InverseTransformDirection(Vector3の値); // 位置をワールド→ローカルへと変える transform.InverseTransformPoint (Vector3の値); // 方向ベクトルをローカル→ワールドへと変える transform.TransformDirection(Vector3の値); // 位置をローカル→ワールドへと変える transform.TransformPoint (Vector3の値); |
引数として渡すのは全てVector3の値で戻り値も全てVector3の値が返ってきます。
これらの処理は実に混乱しやすい処理になります。(^_^;)
サンプル1
サンプルを作成し実行してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Update is called once per frame void Update () { // 子のゲームオブジェクトが向いているワールドの方向 Debug.Log("子の方向:" + child.forward); // 子のゲームオブジェクトのワールド位置 Debug.Log("子の位置:" + child.position); // 方向ベクトルをワールド→ローカルで表示 Debug.Log("InverseTransformDirection:" + transform.InverseTransformDirection(child.forward)); // 位置をワールド→ローカルで表示 Debug.Log("InverseTransformPoint:" + transform.InverseTransformPoint (child.position)); // 位置をローカル→ワールドで表示 Debug.Log("TransformPoint:" + transform.TransformPoint (child.localPosition)); // 方向ベクトルをワールドで表示 Debug.Log("TransformDirection:" + transform.TransformDirection(child.forward)); } |
↑のようなサンプルスクリプトを作成し親と子両方のゲームオブジェクトに設定します。
child.forwardで子の向いているワールドの方向、child.positionでワールド位置を取得出来ます。
それらを設定したスクリプトのゲームオブジェクトから見た変換を行います。
サンプルを実行する前に親と子のデータをわかりやすいものに変えます。
親のPositionは(0, 0, 0)、Rotationは(0, 0, 0)、Scaleは(1, 1, 1)に設定します。
子のPositionは(1, 1, 1)、Rotationは(0, 45, 0)、Scaleは(1, 1, 1)に設定します。
親と子に設定したスクリプトのどちらかを無効化し実行します。
↑が実行結果です。
スクリプトを設定した位置で実行結果が変わっていますね、かなり混乱します・・・・。
スクリプトを設定したゲームオブジェクトから見たワールドの位置やローカルの位置になるからです。
InverseTransformDirectionでは親にスクリプトを設定した場合(0.7, 0, 0.7)、子に設定した場合(0, 0, 1)が出力されています。
これは親から見た子の方向をローカルに変更すると子をローカル表示した時にZ軸がそちらの方向を向いているからです。
子に設定した場合は子の向いている方向を子から見ているのでZ軸と子の向いている方向が同じになり(0, 0, 1)になっています。
同じ理由でInverseTransformPointも親から見た子のローカル位置は(1, 1, 1)、子から見た子のローカル位置は(0, 0, 0)となります。
TransformPointも同じで親の場合は(1, 1, 1)、子の場合は(2.4, 2.0, 1.0)となります。
子をドラッグして親子関係を解消すると子のPositionが(2.4, 2.0, 1.0)となるのでわかりやすいと思います。
最後のTransformDirectionの場合は子の値がX軸向きになっています。
TransformDirectionはローカルの方向をワールドに変換するものなので指定している引数がワールドの方向を渡している為おかしな結果となっていると思います。
これらの解説を見てわかるとおり非常にこれらの処理は解り辛いですね・・・・(^_^;)
その為、必要に迫られない限りはこれらの処理は使わない方が混乱の元にならないと思います。
わたくしは完全に混乱しました・・・・・( ノД`)シクシク…
サンプル2
先ほどのサンプルだと少し分かり辛いのでもう一つ別のサンプルを作成してみます。
上のように新しくCubeを作成し、その子要素にCubeを作成します。
Main CameraのPositionをXを1、Yを2、Zを3、RotationのXとZを0、Yを270にします。
親のCubeはPositionのYを2、XZを0、RotationのXYZを0にします。
子のCubeはPositionのXを5、YZを0、RotationのYを180、XZを0とします。
Cubeに以下のスクリプトを取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class WorldLocalTest : MonoBehaviour { // Start is called before the first frame update void Start() { // このゲームオブジェクトのワールド位置をメインカメラからの相対位置に変換 Debug.Log(Camera.main.transform.InverseTransformPoint(transform.position)); // ワールド座標のX方向ベクトルをメインカメラのローカルの方向に変換 Debug.Log(Camera.main.transform.InverseTransformDirection(Vector3.right)); // このゲームオブジェクトのローカル座標をワールド座標としてメインカメラの位置に足す Debug.Log(Camera.main.transform.TransformPoint(transform.localPosition)); // メインカメラから見た引数で与えた方向ベクトルの向きを返します。 Debug.Log(Camera.main.transform.TransformDirection(Vector3.right)); } } |
最初のInverseTransformPointは位置をワールド空間からローカル空間へと変換するメソッドで、このスクリプトを取り付けた子要素のCubeのワールド位置をメインカメラの子要素に移動した時のPositionが得られます。
コンソールには
(-3.0, 0.0, -4.0)
が表示されます。Unity実行中に子要素のCubeをドラッグしてMain Cameraの子要素に移動させるとインスペクタのPositionがこの値になります。
InverseTransformDirectionは方向をワールド空間からローカル空間へと変換するメソッドで、ワールドの右方向(Vector3.right)をメインカメラのローカルの方向に変換します。
コンソールには
(0.0, 0.0, -1.0)
が表示されます。
Vector3.rightの方向はメインカメラの反対方向を向いているので上のような値が返ってきます。前方方向は(0.0, 0.0, 1.0)なのでZの値が反転しています。
TransformPointは位置をローカル空間からワールド空間に変換するメソッドで、子要素のCubeのローカル位置をワールド位置に変換しメインカメラの位置に足した結果が得られます。
このサンプルだと分かり辛いですが、メインカメラのローカル空間からこのスクリプトが設定されているゲームオブジェクトの位置分動かした場所の位置が返されます。
メインカメラのRotationのY軸は270にしているので前方はX軸の反対側を向いています。
上のようにメインカメラの前方(青い矢印の方向)はワールド空間のX軸の反対側を向いています。
引数で指定したのはtransform.localPositionでCubeのローカルポジションは(5, 0, 0)となっています。
つまりメインカメラのローカル空間のX軸方向にこのローカルポジションのXを足した位置がTransformPointで返される位置になります。
コンソールには
(1.0, 2.0, 8.0)
が表示されます。
TransformDirectionは方向をローカル空間からワールド空間に変換します。サンプルではメインカメラのVector3.rightの方向を求めています。
コンソールには
(0.0, 0.0, 1.0)
が表示されます。
メインカメラのローカル空間の右側(Vector3.right)をワールド空間にしていて、メインカメラはワールド空間でX軸の反対側を向いていてローカル空間の右側はワールド空間のZ軸方向になっています。
なのでVector3.rightの方向はワールド空間のZ軸方向になるので(0, 0, 1)の値が出力されます。
んー『サンプル2』の項目作りましたがやっぱり混乱しますねぇ・・・・(^_^;)
終わりに
ワールド空間、ローカル空間の値を得るには専用のプロパティを使えば簡単に取得する事が出来ます。
ただし、それらを変換しようと思うと非常に解り辛い処理が絡んできます・・・・。
大体のローカル値の取得はtransform.localPositionやtransform.localRotation、transform.localScaleを使えば事足ります。
変換メソッドは結構混乱しますね・・・・。