UnityでXY平面上でのA*経路探索を実現したい

Applying AStarPathFinding Project for XYPlane

A* Path Finding Project

アクションゲームを作ろうと思うと、AIにどういう動きをさせるかが結構問題になります。
そんなときのために様々な経路探索の方法が巷では考えられているわけですが、A*アルゴリズム(エースターアルゴリズム)はその中でもメジャーな経路探索方法です。

UnityのAsset StoreでもよくこのA*系のアセットを見つけるのですが、その中でも無料で使えて単体機能としてとても利用しやすいのが、A* Path Finding Projectです。公式サイトに行くと無料版が手に入りますし、動的な経路変更に対応した有料版もAsset Storeで手に入ります。

テラシュールウェアさんでも使い方が紹介されていました。
[Unity3D]無料で経路探索、A* Pathfinding Projectを試す。その1
[Unity3D]無料で経路探索、A* Pathfinding Projectを試す。その2
[Unity3D]無料で経路探索、A* Pathfinding Projectを試す。その3

XY平面でやりたい

…と、ここまでは3Dアクションゲーム等で利用する分には問題ないのですが、
添付されているサンプルも含めて、A* Path Finding Projectは基本的にXZ平面上での経路探索を
前提として機能が提供されています。

しかし、3D表現の2Dアクションゲーム等の場合、XY平面で経路探索がしたいときがあります。

この記事では、無料版のA* Path Finding Projectの一部ソースをいじって、
XY平面に対応させる方法を記述します。

サンプル

AStarXYSample

とりあえずサンプルを置いておきます。
A* PathFindingProject 3.4.0.6を利用しています。

手順

A* Path Finding Project自体の使い方に関してはざっくりいきます。
前述のWebサイト等を参考にお願いします。

  1. A* Pathfinding Projectの設定
  2. 必要な要素を配置
  3. スクリプトの追加
  4. AIPath.csをXY平面用に拡張する

(1) A* Path Finding Projectの設定

今回はPoint Graphでの経路探索を実装します。

(2) 必要な要素を配置

サンプルのヒエラルキー ヒエラルキービューはこんな感じ

  • AStar
    空オブジェクトです。Path Finder Componentをアタッチして、経路探索の全体制御をします。
  • Directional Light
  • Main Camera
  • NodeRoot
    経路となるノード群です。60個くらいあります。
    このノード以下のオブジェクトがすべて経路探索ノードとなるように設定しています。
  • Plane
    経路のテクスチャを貼っているパネルです
  • SeekerRoot
    経路探索を元にターゲットまで移動するオブジェクトです
  • Target
    経路探索の目的地となるオブジェクトです

AStarXY_001

(3) スクリプトの追加

PathFinderコンポーネント以外のスクリプトでやることは大きく2つあります。

  1. Seekerの制御
  2. ノードの制御

ノードの制御については、Assets/AstarPathfindingProject/Core/Misc内にある
NodeLink.csというスクリプトを制御したいノードにぺちっとつけることで可能です。
作ったサンプルでは何をやっているかというと、つながってほしくないノードの「断絶」を行っています。

AStarXY_002

もうひとつ、Seekerの制御に使用するAIPath.csというのが、今回編集の必要のあるスクリプトです。

(4) AIPath.csをXY平面用に拡張する

SeekerにTargetを追尾させるには、AIPath.csというスクリプトを追う側のオブジェクトにアタッチします。
これは対象のオブジェクトを選択して/Component/Pathfinding/AI/AIPath(generic)を
選択することでできます。

AIPath.csの設定方法

今回はAIPath.csをコピーしてAIPathXY.csという別のスクリプトを自前で作って、
それをオブジェクトに付与しています。

  1. XYSqrMagnitudeメソッドの作成
    XZSqrMagnitudeメソッドという、XZ方向のベクトル長をとる関数がもともとあるのですが、
    これのXY平面版を作成しておきます。

    protected float XYSqrMagnitude (Vector3 a, Vector3 b) {
    float dx = b.x-a.x;
    float dy = b.y-a.y;
    return dx*dx + dy*dy;
    }
    
  2. CalculateTargetPointメソッドを修正
    Y軸に対する扱いをすべてZ軸に変更します。

    protected Vector3 CalculateTargetPoint (Vector3 p, Vector3 a, Vector3 b) {
        a.z = p.z;
        b.z = p.z;
        
        float magn = (a-b).magnitude;
        if (magn == 0) return a;
    
  3. RotateTowardsメソッドの修正
    通常はY軸回転以外を0にしていますが、どの軸に関しても制限を行わないように変更します。

    protected virtual void RotateTowards (Vector3 dir) {
    
    if (dir == Vector3.zero) return;
    
    Quaternion rot = tr.rotation;
    Quaternion toTarget = Quaternion.LookRotation (dir);
    
    rot = Quaternion.Slerp (rot,toTarget,turningSpeed*Time.deltaTime);
    Vector3 euler = rot.eulerAngles;
    //euler.y = 0;
    //euler.x = 0;
    rot = Quaternion.Euler (euler);
    
    tr.rotation = rot;
    }
    
  4. CalculateVelocityメソッドの修正
    XZSqrMagnitudeを呼び出している箇所は先ほど作成したXYSqrMagnitudeに変更します。
    y軸の速度変化を無効にしている記述があるので、z軸を無効化するように変更します。

    dir = targetPosition-currentPosition;
    dir.z = 0;
    float targetDist = dir.magnitude;
    
    float slowdown = Mathf.Clamp01 (targetDist / slowdownDistance);
    
    this.targetDirection = dir;
    this.targetPoint = targetPosition;
    
  5. Updateメソッドに処理を追加
    ここまでの修正で一応XY平面で動くようにはなるのですが、
    縦横無尽に回転できてしまうために、ローテーションスピードによってはZ軸方向に
    ガンガン移動していってしまいます。
    これを無理やり補正するために、Updateの最後でZ軸の位置を0に補正しています。

    // Fix Z-axis position at 0.0f
    Vector3 temp_pos = new Vector3 (transform.position.x, transform.position.y, 0.0f);
    transform.position = temp_pos;
    

おしまい

最後は結構無理やりです…。
何でこんなことになってるかというと、AIPathは進行方向の決定をLookRotationという関数で
行っているためです。LookRotationはZ軸プラス方向を引数で与えたベクトルへ向ける
回転行列を生成する関数ですが、これをFromToRotation関数とかで代替したら
もっと綺麗に書けるのかなぁと思います。

とりあえずの拡張としては以上になります。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です