This document is about: QUANTUM 3
SWITCH TO

ナビメッシュエージェントの作成

ナビメッシュエージェント機能は、複数のコンポーネントに分けられています。

  • NavMeshPathfinder:マルチスレッドで経路探索を実行し、ウェイポイントと進行状況を保存します
  • NavMeshSteeringAgent:エージェントのステアリング(速度・加速度・回転速度・ブレーキの計算)を実行します
  • NavMeshAvoidanceAgent:エージェント同士が相互に回避できるようにします
  • NavMeshAvoidanceObstacle:エンティティをNavMeshAvoidanceAgentの回避対象にします

エージェントコンポーネントは、Transform2D/Transform3Dコンポーネントのいずれかで動作します。

すべてのコンポーネントは、一つのNavMeshAgentConfigコンフィグアセットを共有します。

これらは様々なユースケースに応じて、組み合わせることが可能です。

完全 Pathfinder + SteeringAgent + AvoidanceAgent 3つすべてのコンポーネントを持つエンティティは、経路を探索し、それに沿って移動し、動的に他のエージェントを回避できます。
回避なし Pathfinder + SteeringAgent 回避コードは実行されず、コンポーネントは回避関連のデータを保持しません。

SimulationConfig.Navigation.EnableAvoidanceを無効にすると、CPU時間を節約できます。
カスタム移動 Pathfinder + SteeringAgent エージェントは経路探索と経路の保存が可能ですが、独自システムで移動シグナルISignalOnNavMeshMoveAgentを実装することで、ステアリングは無効化されます。(注意:エージェントが実際にターゲットを持つ場合のみコールバックが実行されます)

NavMeshAgentConfig.MovementTypeCallbackに設定します。

SimulationConfig.Navigation.EnableNavigationCallbacksを有効にする必要があります。
回避ありのカスタム移動 Pathfinder + SteeringAgent + AvoidanceAgent Custom movement要件に加えて、ISignalOnNavMeshMoveAgentdesiredDirectionパラメーターには、回避行動後の移動方向が含まれます。
経路探索のみ Pathfinder エージェントは経路探索とウェイポイント進行状況の保存を実行します。ステアリング・回避・移動は、独自システムで処理されます。

Custom movementの設定が必要です。

ステアリングコンポーネントなしでウェイポイント進行状況を機能させるには、エージェントがウェイポイントに接近する速度情報が必要です。NavMeshPathfinder.WaypointDetectionDistanceSqrは毎フレーム設定する必要があります。

エンティティプロトタイプによるエージェントの作成

チュートリアルでは、ベイク済みQuantumナビメッシュを含むUnityシーンを前提にしています。(Unityナビメッシュのインポートをご覧ください)

  • GameObject > Quantum > Empty Entityから、シーン上にQuantumプロトタイプを作成する
  • QuantumEntityPrototypeを選択して、Transform2Dに設定する
  • QuantumEntityPrototypeを選択して、NavMeshPathfinderを有効にする
    • NavMeshPathfinder.Initial Targetを展開して、ワールド座標Positionを設定する
    • Initial TargetNavMeshで、ベイク済みQuantumナビメッシュアセットを選択する
    • 任意でNavMeshAgentConfigアセットを割り当てるか、デフォルトのコンフィグを使用する
  • QuantumEntityPrototypeを選択して、NavMeshSteeringAgentを有効にする
  • ゲームオブジェクトを右クリック > 3D Object > Capsuleから、Unityビューにゲームオブジェクトを追加して、カプセルを1単位上に動かす
  • Open the Quantum Gizmosオーバーレイメニューを開き、NavMesh Pathfinderを切り替える
  • Playを押す
Navmesh Agent Prototype

コードによるエージェントの作成

チュートリアルでは、ベイク済みQuantumナビメッシュを含むUnityシーンを前提にしています。(Unityナビメッシュのインポートをご覧ください)

新しいQuantumシステムを作成するためには、QuantumUser > Simulationフォルダーを右クリックし、Create > Quantum > Systemを選択します。スクリプトとクラス名をNavMeshAgentTestSystemに変更して、NavMeshAgentTestSystemアセットを開き、DefaultSystemsConfig.Entriesに作成したシステムを追加してください。

Quantum Gizmosオーバーレイメニューを開き、NavMesh Pathfinderを有効にして、再生中にエージェントのギズモが表示されるようにします。Unityのプレハブを作成するには、Viewコンポーネントを追加する必要があります。(この例では説明しません)

