4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Unityで、Instantiateの「引数で座標を指定する」のと「戻り値を使って設定する」のは挙動が違う可能性がある

Last updated at Posted at 2020-07-19

環境

Mac , Unity 2019.4.2f1で実験しました。

#結論から言うと
Instantiateで生成と同時に座標を設定したほうが良い

var obj = Instantiate(cube, new Vector3(1, 1, 0), Quaternion.identity);

生成と同時に座標を設定する場合はこの2つの方法が考えられます

引数に指定
var obj = Instantiate(cube, new Vector3(1, 1, 0), Quaternion.identity);
戻り値で指定
var obj = Instantiate(cube);
obj.transform.position = new Vector3(1, 1, 0);

とくにInstantiateは引数の組み合わせが多く、(何故か座標だけで設定できない)
第3引数にQuaternion.identityも設定するか、
Instantiateの戻り値を使って設定することになります。

後者のほうが見慣れないQuaternionを使わなくてもいいので特に初心者はわかりやすいと思ってしまうかもしれません。

同じフレーム内で設定しているはずなのでどちらも同じように見えますが実はこの2つは挙動が変わってしまうときがあります。

検証

適当に3Dオブジェクトを作り、Rigidbodyをつけました。

image.png

image.png

このような検証コードを書きました。

検証コード
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameController : MonoBehaviour
{
    public GameObject cube;


    private GameObject obj;

    // Update is called once per frame
    void Update()
    {
        if (obj != null)
        {
            RaycastHit hit;
            var raycastHits = Physics.RaycastAll(new Vector3(1, 10, 0), new Vector3(0, -1, 0));
            foreach (var raycastHit in raycastHits)
            {
                Debug.Log(raycastHit.collider.gameObject.transform.position);
            }

            Debug.Log(raycastHits.Length);

            if (raycastHits.Length == 0)
            {
                // レイキャストにヒットしなかった
                Debug.Break();
            }


            Destroy(obj);
            obj = null;
        }
        else
        {
            if (Input.GetKey(KeyCode.Space))
            {

                // A
                obj = Instantiate(cube);
                obj.transform.position = new Vector3(1, 1, 0);

                // B
                // obj = Instantiate(cube, new Vector3(1, 1, 0), Quaternion.identity);
                
                Debug.Log("put");

            }
        }
    }

    private void FixedUpdate()
    {
        Debug.Log("FixedUpdate");
    }
}

スペースキーを押したらオブジェクトを生成して、

(1, 1, 0) にオブジェクト生成され、生成されて次のゲームフレームで
Physics.RaycastAllで (1,10,0) から (0, -1, 0)方向に向かってRayを投げてます。

(1, 1, 0)に生成されているのであれば当たるはずです。

レイキャストがヒットしない?

Aとなっている方(上のコードそのまま)を動かしたら
スペースを押しっぱなしにしたときにいずれUnityが一時停止すると思います。
(一時停止をするのは Debug.break() の影響)

raycastHits.Length == 0 になっている つまりレイキャストがヒットしなかったということになります。

つまり、 (1,1,0) にオブジェクトがいないみたいです。  
ちなみにobj.transform.position を見てみると (1,1,0)には、いるみたいです。

すぐ止まるときと止まらないときの差を調べてみると、上のコードでもしれっと書いてあるFixedUpdate が重要なことがわかりました。

意図した挙動のときのログ

image.png

putはオブジェクトを生成したとき,

「座標」と「最後の1」はレイキャストの接触したオブジェクトの座標とヒット数ですが、これは次のゲームフレームで呼ばれています。

その途中で「FixedUpdate」が呼ばれています。

FixedUpdateについては他の記事に参考にしてほしいのですが、物理演算の前に呼ばれるものであり、ゲームのフレームレートとは無関係に呼ばれます。

意図しない挙動のときのログ

一方 Unityが一時停止したとき (レイキャストが反応しなかったとき)はputの直後に0 (レイキャストがヒットしなかった)が出力されています。

image.png

このときに「FixedUpdate」が途中で呼ばれていないことがわかります。

つまり、物理演算がされていないことがわかります。

一時停止するときは、すべてこうなっていたため、これが原因だと言えそうです。

Instantiateの引数で座標を与えたとき

AをコメントアウトしてBだけを使うようにしてみると、つまり下のコードを使う

obj = Instantiate(cube, new Vector3(1, 1, 0), Quaternion.identity);

Unityが一時停止することはありませんでした。

ログを見ると 「FixedUpdate」 が挟まっていないこともありましたが ちゃんと1つ見つかっています。
image.png

つまり、物理演算が挟まっていなくても大丈夫のようです。

同じゲームフレーム内でやってみると

先程はあえて別のゲームフレームでやっていましたが、
同じフレームでやってみました。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameController : MonoBehaviour
{
    public GameObject cube;


    private GameObject obj;

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKey(KeyCode.Space))
        {

            // A
            obj = Instantiate(cube);
            obj.transform.position = new Vector3(1, 1, 0);
            Debug.Log("put");

            // B
            // obj = Instantiate(cube, new Vector3(1, 1, 0), Quaternion.identity);

            var raycastHits = Physics.RaycastAll(new Vector3(1, 10, 0), new Vector3(0, -1, 0));
            foreach (var raycastHit in raycastHits)
            {
                Debug.Log(raycastHit.collider.gameObject.transform.position);
            }

            if (raycastHits.Length == 0)
            {
                Debug.Break();
            }

            Destroy(obj);
        }
    }

}

するとAのときは、止まらなかったのに対して、 Bの方はほぼ確実止まるような感じでした。

結論(予想)

  • Instantiateの引数で座標を与えたときは、その瞬間に処理がされるのか 物理演算に認識される状態になります。

  • 座標を与えない場合(0,0,0) に置かれ (0,0,0)としては認識する模様。
    その後すぐに

obj.transform.position = new Vector3(1, 1, 0)

としても変数上では座標は移動しても物理演算的には移動していない。

Updateの後に「FixedUpdate」が呼ばれた場合の物理演算処理で初めて、その座標で認識される状態になる。

そのため、タイミングが悪くFixedUpdateが呼ばれない状態だった場合、
何故かたまにバグが起こるみたいになってしまうようです。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?