This document is about: QUANTUM 2
SWITCH TO

Navmeエージェントを作成する

Quantum 2.0以降、navmeshエージェントは複数のコンポーネントに別れました。我々は、navmeshおよびステアリングを使用している開発者が動きの最終結果のコントロールを希望していることに気が付きました。これはしばしばゲーム体験に極めて重要な要素ですので、開発者がこのように希望することはもっともなことです。新しいnavmeshエージェントパーツは開発者がnavmeshサポートの組み合わせを、マルチスレッドパフォーマンスを行わずに、また必要のないパーツを実行したり必要のないメモリ消費を行わずに選ぶ手助けになります。

エージェントコンポーネントはNavMeshPathfinderNavMeshSteeringAgentNavMeshAvoidanceAgentの3つで、NavMeshAvoidanceObstacleはスタンドアロンのコンポーネントです。

エージェントエンティティはの作成方法は2通りあります。UnityでEntity Prototypesを使用するか、コードでエンティティを組み立てるかです。これらの方法では、NavMeshAgentConfig Quantumアセットを使用します。

UnityのEntity Prototypesでエージェントを作成する

  • Unityメニューを介してからQuantum prototypeを作成する : GameObject/Quantum/Empty Entity
  • エンティティを選択し、Transform2Dに設定する
  • NavMeshPathfinderコンポーネントをトグルする
    • デフォルトNavMeshAgentConfigを選択する
    • Initial Targetをトグルし、Unityシーンからトランスフォームを選択し移動先位置に初期位置を提供する
    • ベイクしたQuantum navmeshを選択する (Navmeshワークフローを参照すること)
  • NavMeshSteeringAgentでトグルする
  • パスギズモを確認するには:
    • NavMeshAgentConfigまたはデフォルトのShow Debug Steeringを有効化する
    • QuantumEditorSettingsでNavmeshギズモDraw Pathfinder Funnelを有効化する
  • 再生を押す
navmesh agent prototype

コード内のコンポーネントでエージェントを作成する

もう一つの手段として、コードでエージェントエンティティを組み立てることもできます。

初めに、エンティティには Transform2D または Transform3D コンポーネントが必要となります。 View コンポーネントを追加することでプレハブがシーンにレンダリングされます。

最も重要なコンポーネントは NavMeshPathfinder です。これはパスファインディングを行い、目的の位置とユーザー定義済みのウェイポイント数を蓄積してウェイポイントの進行を見つけます。このコンポーネントは、NavMeshPathfinder.Create() Factoryメソッドで、NavMeshAgentConfig内にパスして作成する必要があります。

NavMeshSteeringAgent コンポーネントは任意で、NavMeshPathfinderを必要とします。これには最高速度、加速度、回転速度の変数があり、いずれもランタイム中に変更可能で、パスに沿ってエンティティをステアリングします。このコンポーネントを使用しない代わりに、開発者はMovementTypeCallbackに変更し、最新の回避データを持ちながら独自の動作を挿入できます。回転速度や加速を無効にするには、0に設定します。

NavMeshAvoidanceAgent は、このコンポーネントに先立ってエンティティ上でSet()する必要のあるNavMeshPathfinderおよびNavMeshSteeringAgentの両方のコンポーネントを要件とします。このエージェントは回避と計算を行い、プライオリティおよびマスクとレイヤーでのフィルタリングを用いて、他の動いているエージェント(HRVO)を回避します。初めにNavMeshAgentConfigプライオリティで設定を行います。マスクとレイヤーはランタイム中にコンポーネント上で変更可能です。

エージェントが物体にステアリングされることで、例えばエージェントが静的コリジョンを貫通しないようにするには、エンティティに PhysicsCollider2D/3D および PhysicsBody2D が必要となります。これを有効にするには、NavMeshAgentConfigMovementTypeDynamicBodyに設定する必要があります。

C#

