Unityのtransform.localScaleとlossyScaleの違い|親のScaleは子にどう影響する?

広告

今回もWallとBulletHoleで考える

これまで、

transform.position
transform.localPosition

では、

position
= 世界から見た位置

localPosition
= 親から見た位置

と学びました。

さらに、

transform.rotation
transform.localRotation

では、

rotation
= 世界から見た向き

localRotation
= 親から見た向き

と学びました。

今回は、Transformの最後の基本要素である、

Scale

を見ていきます。

扱うのは、

transform.localScale

と、

transform.lossyScale

です。

これまでと同じように、

Wall
└ BulletHole

という親子関係を使います。

BulletHoleはWallの子なので、

Wallを移動すれば一緒に移動し、

Wallを回転すれば一緒に回転しました。

では、

Wallの大きさを2倍にしたらどうなるでしょうか?

親のWallと子のBulletHoleの親子関係

まずlocalScaleとは?

UnityのTransformには、

transform.localScale

があります。

これは、

親Transformに対する相対的なScale

です。

たとえば、

transform.localScale =
    new Vector3(1, 1, 1);

なら、

元の大きさ

です。

transform.localScale =
    new Vector3(2, 2, 2);

なら、

親から見て2倍

です。

Scaleの1とは?

TransformのScaleでは、

1

が基本値です。

たとえば、

Scale

X = 1
Y = 1
Z = 1

なら、

元の大きさです。

X = 2
Y = 2
Z = 2

なら、

各方向へ2倍です。

UnityのTransformマニュアルでも、Scaleの値1は元のサイズを表します。

WallのScaleを2倍にする

たとえばWallが、

wall.transform.localScale =
    new Vector3(2, 2, 2);

になったとします。

Hierarchyは、

Wall
└ BulletHole

です。

BulletHoleはWallの子なので、

WallのScaleの影響を受けます。

つまり、

Wallが2倍
↓
BulletHoleも一緒に拡大される

ということが起きます。

BulletHoleのlocalScaleが1でも大きくなる

ここが重要です。

BulletHole自身は、

bulletHole.transform.localScale

が、

(1, 1, 1)

だったとします。

それでも親Wallが、

(2, 2, 2)

なら、

BulletHoleは世界から見ると、

おおよそ2倍の大きさになります。

つまり、

BulletHole.localScale
= (1, 1, 1)

だからといって、

世界から見ても1倍とは限らない

ということです。

そこでlossyScaleが登場する

世界側から見たScaleを確認したい場合に使えるのが、

transform.lossyScale

です。

Unity 6では lossyScale は、

オブジェクトのグローバルScaleを表す読み取り専用プロパティ

です。

たとえば、

Debug.Log(
    bulletHole.transform.lossyScale
);

とすれば、

世界から見た実効的なScaleを確認できます。

数字で比較してみる

たとえば、

Wall.localScale
= (2, 2, 2)

BulletHole.localScale
= (1, 1, 1)

だったとします。

単純な親子関係なら、

BulletHoleの、

bulletHole.transform.lossyScale

は、

(2, 2, 2)

になります。

つまり、

localScale
= 親から見た倍率

lossyScale
= 世界から見た実効的な倍率

です。

親と子のScaleは掛け合わされる

さらに、

Wall.localScale
= (2, 2, 2)

BulletHole.localScale
= (0.5, 0.5, 0.5)

だったとします。

単純なケースでは、

2 × 0.5
=
1

なので、

BulletHoleの、

transform.lossyScale

は、

(1, 1, 1)

程度になります。

つまり、

親で2倍
子で0.5倍
↓
世界から見ると1倍

です。

positionとの違いも見えてくる

親のScaleを変えると、

子の大きさだけでなく位置にも影響します。

たとえば、

Wall

└ BulletHole
   localPosition = (1, 0, 0)

だったとします。

WallのScaleを、

(1, 1, 1)

から、

(2, 2, 2)

へ変更すると、

BulletHoleは親から見た、

localPosition = (1, 0, 0)

を保っていても、

世界から見た距離は広がります。

UnityのTransformマニュアルでも、親のScale変更は子のTransform位置に影響すると説明されています。

図で考えてみる

最初は、

Wall Scale = 1

[Wall]---● BulletHole
          ↑
    localPosition = 1

だとします。