Navmesh Agent Code

以下のシステムをコピーして、OnInit()内でエージェントを作成してターゲット位置を設定します。

C#

namespace Quantum
{
  using Photon.Deterministic;
  using UnityEngine.Scripting;

  [Preserve]
  public unsafe class NavMeshAgentTestSystem : SystemMainThread
  {
    public override void OnInit(Frame frame)
    {
      base.OnInit(frame);

      var entity = frame.Create();

      // Transform3D(または2D)コンポーネントを追加する
      frame.Set(entity, new Transform3D()
      {
        Position = FPVector3.Zero,
        Rotation = FPQuaternion.Identity
      });

      // ファクトリーメソッドを使用してPathfinderコンポーネントを作成し、任意でNavMeshAgentConfigを渡す
      var pathfinder = NavMeshPathfinder.Create(frame, entity, null);

      // 名前からナビメッシュを探し、コンポーネントに追加する前にターゲットを設定する
      var navmesh = frame.Map.NavMeshes["Navmesh"];
      pathfinder.SetTarget(frame, new FPVector3(12, 0, 0), navmesh);

      // pathfinderとステアリングコンポーネントをエンティティに追加する
      frame.Set(entity, pathfinder);
      frame.Set(entity, new NavMeshSteeringAgent());
    }

    public override void Update(Frame frame)
    {
    }
  }
}

NavMeshPathfinderは、経路の生成・ウェイポイントの保存・ウェイポイント進行を実行する主要なコンポーネントです。

このコンポーネントで経路を生成して移動するには、SetTarget()を呼び出す必要があります。入力されたTarget位置は保存され、微修正が可能なInternalTargetも作成されます。

ターゲットを設定すると、エージェントはIsActiveになります。ターゲットに到達すると、自動的に非アクティブ化されます。

ウェイポイントはGetWaypoint()から確認できます。ウェイポイント数はWaypointCountから、現在のウェイポイントはWaypointIndexから取得できます。

エージェントを即時停止するには、Stop()が使用できます。

NavMeshPathfinder.SetConfig()は、コンポーネント作成時または実行時に呼び出し可能です。現在のエージェントが経路を辿っていて、新しいコンフィグのウェイポイント数が異なる場合、その経路はリセットされます。コンフィグはエンティティのNavMeshSteeringAgentNavMeshAvoidanceAgentコンポーネントで自動的に更新され、SpeedAccelerationAvoidancePriorityLayerMaskは、コンフィグ値にリセットされます。

UpdateInterval パフォーマンス最適化のため、ティックごとに経路探索や回避を実行しないように、エージェントを調整できます。

UpdateInterval1より大きい値に設定すると、更新頻度が減少します。これによって、エージェントの応答性は低下しますが、CPU時間を節約できます。エージェントのエンティティのインデックスを使用して正確な更新頻度を定義するため、すべてのエンティティが同じティックで更新されることはありません。

計算式は次の通りです。
updateAgent = entity.Index % agentConfig.UpdateInterval == f.Number % agentConfig.UpdateInterval

1 = 毎ティック更新
2 = 2ティックごとに更新
8 = 8ティックごとに更新
PathQuality A*のヒューリスティック関数を変更して、経路の品質に反映します。Goodは、品質とパフォーマンスとの最適なバランスを取ります。

Fast - 親Gとマンハッタン距離を使用します。
Good - ゴールへの進入エッジ上にピボットを作成し、Gとマンハッタン距離を再計算します。
Best - ゴールへの進入エッジ上にピボットを作成し、スタートへの別のピボットのGとユークリッド距離を再計算します。
CachedWaypointCount NavMeshPathfinderにキャッシュされるウェイポイント数を調整します。

コンポーネントに保存できるウェイポイント数には制限があります。これは、一時的ではないデータ量を増やすとシミュレーション速度が低下するためです。

エージェントが最後のウェイポイントに向かって移動を開始すると、自動的に経路探索が再実行され、ウェイポイントを更新するための新しい経路が計算されます。

キャッシュに保存される最初のウェイポイントは、SetTarget()が呼び出された際のエージェントの現在位置で、ウェイポイント到達判定精度を高めるために使用されます。
MaxRepathTimeout タイムアウト時間(秒)で、この時間までにウェイポイントに到達しなかった場合、新しい経路探索がトリガーされます。エージェントがスタックする現象を軽減するためのフェイルセーフ機能です。無効化するには値を0に設定してください。
LineOfSightFunneling 有効にすると、視線判定を使用してウェイポイントが削除されます。