public override void OnInit(Frame f) {
    base.OnInit(f);

    var entity = f.Create();
    f.Set(entity, new Transform3D() { Position = FPVector3.Zero, Rotation = FPQuaternion.Identity });
    var config = f.FindAsset<NavMeshAgentConfig>(NavMeshAgentConfig.DEFAULT_ID);
    var pathfinder = NavMeshPathfinder.Create(f, entity, config);

    // find a random point to move to
    var navmesh = f.Map.NavMeshes["Navmesh"];
    if (navmesh.FindRandomPointOnNavmesh(FPVector2.Zero, FP._10, f.RNG, *f.NavMeshRegionMask, out FPVector2 randomPoint)) {
    pathfinder.SetTarget(f, randomPoint, navmesh);
    }

    f.Set(entity, pathfinder);
    f.Set(entity, new NavMeshSteeringAgent());
}

NavMeshエージェントギズモDraw Nav Mesh Agentsをアクティベートして、エージェントギズモがシーンウィンドウで描画できるようにします。

重要なエージェント設定

###パスファインダー###

NavMeshPathfinder.SetConfig() はコンポーネント作成およびランタイムの間に実行できます。現在、エージェントがパスやウェイポイントに従っているのであれば、新しいコンフィグからのカウントは異なり、パスはリセットされます。コンフィグは得エンティティのNavMeshSteeringAgentNavMeshAvoidanceAgentコンポーネント上で自動的にアップデートされ、Speed、Acceleration、AvoidancePriority、Layer、Maskの値はコンフィグ値にリセットされます。

NavMeshAgentConfig.MaxRepathTimeout は、ウェイポイントがこの時間に到達しない場合、エージェントパスファインディングをトリガーする秒数です。これはフェイルセーフ的なもので、行き詰ったエージェントを解放します。無効にするには、値を0に設定します。

NavMeshAgentConfig.LineOfSightFunneling は、メインのnavmeshの真ん中に配置されたnavmeshリージョンが使用された場合にアクティベートします。例えば、破壊できるビルなどです。リージョンによって導入された追加のトライアングルは、アクティブなリージョンの近くで少々奇妙なパスになることがあります。このオプションによって、リージョンの近くの不必要なウェイポイントが取り除かれます。

NavMeshAgentConfig.DynamicLineOfSight は、ウェイポイントが各ティックでスキップ可能かどうかエージェントチェックを行います。このオプションはコストがかかりますが、パス上にある不必要なウェイポイントをすべて取り除きます。

NavMeshAgentConfig.DynamicLineOfSightWaypointRange が設定されると、サイトチェックの行がウェイポイント(範囲)に近い場合のみ各ティックで実行されます。DynamicLineOfSightが有効化されていなくてもこのように動作します。

NavMeshAgentConfig.FindValidTargetCellRange は、SetTarget()中にnavmesh外の位置が選択された場合に有用です。範囲パラメータが隣合わせのセルを検索して、navmesh内にある目標の最適な置換を行います。

以下の画像で、黄色のxでマークされた目標をご覧ください。特定のセル範囲で検索されたセルには数字が入っています。例えば、0のセル範囲は目的地を見つけられなかったセルです。一方で範囲1は、黄色の点線で書かれているようにnavmesh上で最も近い位置を見つけます。セル範囲を不合理に大きくするとパフォーマンスに影響しますので注意してください。

navmesh agent waypoint reached detection axis

NavMeshAgentConfig.CachedWaypointCount を用いてNavMeshPathfinder上でキャッシュされるウェイポイントの数を設定します。一時的ではないデータを多く保管すると、シミュレーションの速度が遅くなるのでご注意ください。はじめにキャッシュに保管されたウェイポイントが、SetTarget()が呼び出されたときのエージェントの現在の位置で、ウェイポイント到達検出を強化するのに使用します。エージェントが最後のウェイポイントに向かってステアを開始すると、自動的に再度パスファインディングが行われ、エージェントがフレームの計算時に有効なウェイポイントを持たない状況を解決します。