Wallを2倍にすると、

Wall Scale = 2

[   Wall   ]------● BulletHole
                   ↑
             localPosition = 1

のようになります。

BulletHoleの、

localPosition

は変わっていなくても、

世界での位置は変化します。

親のScaleが1と2のとき、localPositionが同じでも世界での距離が変わる

localScaleは書き換えられる

localScale は、

設定できます。

たとえば、

bulletHole.transform.localScale =
    new Vector3(
        0.5f,
        0.5f,
        0.5f
    );

とすれば、

親から見て半分の大きさになります。

子のlocalScaleだけを0.5倍にすると、lossyScaleも(0.5, 0.5, 0.5)になる

lossyScaleは読み取り専用

一方、

transform.lossyScale

は、

読み取り専用

です。

そのため、

transform.lossyScale =
    new Vector3(2, 2, 2);

のようには書けません。

lossyScale は、

今、世界から見ると実際にどのくらいのScaleになっているのか?

を確認するために使います。

なぜworldScaleというプロパティではないの?

ここは面白いところです。

位置には、

position

回転には、

rotation

があります。

そのため、

Scaleにも、

scale

のようなワールド用プロパティがありそうに感じます。

しかしUnityでは、

lossyScale

という名前になっています。

lossyとは?

lossy は、

大まかには、

完全な情報を保持できない

という意味です。

なぜこの名前なのでしょう。

Scaleは親子関係が単純なら、

親Scale × 子Scale

で考えやすいです。

しかし、

親に、

非一様Scale

があり、

さらに子や親に、

回転

が加わると、

Transformが歪んだ状態になることがあります。

非一様Scaleとは?

たとえば、

(2, 1, 1)

のように、

Xだけ2倍、

YとZは1倍、

というScaleです。

これを、

非一様Scale

と考えます。

一方、

(2, 2, 2)

なら、

すべて同じ倍率なので、

一様Scaleです。

親のScaleがXだけ2倍のとき、lossyScaleは(2, 1, 1)になる

親の非一様Scaleと回転が組み合わさると?

たとえばWallが、

Scale
(2, 1, 1)

で、

さらに回転しているとします。

その子BulletHoleも別方向へ回転している場合、

世界側では、

単純な、

X倍率
Y倍率
Z倍率

だけでは正確に表しづらい、

歪み

が発生する場合があります。

Unity 6の公式ドキュメントでも、親のScaleと子の回転の組み合わせによってScaleがskewし、3成分のVectorだけでは完全に表現できない場合があると説明されています。

だからlossyScale

そのような場合、

transform.lossyScale

は、

世界での実際のScaleに、

できるだけ近い値

をVector3として返します。

そのため、

完全なworldScale

ではなく、

lossyScale

という名前になっています。

初心者のうちはどう考えればよい?

最初は、

localScale
=
親から見たScale

lossyScale
=
世界から見た実効Scale

と覚えておけば十分です。

そして、

lossyScaleは読み取り専用

も押さえておきましょう。

実際にConsoleへ表示してみる

BulletHole生成後に、

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

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

を追加します。

実験例1:親が1倍

Wallが、

localScale
(1, 1, 1)

BulletHoleが、

localScale
(1, 1, 1)

なら、

BulletHole.localScale
(1, 1, 1)

BulletHole.lossyScale
(1, 1, 1)

になります。

WallもBulletHoleもScaleが1のとき、localScaleもlossyScaleも(1, 1, 1)

実験例2:親を2倍

Wallを、

localScale
(2, 2, 2)

にします。

BulletHoleは、

localScale
(1, 1, 1)

のままです。

すると、

BulletHole.localScale
(1, 1, 1)

BulletHole.lossyScale
(2, 2, 2)

となります。

ここを見ると、

違いが一気に分かります。

親のScaleを2倍にすると、子のlocalScaleは(1, 1, 1)のままlossyScaleは(2, 2, 2)になる

実験例3:子を0.5倍

さらにBulletHoleを、

localScale
(0.5, 0.5, 0.5)

にします。

親Wallが、

(2, 2, 2)

なら、

単純なケースでは、

BulletHole.lossyScale
(1, 1, 1)

となります。

つまり、

親2倍
×
子0.5倍
=
世界では1倍

です。

