Unityのtransform.positionとlocalPositionの違い|世界から見た座標と親から見た座標

広告

前回は、弾痕を壁の子GameObjectにしました。

Hierarchyは、

Wall
└ BulletHole

という状態です。

そして、

bulletHole.transform.SetParent(
    hit.collider.transform,
    true
);

とすることで、

壁が動いたり回転したりしても、弾痕が一緒についてくるようになりました。

壁をクリックして弾痕を生成し、壁の子にするまでの前回までの流れ

ここで新しい疑問が出てきます。

BulletHoleの位置は、どの座標で表されているのでしょうか?

Unityには、位置を表す代表的なプロパティとして、

transform.position

と、

transform.localPosition

があります。

今回は、この2つの違いを見ていきましょう。

positionは「世界から見た位置」

まず、

transform.position

です。

これは、

Unityの世界全体から見たGameObjectの位置

を表します。

Unity 6の公式リファレンスでも、Transform.position は「Transformのワールド空間上の位置」とされています。

たとえばWallが、

X = 10
Y = 0
Z = 0

にあるとします。

そしてBulletHoleが、

X = 10.5
Y = 1
Z = 0

にあるとします。

この場合、

bulletHole.transform.position

は、

(10.5, 1, 0)

です。

これは、

シーン全体の原点から見てどこにいるか

を表しています。

localPositionは「親から見た位置」

一方、

transform.localPosition

は、

親Transformから見た相対位置

です。

Unity 6の公式リファレンスでも、localPosition は親Transformに対する相対位置と説明されています。

先ほど、

Wall
└ BulletHole

という親子関係がありました。

Wallが、

World Position
(10, 0, 0)

にあり、

BulletHoleが、

World Position
(10.5, 1, 0)

にあるとします。

WallからBulletHoleを見ると、

右へ 0.5
上へ 1

です。

したがって、単純な回転・スケールのない例なら、

bulletHole.transform.localPosition

は、

(0.5, 1, 0)

になります。

2つを並べてみる

同じBulletHoleでも、

transform.position

と、

transform.localPosition

では値が違います。

たとえば、

Wall

position
(10, 0, 0)

└ BulletHole

position
(10.5, 1, 0)

localPosition
(0.5, 1, 0)

です。

つまり、

position
↓
世界全体から見た場所

localPosition
↓
親から見た場所

です。

移動前のWallとBulletHoleのpositionとlocalPositionの値

「誰から見た座標か」が違う

ここが今回の最重要ポイントです。

どちらも、

位置

を表していることに変わりありません。

違うのは、

どこを基準にしているか

です。

transform.position

なら、

Unity世界の原点
(0, 0, 0)

を基準にします。

一方、

transform.localPosition

なら、

親GameObject

を基準にします。

一言で言えば、

position
=
世界から見た座標

localPosition
=
親から見た座標

です。

transform.positionは世界から見た位置、transform.localPositionは親から見た位置

実際にConsoleへ表示してみよう

BulletHoleを生成したあとに、

Debug.Log(
    "position = "
    + bulletHole.transform.position
);

Debug.Log(
    "localPosition = "
    + bulletHole.transform.localPosition
);

を追加してみましょう。

たとえば、

position = (10.50, 1.00, 0.00)

localPosition = (0.50, 1.00, 0.00)

のような違いを確認できます。

親を動かすとどうなる?

ここからが面白いところです。

現在、

Wall
position = (10, 0, 0)

└ BulletHole
  position = (10.5, 1, 0)
  localPosition = (0.5, 1, 0)

だとします。

Wallを、

X = 10

から、

X = 20

へ移動してみます。

するとBulletHoleも親について移動します。

結果は、

Wall
position = (20, 0, 0)

└ BulletHole
  position = (20.5, 1, 0)
  localPosition = (0.5, 1, 0)

となります。

ここに重要な違いがあります。

Wallを右に移動した後は、positionは変わるがlocalPositionは変わらない

positionは変わる

Wallが移動すると、

BulletHoleの、

transform.position

も変わります。

なぜなら、

BulletHoleは世界全体から見ると、

実際に別の場所へ移動している

からです。

移動前
position = (10.5, 1, 0)

移動後
position = (20.5, 1, 0)

となります。

localPositionは変わらない

一方、

transform.localPosition

は、

(0.5, 1, 0)

のままです。

なぜでしょう。

BulletHoleから見れば、