ウェイポイント到達検出を有効にすると、エージェントがウェイポイントに到達するのに問題がある場合、 NavMeshAgentConfig.EnableWaypointDetection に便利です(例えば、回転速度が遅いまたは回避など)。それに続くパラメータAxis ExtendおよびAxis Offsetはウェイポイント到達検出軸(黒い線)を定めています。エージェントが黄色のゾーンに入ると、ウェイポイントに到達したとみなされます。

navmesh agent waypoint reached detection axis
Waypoint Reached Detection Axis

パスファインダーコンポーネントに付随するステアリングコンポーネントがない場合、ウェイポイント到達検出を実行するのに DefaultWaypointDetectionDistance が使用され、エージェントの最大速度×デルタ時間に設定されます。ウェイポイント到達検出の拡張方法については「便利なNavmeshエージェント設定」セクションを参照してください。

エージェントをステアリングする

NavMeshAgentConfig.StoppingDistance および AutoBraking は、最終目標に近づきつつあるエージェントに適用されます。 StoppingDistanceは、エージェントが目的地の前で停止する絶対的な距離です。この値を設定するとエージェントが安定してオーバーシュートしないようにすることができます。残りの距離が、エージェントの現在のティック毎の移動距離よりも少なくなると、エージェントは常に停止します。

AutoBrakingはどちらかというと視覚的な機能で、目的地に到着する前にエージェントの速度を落とし、エージェントの停止挙動を安定させるのにも使用できます。。AutoBrakingDistanceは、目的地の周りでエージェントが速度を落とし始める半径を決めます。内部的には、ブレーキングをスムーズに行うために、平方根が用いられます。

navmeshエージェントがMovementType PhysicsBodyを使用してジオメトリで跳ね返されない場合、特に回避を用いている時には、エージェントはnavmeshの外側に移動します。これを完全に防ぐには、エージェントをnavmeshのボーダーに沿ってスライドさせ、 NavMeshAgentConfig.ClampAgentToNavmesh を有効にします。

エージェントの半径はnavmeshのMinAgentRadiusよりも大きい可能性があります。Quantumでは、エージェントのウェイポイントをボーダーよりも遠くに移動させることでこれに対応しています。ただし、これはエージェントをnavmeshにより複雑にクランプすることになり、パラメーター ClampAgentToNavmeshRadiusThreshold がどのテクニックを使用すればいいか選択する助けになります。エージェントが小さい場合に半径を増やすと、navmeshの外側に言おうする傾向があります。

訂正を安定させるため、エージェントは侵入度全体の1パーセント(ClampAgentToNavmeshCorrection)しか移動しません。

インターバルを更新する

