【Unity】Colliderのboundsを使用する上での注意点


はじめに

unityroom で開催された「Unity 1 週間ゲームジャム」に参加し、図形の中心をクリックするというゲームを作りました。
そのゲームで使用した Collider の Bounds を使用するうえでつまずいた点をまとめてみました。

Bounds とは

Bounds とは Unity 側で用意されている 3 次元空間の範囲を表現する構造体です。
Collider,Mesh,Renderer に使用されており、バウンディングボックス(AABB)を表現するために使用されています。
簡単にいうと Rect の 3D 版のような構造体です。

UnityEngine.Bounds - Unity スクリプトリファレンス

今回は Collider の Bounds を使用しましたが、Mesh や Renderer もあまり変わらないと思います。
また、Collider2D のような 2D 系のコンポーネントでも Bounds が使用されています。(この場合スケールの z は 0 になります)

本題

まず、以下のコードを見てみましょう。
以下のコードはオブジェクトを生成してランダムな回転、スケーリングを行い、生成したオブジェクトの Bounds を使って何か処理を行うコードです。

private GameObject prefab;

private void Start()
{
    Generate();
}

/// <summary>
/// オブジェクトの生成
/// </summary>
private void Generate()
{
    GameObject generatedObject = Instantiate(prefab);
    Transform transComp = generatedObject.transform;
    // ランダムな回転とスケールを適用
    float randomRad = Random.Range(0.5f, 1.5f);
    transComp.localScale = new Vector2(randomRad, randomRad);
    transComp.rotation = Quaternion.Euler(new Vector3(0, 0, Random.value * 360));
    // Boundsを取得
    Bounds generatedObjBounds = generatedObject.GetComponent<Collider2D>().bounds;

    // ここにBoundsを使った処理
}

一見すると特に問題ないように見えますがgeneratedObjBoundsで取得されるのは回転とスケールが適用される"前"の Bounds が取得されます。
つまりここで行われる処理は以下のコードと同じ処理になってしまいます。

/// <summary>
/// オブジェクトの生成
/// </summary>
private void Generate()
{
    GameObject generatedObject = Instantiate(prefab);
    // Boundsを取得
    Bounds generatedObjBounds = generatedObject.GetComponent<Collider2D>().bounds;

    // ここにBoundsを使った処理
}

おそらく Bounds は Unity の物理処理で更新される関係上 1 フレーム経たないと
トランスフォーム適用後の bounds が取得できないのではないかと思います。

イベント関数の実行順序 - Unity マニュアル

そのためフラグなどを駆使するか、非同期処理を使用するなどして取得タイミングをずらす必要があります。

private GameObject prefab;

private void Start()
{
    // コルーチンにする
    StartCoroutine(Generate());
}

/// <summary>
/// オブジェクトの生成
/// </summary>
private IEnumerator Generate()
{
    GameObject generatedObject = Instantiate(prefab);
    Transform transComp = generatedObject.transform;
    // ランダムな回転とスケールを適用
    float randomRad = Random.Range(0.5f, 1.5f);
    transComp.localScale = new Vector2(randomRad, randomRad);
    transComp.rotation = Quaternion.Euler(new Vector3(0, 0, Random.value * 360));

    // Boundsが再計算されるまで少し待つ
    yield return new WaitForSeconds(0.1f);

    // Boundsを取得
    Bounds generatedObjBounds = generatedObject.GetComponent<Collider2D>().bounds;

    // ここにBoundsを使った処理
}

ここではコルーチンを使用しています。(というかコルーチンのほうがシンプルで簡単)
上で 1 フレーム経ってからと言っていますが、正直挙動が怪しいので確実に取得するために 0.1 秒程度待ったほうが確実だと思います。

また、SetActive(false)でオブジェクトが非アクティブになっていても再計算されないため注意が必要です。

さいごに

よかったら遊んでみてください
Center | フリーゲーム投稿サイト unityroom