Wallから右へ0.5
Wallから上へ1

という関係は変わっていないからです。

つまり、

Wall
      ● BulletHole

親から見た位置関係

は、そのままなのです。

これが「子が親についてくる」の正体

前回、

親を動かすと子もついてくる

と学びました。

今回の内容とつなげると、

その仕組みがさらに分かりやすくなります。

BulletHoleは、

localPosition

によって、

Wallから見てこの位置にいる

という関係を持っています。

Wall自体が動くと、

その相対関係を保ったまま、

position

が変化します。

つまり、

localPosition
↓
親との位置関係を保持

親が移動
↓
世界での位置が変わる

position
↓
新しいワールド座標になる

ということです。

数字で見るとさらに分かりやすい

最初は、

Wall.position
= 10

BulletHole.localPosition
= 0.5

です。

単純化すれば、

BulletHole.position

10 + 0.5
=
10.5

と考えることができます。

次にWallを、

20

へ動かします。

すると、

20 + 0.5
=
20.5

となります。

BulletHoleの、

localPosition = 0.5

は変わっていません。

変わったのは親の位置です。

localPositionを0にするとどうなる?

では、

bulletHole.transform.localPosition =
    Vector3.zero;

としてみましょう。

Vector3.zero は、

(0, 0, 0)

です。

これは、

親から見て位置0

という意味です。

つまりBulletHoleは、

親Wallの原点と同じ場所

へ移動します。

positionを0にすると意味が違う

一方、

bulletHole.transform.position =
    Vector3.zero;

とすると、

BulletHoleは、

世界の原点

(0, 0, 0)

へ移動します。

同じ、

Vector3.zero

を代入しているのに、結果が大きく違います。

比較してみよう

transform.position =
    Vector3.zero;

なら、

世界の原点へ移動

です。

一方、

transform.localPosition =
    Vector3.zero;

なら、

親の原点へ移動

です。

これは非常に重要な違いです。

親がいない場合はどうなる?

では、GameObjectに親がない場合はどうでしょうか。

たとえば、

Hierarchy

Player

だけで、

Playerに親がない場合です。

この場合、

transform.position

と、

transform.localPosition

は同じ値になります。

Unity 6の公式ドキュメントにも、親がない場合は localPosition と position は同じになると記載されています。

つまり、

親なし

position
=
localPosition

です。

親を付けると意味が生まれる

localPosition が特に重要になるのは、

Parent
└ Child

という親子関係がある場合です。

たとえば、

Car
└ Camera

なら、

Cameraの localPosition は、

車から見てカメラがどこにあるか

を表します。

Player
└ Weapon

なら、

Weaponの localPosition は、

Playerから見て武器がどこにあるか

を表します。

今回なら、

Wall
└ BulletHole

なので、

BulletHoleの localPosition は、

Wallから見て弾痕がどこに付いているか

を表します。

弾痕ではlocalPositionの考え方がとても自然

弾痕について考えてみると、

世界のどこにあるかより、

壁のどこに付いているか

の方が重要です。

つまり、

BulletHole.position

よりも、

BulletHole.localPosition

の考え方が、弾痕と壁の関係を表すうえで自然です。

もちろん実際のRaycastでは、

hit.point

というワールド座標を使って最初の配置を行っています。

そのあとWallの子にすることで、

ワールド上の当たった位置
↓
親から見た相対位置

という関係が作られます。

SetParent(true)ともつながる

前回は、

bulletHole.transform.SetParent(
    hit.collider.transform,
    true
);

としました。

この true は、

現在のワールド位置を保ったまま親を変更する

という指定でした。

つまり最初に、

hit.point

を使って、

世界の正しい位置にBulletHoleを作ります。

そして、

SetParent(..., true)

によって、

見た目の位置を変えずにWallの子にします。

するとUnity側で、

その位置に対応する、

localPosition

が計算されます。

生成した弾痕をWallの子にするとHierarchyでBulletHoleがWallの下に入る

流れを整理すると

hit.point
↓
ワールド座標を取得

Instantiate
↓
ワールド座標へ弾痕を置く

SetParent(..., true)
↓
見た目の場所を保ったまま
Wallの子にする

localPosition
↓
Wallから見た相対位置が決まる

という流れです。

これまで学んだ内容がここでつながります。

実験用コード

次のようにすると、

position と localPosition を同時に確認できます。

using UnityEngine;
using UnityEngine.InputSystem;