パフォーマンスの最適化のため、シミュレーションティックのたびに経路探索と回避を**実行しない **ように個々のエージェント(config)は設定できます。NavMeshAgentConfig.UdpateInterval`を1より大きい値に設定すると、取得する更新の量が少なくなります。これはエージェントの応答性を低下させますが、CPU時間を節約することができます。エージェントエンティティインデックスは、更新する正確なティックを定義するために使用されるので、すべてのエンティティが同じティックで更新されるわけではありません。

数式は以下のとおりです:

C#

updateAgent = entity.Index % agentConfig.UpdateInterval == f.Number % agentConfig.UpdateInterval
  • 1 = ティックのたびに更新
  • 2 = ティック1つおきに更新
  • 8 = ティック8つごとに更新、等

エージェントからのすべてのコールバックはメインスレッドから呼び出され、他のコンポーネントやエンティティにアクセスしたり記述する際に、マルチスレッディングの問題を引き起こしません。

ナビゲーションエージェントコールバックはオプトインされている必要があります。自分のシミュレーションコンフィグを開いて、Enable Navigation Callbacksをトグルします。

simulation config
Enable Navigation Agent Callbacks in the Simulation Config

次のシグナルは、エージェントをさらに制御する野に使用できる、差し迫ったフィードバックを提供します。

C#

namespace Quantum {
  public unsafe partial class NavMeshAgentTestSystem : SystemMainThread,
                                              ISignalOnNavMeshSearchFailed,
                                              ISignalOnNavMeshWaypointReached,
                                              ISignalOnNavMeshMoveAgent {
    }
}

ISignalOnNavMeshSearchFailed は、エージェントが現在の位置とSetTarget()で設定された目標の間にパスを作成できなかった場合呼び出されます。例えば、目的地はnavmeshと一致させられません。このコールバックの間、SetTarget()を実行する場合はresetAgentパラメータをfalseに設定します。

ISignalOnNavMeshWaypointReached はエージェントが目標へのパス上でウェイポイントに到達した場合に呼び出されます。ウェイポイントのTargetLinkStartLinkEndについての詳細は、WaypointFlags列挙を確認してください。

ISignalOnNavMeshMoveAgent が呼び出されるのはNavMeshAgentConfig.MovementTypeCallbackに設定されていてエージェントが NavMeshSteeringAgentコンポーネントを持っている場合のみです。desiredDirectionパラメータは、内部のステアリングや回避がエージェントの移動ベクトルであると認識する、標準化された方向です。

C#

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

    // simple demonstration how to move the agent.
    if (f.Has<Transform2D>(entity)) {
        var transform = f.Unsafe.GetPointer<Transform2D>(entity);
        transform->Position.X.RawValue = transform->Position.X.RawValue + ((desiredDirection.X.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION);
        transform->Position.Y.RawValue = transform->Position.Y.RawValue + ((desiredDirection.Y.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION);
        transform->Rotation = FPVector2.RadiansSignedSkipNormalize(FPVector2.Up, desiredDirection);
    } else if (f.Has<Transform3D>(entity)) {
        var transform = f.Unsafe.GetPointer<Transform3D>(entity);
        transform->Position.X.RawValue = transform->Position.X.RawValue + ((desiredDirection.X.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION);
        transform->Position.Z.RawValue = transform->Position.Z.RawValue + ((desiredDirection.Y.RawValue * f.DeltaTime.RawValue) >> FPLut.PRECISION);
        var desiredRotation = FPVector2.RadiansSignedSkipNormalize(FPVector2.Up, desiredDirection);
        transform->Rotation = FPQuaternion.AngleAxis(desiredRotation * FP.Rad2Deg, -FPVector3.Up);
    }
}

パスファインディングのみ

MapNavMeshPathfinderコンポーネントを使用して、マルチスレッドパスファインディング、目標をウェイポイントの保存を実行し、ウェイポイントのインデックス進行を行います。自分のシステム内のステアリング、回避、移動を自分で制御します。

ウェイポイントのインデックス進行がパスファインダーコンポーネントを実行するには、ウェイポイントにどれくらいの速度で近づいているかの情報が要件となります。各フレームにWaypointDetectionDistanceSqrプロパティを設定します。

回避なし

PathfinderコンポーネントとSteeringAgentコンポーネントのみを使用します。回避コードは1つも実行されず、コンポーネントは回避に関するデータを保存しません。SimulationConfig.Navigation.EnableAvoidanceをトグルオフして、CPU時間を節約してください。

Quantum Avoidanceを使用したカスタムムーブメント

3つのコンポーネントすべてを使用します(Pathfinder、SteeringAgent、AvoidanceAgent)。AvoidanceAgentsは、上書きしようとしてもSteeringAgentのパーツに依存します。NavMeshAgentConfig内でMovementTypeCallbackに設定し、ISignalOnNavMeshMoveAgentシグナル(前のセクションを参照)を実装します。desiredDirectionパラメータは移動方向を変更した回避も含んでいます。

Back to top