今回はNavMeshを使っていたら発生した謎の現象についてのお話です。
最終的には力技を使って、現象を発生しないようにしています。
一体なぜこんなことが……。
この記事にはUnity2017.2.0f3を使用しています。
現象の発生手順
では現象が発生した手順をみていきましょう。
まず段差のあるフィールドを作成しましょう。
二つのCubeメッシュを使用して簡単に作ります。
次にNavMeshを焼いて作成しましょう。
上の段と下の段が分かれるように設定する必要があります。
次にオブジェクトを生成する場所であるSpawnポイントを作成しましょう。
見た目で位置分かりやすいように、今回はSphereメッシュを付けていますが、Transform
があれば大丈夫です。
ここで重要な点として、Spawnポイントはフィールドの上の段と下の段に分けて設定する必要があります。
では次にSpawnさせるオブジェクトを作成しましょう。
オブジェクトには、NavMeshAgent
とNavMeshAgent
を制御するコンポーネントがついています。
NavController.cs
using UnityEngine; using UnityEngine.AI; [RequireComponent( typeof( NavMeshAgent ) )] public class NavController : MonoBehaviour { private NavMeshAgent m_navMeshAgent = null; private void Awake() { m_navMeshAgent = GetComponent<NavMeshAgent>(); } } // class NavController
NavController
はNavMeshAgent
を制御用のコンポーネントですが、現時点は特に制御はしていません。
簡単な作りです。
最後に、先ほど作成したオブジェクトをSpawnするコントローラーを作成しましょう。
GameController.cs
using UnityEngine; using System.Collections; public class GameController : MonoBehaviour { [SerializeField] private Transform[] m_points = null; [SerializeField] private float m_spawnTime = 1.0f; [SerializeField] private GameObject m_spawnObject = null; private int m_spawnIndex = 0; private void Start() { StartCoroutine( SpawnProcess() ); } private IEnumerator SpawnProcess() { while( true ) { yield return new WaitForSeconds( m_spawnTime ); SpawnObject(); } } private void SpawnObject() { if( m_spawnObject == null ) { return; } if( m_points == null || m_points.Length == 0 ) { return; } Transform point = m_points[ m_spawnIndex ]; m_spawnIndex = ( m_spawnIndex + 1 ) % m_points.Length; Instantiate( m_spawnObject, point.position, point.rotation ); } } // class GameController
コントローラーは一定時間ごとに、指定したポイントに順番にオブジェクトをSpawnさせる機能だけがあります。
準備が整ったので、実行してみましょう。
はーい、不具合が発生しましたよー。
下の段のSpawnポイントに、オブジェクトを生成しようとすると、Spawnポイントの場所ではないところに配置されてしまっています。
ちなみにこの現象、作成したNavMeshによっては、上の段に配置しようとしたオブジェクトが下の段に配置される場合もあります。
この現象の肝は、高さの異なるNavMeshに、ランタイムでオブジェクトを生成して配置しようとすると発生するみたいです。
というわけで担当プログラマーの方ー、このバグをアサインしますよー。
現象が発生しない方法A
残念ながらぼっちプロジェクトのため、報告担当も修正担当も僕がやる必要があるため原因を調査いてみましょう。
以下の場合にはこの不具合は発生しないことを確認しました。
まずNavMeshの焼き方を変えてみましょう。
フィールドの段差がNavMesh的に普通に登れる値にAgentHeightとStepHeightを調整しましょう。
段差の高さよりStepHeightの値が大きいことが特徴です。
さあ、実行しましょう。
これなら問題なく指定したSpawnポイントにオブジェクトを配置することができます。
ただやり方には問題しかありません。
「段差があると、なんか知らんけどオブジェクトが配置できないから、段差がある仕様やめようぜ!」
と言っているようなものです。
現象が発生しない方法B
NavMeshを元に戻しました。
次の方法は、SpawnするオブジェクトのNavMeshAgent
を無効化するやり方です。
自作コンポーネントがほとんど何もしていない以上、NavMeshAgent
が何かしら悪さをしているのは明白です。
NavMeshAgent
を無効化しました。
この状態で再度実行してみます。
うまく配置できました。
Spawnポイントは地面より若干上の方にあるので、オブジェクトは空中に配置されています。
ちなみにSpawnポイントを地面に同じ高さに設定しても現象は発生したので原因ではなさそうです。
もちろん、このままではダメです。
NavMeshAgent
を付けている理由は、NavMeshAgent
を使って移動処理を行うためです。
NavMeshAgent
を無効化してしまっては、そもそもNavMesh焼く必要すらなくなってしまいます。
現象が発生しない方法C
先ほどの方法に少し手を加えましょう。
オブジェクトのNavMeshAgent
を有効状態に戻します。
NavMeshAgent
の有効無効をコンポーネントで制御しましょう。
何もしていなかったNavController.cs
に仕事をさせます。
NavController.cs
private void Awake() { m_navMeshAgent = GetComponent<NavMeshAgent>(); m_navMeshAgent.enabled = false; } private void Start() { m_navMeshAgent.enabled = true; }
Awake()
でNavMeshAgent
のenabled
をfalse
にして、Start()
でNavMeshAgent
のenabled
をtrue
にして元に戻します。
では実行しましょう。
不具合の現象が発生しなくなりました!
今回、コンポーネントの処理順は設定していないため、状況によってはStart()
でNavMeshAgent
のenabled
を元に戻すタイミングが間に合わない可能性もありますが、少なくとも生成時にNavMeshAgent
を無効化し、のちに元に戻せば現象が発生しなくなることがわかりました。
Warp()を使ってみようよ
2017.12.14 追記
このようなご連絡をいただきましたよー。
ありがとうございます。
[https://twitter.com/sugikami1982/status/940987309310144514:embed#@urahimono_maigo ナビメッシュのオブジェクトって座標直接いれると移動できなかったような例 void OnEnable() { nav = GetComponent そうか、 試してみましょう。 NavController.cs
やった、現象が発生しなくなりましたよ。Warp()
関数がありましたよ!
確かにNavMeshAgent
が実行中にtransform
を直接いじるのはよくないですな。
docs.unity3d.comprivate void Awake()
{
m_navMeshAgent = GetComponent<NavMeshAgent>();
}
private void Start()
{
m_navMeshAgent.Warp( transform.position );
}
enabled
を切り替えるより処理がシンプルでいいですね。
ちなみにAwake()
でWarp()
を呼んだ場合はダメでした。お気を付けを。