public class BulletHoleShooter : MonoBehaviour
{
    [SerializeField]
    GameObject bulletHolePrefab;

    void Update()
    {
        if (Mouse.current.leftButton.wasPressedThisFrame)
        {
            Ray ray =
                Camera.main.ScreenPointToRay(
                    Mouse.current.position.value
                );

            RaycastHit hit;

            if (Physics.Raycast(ray, out hit))
            {
                Vector3 position =
                    hit.point
                    + hit.normal * 0.01f;

                Quaternion rotation =
                    Quaternion.LookRotation(
                        hit.normal
                    );

                GameObject bulletHole =
                    Instantiate(
                        bulletHolePrefab,
                        position,
                        rotation
                    );

                bulletHole.transform.SetParent(
                    hit.collider.transform,
                    true
                );

                Debug.Log(
                    "position = "
                    + bulletHole.transform.position
                );

                Debug.Log(
                    "localPosition = "
                    + bulletHole.transform.localPosition
                );
            }
        }
    }
}

弾痕を作ったあとに親Wallを動かして、再び値を確認してみると理解しやすいでしょう。

Inspectorでも確認できる

この違いはコードだけでなく、Inspectorでも確認できます。

たとえばHierarchyが、

Wall
└ BulletHole

なら、

BulletHoleを選択したときのTransformのPosition欄は、

基本的に、

親から見たローカル位置

として表示されます。

そのため、

コードの、

transform.localPosition

と対応させて見ると理解しやすくなります。

注意:単純な足し算とは限らない

先ほど、

親のposition
+
localPosition
=
子のposition

のように説明しました。

これは、

親に回転やScaleがない単純な例

では理解しやすい考え方です。

しかし実際には、親の、

Rotation
Scale

も子のワールド位置に影響します。

Unity公式ドキュメントでも、親Transformの回転やScaleが localPosition からワールド位置を計算するときに適用されると説明されています。

そのため、常に単純な、

position + localPosition

だけで求まるわけではありません。

初心者のうちは、

position
=
世界基準

localPosition
=
親基準

をまず理解しておけば十分です。

回転する親だとさらに違いが分かる

Wallを90度回転させてみると、

BulletHoleも一緒に回ります。

このとき、

bulletHole.transform.localPosition

は、

Wallを基準にした位置なので、

基本的な親子の位置関係を維持します。

しかし、

bulletHole.transform.position

は、

世界全体から見た位置なので変化します。

これによって、

localとは「自分または親を基準にした世界」

という感覚が見えてきます。

Wallを回転させても、子のBulletHoleも一緒に回転する

transform.positionはいつ使う?

たとえば、

敵が世界のどこにいるか知りたい

プレイヤーを特定の地点へ移動したい

Raycastのhit.pointへ移動したい

など、

シーン全体を基準に位置を扱いたい場合

には、

transform.position

が分かりやすいでしょう。

transform.localPositionはいつ使う?

一方、

武器を手の少し前へ置く

カメラを車体の後ろへ置く

UIやパーツを親基準で配置する

弾痕を壁の特定位置に保持する

など、

親との位置関係を扱いたい場合

には、

transform.localPosition

が重要になります。

まとめ

今回の違いは、とてもシンプルです。

transform.position

は、

世界から見た位置

です。

transform.localPosition

は、

親から見た位置

です。

たとえば、

Wall.position
= (10, 0, 0)

BulletHole.position
= (10.5, 1, 0)

BulletHole.localPosition
= (0.5, 1, 0)

という関係になります。

そしてWallを、

(20, 0, 0)

へ動かすと、

BulletHole.position
= (20.5, 1, 0)

BulletHole.localPosition
= (0.5, 1, 0)

となります。

つまり、

親が動く

↓

position
世界から見た場所なので変わる

localPosition
親から見た場所なので変わらない

ということです。

今回の Wall → BulletHole を通して考えると、

position と localPosition は別の位置を表しているのではなく、同じGameObjectの位置を「どこを基準に見るか」が違う

ということが分かります。

transform.positionとtransform.localPositionの違いのまとめ表


【シリーズ】Raycastで弾痕を作りながら学ぶ(6/9)
← 前の記事:弾痕を壁の子にする(SetParent) | シリーズ目次 | 次の記事:rotationとlocalRotationの違い →

訪問数 4 回, 今日の訪問数 4回

広告

Unity

Posted by hidepon