このオプションは、ナビメッシュのリージョンがメインのナビメッシュの中央にある場合(例:破壊可能オブジェクト)に有効にする必要があります。リージョンによって追加される三角形は、アクティブなリージョン付近で不自然な経路を生み出すことがあります。
DynamicLineOfSight 有効にすると、エージェントはウェイポイントをスキップ可能かどうか毎ティック確認します。このオプションは負荷が高いですが、経路上の不要なウェイポイントをすべて削除します。
DynamicLineOfSightWaypointRange これはDynamicLineOfSightと似ていますが、エージェントがウェイポイントの特定範囲内にいる場合のみトリガーされます。

0に設定すると無効化されます。
AutomaticTargetCorrection 無効化すると、ナビメッシュ外の位置にいる際にSetTarget()が失敗する可能性があります。

3Dナビメッシュでは絶対に無効化しないでください。
AutomaticTargetCorrectionRadius ターゲット周辺の有効なナビメッシュを検索する範囲です。

この値は開始位置の補正にも使用され、使用しない場合は半径0.25の許容範囲が使用されます。

以下の画像では、ターゲットの黄色のXについて、補正半径がナビメッシュ上の最も近い有効な位置を見つけて、Input Targetを変更せずに内部ターゲットを補正します。

3Dナビメッシュでは絶対に0に設定しないでください。デフォルトは1です。

半径を不必要に大きくしすぎると、非常に負荷がかかる可能性があります。
Auto correction screenshot
EnableWaypointDetection この機能によって、エージェントがウェイポイントに到達しにくい状況(例:回転速度や回避が遅い)が緩和されます。

パラメーターのAxis ExtendAxis Offsetは、ウェイポイント到達判定軸(黒線)を定義します。エージェントが黄色ゾーンに入ると、ウェイポイントに到達したとみなされます。
Navmesh Agent Waypoint Reached Detection Axis
ウェイポイント到達判定軸
DefaultWaypointDetectionDistance エージェントがNavMeshSteeringAgentコンポーネントを持たない場合、この値はウェイポイント到達判定に使用され、エージェントのmax speed * delta timeに設定する必要があります。

この値は、各ティックでWaypointDetectionDistanceSqrを直接設定する際には使用されません。

NavMeshSteeringAgentコンポーネントの使用は任意です。このコンポーネントはNavMeshPathfinderを必要とします。

AccelerationMaxSpeedは、実行時に変更できます。初期値はコンフィグから設定されます。

回転速度と加速度を0に設定すると、無効化することができます。

CurrentSpeedVelocityはコンポーネントから取得できます。

MovementType エージェントの移動は、transformに直接適用するか、移動コールバックを実行するカスタム移動ロジックを実装できます。

None - 移動は適用されません。
Transform - 移動は、エンティティのTransform2D/Transform3Dコンポーネントに適用されます。
Callback - 移動は適用されませんが、ISignalOnNavMeshMoveAgentシグナルが実行されます。
VerticalPositioning このオプションはTransform3D使用時のみ有効です。エージェントのY座標の計算方法を定義します。

None - 垂直方向の位置は適用されません。
Navmesh - ナビメッシュにレイキャストします。Navmeshはデフォルト値ですが、キャラクターの移動に対してナビメッシュのジオメトリは単純すぎるという欠点があります。
Physics - 3D物理ジオメトリにレイキャストします。Physicsを使用するには、Quantumコライダーで構成された歩行可能な地面が存在する必要があります。
Speed エージェントの最高速度です。

実行時にSpeedを変更するには、NavMeshSteeringAgent.MaxSpeedを使用してください。
AngularSpeed エージェントの角速度(ラジアン/秒)です。

0に設定すると、エージェントの回転が無効になります。
200以上に設定すると、回転が即時反映されるようになります。
Acceleration エージェントの加速度です。

0に設定すると、加速度が無効になります。
StoppingDistance エージェントがターゲット手前で停止するようになる距離で、オーバーシュートを回避して安定性を高めます。残距離がエージェントのティックあたりの移動距離未満になった場合、エージェントは停止します。
AutoBraking 有効にすると、目標に近づいたエージェントは減速を開始します。
AutoBrakingDistance エージェントが減速を開始するまでのターゲットへの距離です。
ClampAgentToNavmesh MovementTypeTransformの場合のみ選択可能です。