親が2倍で子が0.5倍のとき、lossyScaleは(1, 1, 1)になる

弾痕では困ることがある

今回のBulletHoleを考えてみましょう。

Wallを後から、

2倍

にすると、

BulletHoleまで2倍になってしまいます。

しかしゲームによっては、

壁が大きくなっても弾痕のサイズは一定にしたい

こともあります。

この場合、

親のScaleの影響を考える必要があります。

なぜ子は親のScaleの影響を受けるの?

Unityの親子関係は、

Position
Rotation
Scale

を含めたTransformの関係だからです。

つまり、

親が移動
↓
子も移動

親が回転
↓
子も回転

親が拡大
↓
子も拡大

となります。

これで、

Transformの親子関係が一通りつながります。

これまでの3つを並べる

Position

position
= 世界から見た位置

localPosition
= 親から見た位置

Rotation

rotation
= 世界から見た向き

localRotation
= 親から見た向き

Scale

lossyScale
= 世界から見た実効Scale

localScale
= 親から見たScale

です。

ただしScaleだけ少し違う

PositionとRotationは、

transform.position
transform.rotation

のように、

ワールド値を直接設定できます。

しかしScaleでは、

transform.lossyScale

は読み取り専用です。

つまり、

position
↔ localPosition

rotation
↔ localRotation

ほど完全な対称ではありません。

ここはScale特有のポイントです。

実験用コード

これまでのBulletHoleコードに、

Scaleの表示を追加すると次のようになります。

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(
                    "localScale = "
                    + bulletHole.transform.localScale
                );

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

この状態で、

親WallのScaleを、

(1, 1, 1)

↓

(2, 2, 2)

↓

(2, 1, 1)

などに変えて、

Consoleの値を比較してみましょう。

localScaleとlossyScaleをConsoleに表示するコードと出力例

Inspectorで見えるScaleは?

Hierarchyが、

Wall
└ BulletHole

となっている場合、

BulletHoleのInspectorに表示されるScaleは、

基本的に、

transform.localScale

に対応する値です。

つまりInspectorで、

Scale
X 1
Y 1
Z 1

と表示されていても、

世界から見たScaleが、

(1, 1, 1)

とは限りません。

親のScaleが影響しているからです。

ここは初心者が混乱しやすい

Inspectorで、

BulletHole

Scale
1, 1, 1

なのに、

画面では2倍に見える。

これはバグではありません。

親Wallが、

Scale
2, 2, 2

だからです。

つまり、

InspectorのScale
=
localScale

と考えると理解しやすくなります。

親がない場合は?

親がないGameObjectなら、

localScale

がそのまま世界でのScaleになります。

たとえば、

Wall

localScale
(2, 2, 2)

で、

親がなければ、

lossyScale
(2, 2, 2)

となります。

Transformの3要素がつながった

ここまでで、

Transformの基本である、

Position
Rotation
Scale

が一通りつながりました。

まとめると、

項目 ワールド側 親基準
位置 position localPosition
回転 rotation localRotation
Scale lossyScale ※読み取り専用 localScale

です。

position・rotation・lossyScaleと、localPosition・localRotation・localScaleの違いのまとめ

「local」が付くものは親基準

今回までをまとめると、

localPosition
localRotation
localScale

はいずれも、

親を基準にしたTransform値

です。

一方、

position
rotation
lossyScale

は、

世界側から見た状態を確認するために使います。

まとめ

transform.localScale は、

親から見たScale

です。

transform.localScale

は読み書きできます。

一方、

transform.lossyScale

は、

世界から見た実効的なScale

です。

ただし、

読み取り専用

です。

さらに、

親に回転や非一様Scaleがあると、

単純なXYZの3つの倍率では世界側の変形を完全には表せない場合があります。

そのためUnityでは、

worldScale

ではなく、

lossyScale

という名前になっています。

今回までを一言でまとめると、

position
rotation
lossyScale
↓
世界側

localPosition
localRotation
localScale
↓
親側

です。

これで、

Position・Rotation・Scaleと親子関係の基本

が一通りつながりました。


【シリーズ】Raycastで弾痕を作りながら学ぶ(8/9)
← 前の記事:rotationとlocalRotationの違い | シリーズ目次 | 次の記事:Transformとは結局何なのか(総まとめ) →

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

広告

Unity

Posted by hidepon