環境
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をつけました。
このような検証コードを書きました。
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
が重要なことがわかりました。
意図した挙動のときのログ
putはオブジェクトを生成したとき,
「座標」と「最後の1」はレイキャストの接触したオブジェクトの座標とヒット数ですが、これは次のゲームフレームで呼ばれています。
その途中で「FixedUpdate」が呼ばれています。
FixedUpdateについては他の記事に参考にしてほしいのですが、物理演算の前に呼ばれるものであり、ゲームのフレームレートとは無関係に呼ばれます。
意図しない挙動のときのログ
一方 Unityが一時停止したとき (レイキャストが反応しなかったとき)はputの直後に0 (レイキャストがヒットしなかった)が出力されています。
このときに「FixedUpdate」が途中で呼ばれていないことがわかります。
つまり、物理演算がされていないことがわかります。
一時停止するときは、すべてこうなっていたため、これが原因だと言えそうです。
Instantiateの引数で座標を与えたとき
AをコメントアウトしてBだけを使うようにしてみると、つまり下のコードを使う
obj = Instantiate(cube, new Vector3(1, 1, 0), Quaternion.identity);
Unityが一時停止することはありませんでした。
ログを見ると 「FixedUpdate」 が挟まっていないこともありましたが ちゃんと1つ見つかっています。
つまり、物理演算が挟まっていなくても大丈夫のようです。
同じゲームフレーム内でやってみると
先程はあえて別のゲームフレームでやっていましたが、
同じフレームでやってみました。
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が呼ばれない状態だった場合、
何故かたまにバグが起こるみたいになってしまうようです。