有効にすると、物理コライダーと同様に、エージェントがナビメッシュ領域外から押し出されるようになります。エージェントは回避時などにナビメッシュ領域外に逸れることがあります。
ClampAgentToNavmeshCorrection 各ティックでエージェントが補正される割合(パーセント)です。

NavMeshAvoidanceAgentコンポーネントを正しく動作させるためには、NavMeshPathfinderNavMeshSteeringAgentコンポーネントが必要です。どちらもこのコンポーネントより先にエンティティにSet()する必要があります。

エージェントは、優先度とマスク・レイヤーのフィルタリングを使用して、他の移動エージェント(HRVO)を回避するための計算を実行します。NavMeshAgentConfigから優先度・マスク・レイヤーの初期値が設定されますが、実行時にコンポーネント上で変更可能です。

詳細は「エージェントの回避」セクションをご覧ください。

ナビメッシュエージェントのコールバック

すべてのエージェントのコールバックはメインスレッドから呼び出されるため、他のコンポーネントやエンティティへの読み書き時にマルチスレッドの問題は発生しません。

ナビゲーションエージェントのコールバックは、SimulationConfig.Navigation.EnableNavigationCallbacksから有効化する必要があります。

Simulation Config

以下のシグナルは、エージェントを厳密に制御するための迅速なフィードバックを提供します。

C#

namespace Quantum {
  public unsafe partial class NavMeshAgentTestSystem : SystemMainThread,
                                                       ISignalOnNavMeshSearchFailed,
                                                       ISignalOnNavMeshWaypointReached,
                                                       ISignalOnNavMeshMoveAgent { 
    }
}
ISignalOnNavMeshSearchFailed エージェントが自身の位置とSetTarget()で設定されたターゲット間に経路を作成できなかった(例:ターゲットがナビメッシュから離れすぎている)際に呼び出されるシグナルです。

このコールバック内でSetTarget()を呼び出した場合は、resetAgentパラメーターをfalseに設定する必要があります。
ISignalOnNavMeshWaypointReached エージェントがターゲットへの経路上のウェイポイントに到達した際に呼び出されるシグナルです。

WaypointFlagsは、ウェイポイントの追加情報を保持します。

Target - ウェイポイントがターゲット
LinkStart - ウェイポイントがオフメッシュリンクの始点
LinkEnd - ウェイポイントがオフメッシュリンクの終点
RepathWhenReached - エージェントはウェイポイントに到達した際に経路の再計算を実行する
ISignalOnNavMeshMoveAgent NavMeshAgentConfig.MovementTypeCallbackに設定されていて、NavMeshSteeringAgentコンポーネントを持つエージェントにターゲットが設定されている場合に呼び出されるシグナルです。

desiredDirectionパラメーターは、エージェント内部のステアリングと回避によって割り出される、エージェント移動ベクトルの正規化された方向です。

移動するエージェントのコールバック実装例で、例えばKCCの入力としても使用できます。

C#

public void OnNavMeshMoveAgent(Frame frame, EntityRef entity, FPVector2 desiredDirection) {
    var agent = frame.Unsafe.GetPointer<NavMeshSteeringAgent>(entity);

    // エージェントを移動する単純な例
    if (frame.Has<Transform2D>(entity)) {
        var transform = frame.Unsafe.GetPointer<Transform2D>(entity);
        transform->Position.X.RawValue = transform->Position.X.RawValue + ((desiredDirection.X.RawValue * frame.DeltaTime.RawValue) >> FPLut.PRECISION);
        transform->Position.Y.RawValue = transform->Position.Y.RawValue + ((desiredDirection.Y.RawValue * frame.DeltaTime.RawValue) >> FPLut.PRECISION);
        transform->Rotation = FPVector2.RadiansSignedSkipNormalize(FPVector2.Up, desiredDirection);
    } else if (frame.Has<Transform3D>(entity)) {
        var transform = frame.Unsafe.GetPointer<Transform3D>(entity);
        transform->Position.X.RawValue = transform->Position.X.RawValue + ((desiredDirection.X.RawValue * frame.DeltaTime.RawValue) >> FPLut.PRECISION);
        transform->Position.Z.RawValue = transform->Position.Z.RawValue + ((desiredDirection.Y.RawValue * frame.DeltaTime.RawValue) >> FPLut.PRECISION);
        var desiredRotation = FPVector2.RadiansSignedSkipNormalize(FPVector2.Up, desiredDirection);
        transform->Rotation = FPQuaternion.AngleAxis(desiredRotation * FP.Rad2Deg, -FPVector3.Up);
    }
}
Back to top