A* Pathfinding Project Proは経路探索AISystemです
Unity上で手軽に動作し、Unityにデフォルトで実装されているNavmeshよりも高機能な経路探索を提供してくれます。
積みアセット消化のためずっと前に買っていたA* Pathfinding Project Pro(以下A*)を使ってみようと思います :)
https://www.assetstore.unity3d.com/jp/#!/content/1876
A*は負荷が高いのでスマホゲームでの利用は厳しいところがありますが、レビュー数483,評価MAX5とたくさんのデベロッパーに愛されているAISystemです。
Aはその名前の通りAアルゴリズムが使われた経路探索システムを実装しています。
A*アルゴライズムとは?こちらによくまとまっています :)
素晴らしい
http://qiita.com/2dgames_jp/items/f29e915357c1decbc4b7
A*の公式サイトはこちら
http://arongranberg.com/astar/
A*のドキュメントはこちら
http://arongranberg.com/astar/docs/
説明文を読むだけで思わず使ってみたくなるような素晴らしいAssetですね :)
##A*の主なFeature
まずは主なFeatureを見てみましょう。
いっぱいありますが、要は超手軽に高性能な経路探索が使えることがかいてあります :)
###Navmeshes作成機能
高速+低メモリー+正確なNavmeshesを作成します。
(作成するというよりは作成したNavmeshを使ってAI経路探索を簡単に提供するよという意味かも)
###Grid Graphs
Grid Graphsは、柔軟で使い勝手が良く、素早く更新されます。例えばタワーディフェンス系のゲームに最適です。
Grid Graphsは、その名の通り整列されたGridのようなNodeによって作成されています。
###Point Graphs
Point Graphsは、カスタマイズ可能で柔軟性があります。
いくつかのPointを設定すればそれらの周りにGraphsが組み立てられます。
特別なTransformにPointを設定したりして使うよ。
###Automatic Navmesh Calculation
クリックするだけで地形に合わせてNavmeshを自動生成するよ。
手動でやれば数時間はかかる処理でもAを使うことで数分で終わらせるよ。
多くの経路探索システムのNavmeshesはStaticじゃないとダメっていう制約があるけど、Aはそんなことないよ。しかも低負荷でね(まじか、すごいな)
Navmesh Cutting機能がランタイムでNavmeshesを更新してくれるよ。
###Local Avoidance
RVO (reciprocal velocity obstacles 衝突を予測し回避するやり方)に基づいたLocal Avoidanceシステムを実装しているよ。なので100体ものエージェントを使った群集シミュレーションもできるよ(PC次第だけど1000体もいけるかも)
Local Avoidanceは、Unityのキャラクターコントローラーを使うよりも使いやすいよ
###Multi-platform
Unityがサポートしているプラットフォームには全て対応しているよ。
OSX, Windows, Linux, WebGL, Android and iPhoneでもちゃんとテストしてるから安心して。
###Easy to get started with
A*を使うのは一見難しいと思うかもしれないけど、超簡単だよ。get-started guideを見れば短い時間であなたのゲームに組み込むことができるよ。
###Path Modifiers
Pathの編集はとっても簡単です。まさに朝飯前なくらい。サンプルもsmoothing modifier, raycast simplifier, funnel modifier,他その他用意しているよ。
###Custom Inspectors
A*ではあなたがより効率良く仕事ができるように大規模なEditor拡張をしているよ。ダークスキンとライトスキンの両方用意しているから見た目もバッチリさ
###Specialized Path Types
資源収集者が最寄りの資源の場所までのパスを探したり、モンスターに追われているCPUが逃げるためのパスを探すのはどうやってやるのでしょうか。Aを使えば1つのパスをリクエストするだけで事足ります。
サーチモードはAのコアを書き換えることなく自由に追加することができます。
###Saving and Loading
全てのGlaphデータはファイルに保存された後でロードすることができます。しかも自動的に最新のデータが読み込まれます。
###Multithreaded
A*は情報処理量を最大にするためマルチスレッドで実装されています。
###Graph updates during runtime
Graphsはランタイムに更新されます。
タグ,移動可能,移動コスト,接続状態などの要素によって更新されます。
Graphsの更新は小さく部分的に行われるため高いFPSをキープできます。なので自由に障害物を動かしてキャラクターの行動をダイナミックに変化させることができます。
###Navmesh Cutting
AはGraphs上にある動く障害物を考慮しています。よくあるNavmesh Systemだと経路探索をリアルタイムに更新するのは困難です。しかし、Aではnavmesh cuttingをサポートしているため動く障害物を扱えます。
詳しくはこちら
###Penalties/Weights
移動の困難さに重み付けをすることができます。これを使うことでキャラクターに公園を歩かせるのではなく道路を優先して歩かせることができます。
###Tags
Tags(レイヤーとも言われますが)を使うことでキャラクターの移動を制限したり、ペナルティを与えることができます。もし溶岩を障害物とするタワーディフェンスゲームを作っていた場合に、あるユニットは溶岩を避けて通り、また耐火性のあるユニットは溶岩を突っ切る、といったことが可能になります。(長距離移動によるコストより溶岩の上を渡るコストが低ければ、耐火性のないユニットも溶岩を突っ切るのでそこは注意ですね)
###3rd Party Packages
いくつかのサードパーティパッケージはA*と連携できるようになっています。
NodeベースでAIを組めるBehaviour Designer Movement PackやハイレベルなキャラクターコントロールができるICE Creature Controlなどがあります。
くわしくはこちら
##A*を導入
###1.AssetStoreから購入してUnityインポート
お値段100ドルですね。
昔はもうちょっと安かったと思ったのですが・・・
割愛します
###2.セットアップ
インポートが完了するとDialogが出現しました。
3.8.2も3.8.1もあんまり変わらないような気がしますが、幾つかのバグ修正が入っているのならバージョンアップをすることにしましょう。
「Take me the download page」ボタンを押します。
A*の公式サイトに遷移しました。
シリアルナンバーなんて覚えていないよ・・・
AssetStoreに登録してあるメールを「A* Pathfinding Project」で検索して購入確認メールに添付されていた請求書に描いてあるOrder NoをInvoice Noの所に入力してSubmitする
購入日を見たら2015/2/3だった・・・orz
ナンバーが当たっていればProVersionをDLできるようになる。
「Download(version:3.8.2,last updated:29 Feb 2016)」ボタンをクリックしてDL
2月にupdateしたならAssetStoreのパッケージも更新する時間はあっただろうになんでupdateしないんだろう・・・
DLしたzipを解凍すると最新のUnityPackageが入っています。これをUnityに読み込ませる。
現在のバージョンとの差分が更新され、A*が最新バージョンになります。
うーん、UnityのMenuからAのバージョンを確認する手段がないのがちょっと気になりますね・・・
AでどうやってVersionをチェックしているのか調べてみたところ、
string versionData = UnityEditor.EditorPrefs.GetString ("AstarServerMessage");
Debug.Log (versionData);
上記のコードでチェックできることがわかりました。
Consoleには
3.8.2|3.8.2 Includes some fixes and improvements to DynamicGridObstacle and and a fix to AstarPath.limitGraphUpdates.
と表示されます。無事3.8.2最新バージョンになっていますね :)
##とりあえずDemoSceneをPlay
まずはA*でどんなゲームができるのか見てみましょう :)
楽しみです!
DemoSceneは
AstarPathfindingProject/ExampleScenes以下にまとめて用意されています。
DemoSceneが不要であればExampleScenesフォルダはまるごと削除しても大丈夫です。
ただし、AstarPathfindingProject/ExampleScenes/ExampleScriptsにあるスクリプトはとても便利なので削除しないで再利用したほうが良いかもですね:)
今ExampleScenesの中を見たのですがExample1がない(Example2_Terrainから始まってる)
なんでだろう・・・?
とりあえずExample2_Terrainから見ていきます!
###Example2_Terrain
Terrainとダイナミックオブジェクトを使ったサンプルのようです。
このサンプルには湖と2個のダイナミックオブジェクトと1機のボットが存在します。
ボットはマウスカーソルを常に追いかけますが、段差的に行けない場所,湖,ダイナミックオブジェクトを避けて移動します。
このサンプルを見ることでTerrain,Tagを使った経路探索の実装方法を把握することができます。
実際にプレイしてみた時のスクショ
右上に写っているBOTが一生懸命カーソルを追いかけててかわいいw
###Example3_Recast_Navmesh1
Recast(配役を変える)サンプルということかな・・・?
このサンプルは、Pキーを押すことでマウスカーソルポジションに動的にダイナミックオブジェクトを追加することができます。
BOTは相変わらずマウスカーソルを追いかけていますが、ダイナミックオブジェクトを避けようとします。
せっかくなのでBOTを閉じ込めてみましたw
出れなくて困ってるところがかわいいですね。
また、このサンプルには段差が作られており、BOTでも登れるようになっています。
説明文には次のように書かれています。
The modifier components on the Player
smooths and simplifies the path.
Remove them to see the difference
the Playerが何を指しているのかわからず戸惑いましたが、ここでのthe PlayerはBOTを指しています。
BOTのFunnelModifierコンポーネントを外すと、BOTが移動する際にカクカクするのがわかります。(あるのとないのじゃ結構違う)
FunnelModifierコンポーネントは常にAddしておいたほうがよさそうですね。
Toggle 'Show Graphs' on the A*
GameObject to show graphs
But press 'Scan' first, otherwise
there will be no graphs to show
なるほど、GraphsをScene上に描画するためには
1.AsterPathコンポーネントのScanボタンを押してスキャンする
2.AsterPathコンポーネントのShow Graphsにチェックを入れる
が必要なのね。
HierarchyにA*という名前のGameObjectがあります。そいつがAsterPathコンポーネントを持っています。
指示通りやってみました。
Scene上にGraphが描画された!
なるほど、経路探索の経路を視覚的にわかりやすく描画してくれるのね。
ゲームを再生するとGraphの描画が消されるので注意です。またGraphが見たい場合は1と2をやり直す必要があります。
The 'Repath Rate' on the 'Player'
can be lowered for faster repaths
BOTにはMineBotAIコンポーネントがAddされており、こいつのRepath Rateの値によってRepathする頻度が変わるみたいです。
Repath Rateの値は低いほうが更新頻度が高いみたいですね。
初期値は0.2になっています。これを20にするとBOTはずっと同じ場所に向かおうとします。BOTの移動経路にダイナミックオブジェクトを置いても無視して突っ切ろうとします(突っ切ろうとするのですがダイナミックオブジェクトのほうが重く、移動できていないw)
Repath Rateによって処理負荷も結構変わると思うので処理負荷を見ながら値を調節していきたい(とも思ったがRepath Rateを変えるとAIの挙動が全然違ってしまうのでなるべく固定でいきたい)
###Example4_Recast_Navmesh2
地続きではない場所に移動する経路探索の実装方法がわかるサンプルになっています。
このサンプルを参考にすることで段差や物理的に離れた場所まで移動させる実装方法を理解することができそうです。
###Example5_PointGraph
BOTがどのPathを使って移動しているのか視覚的にわかるサンプルです。処理速度や目的地までの残りのPath数もわかります。
###Example6_Navmesh
Navmesh!
自分で経路探索に使うMeshを設定するサンプルになります。(Meshを設定しないとゲームが正常に動作しません)
Due to technical resons, meshes must always be in the resources folder
however I haven't been able to distribute the project with the mesh there
so you will have to link it yourself.
- On the A* object, drag the navmesh_example6navmesh mesh (not prefab) from the Example6
folder to the "Source Mesh" field on the navmesh graph. - Click on any "Fix" buttons which will appear.
- You should now be able to test this example
(reasonsがresonsに誤字ってるのかな?)
日本語訳
テクニカルな理由により経路探索に使用するMeshは必ずResourcesフォルダ以下に置かなくてはいけません。
Example6_NavmeshサンプルではAゲームオブジェクトのAstarPath*コンポーネントにMeshが指定されていないため開発者自身でMeshを指定してみてね。
- Example6フォルダのnavmesh_example6navmeshメッシュファイル(プレファブではないですよ)をAゲームオブジェクトのAstarPath*コンポーネントの"Source Mesh"フィールドにDrag&Dropします。
- Fixボタンを選択します。
- ゲームを再生してサンプルを見てみましょう。
最初はこのTextを読まないでゲームを再生したけど動かなかったのでバグってるのかと思ったw
ちなみにFixボタンを押したあとのMeshはAssets直下のResourcesフォルダに移動されます。
###Example6_Navmesh_RichAI(またExample6?)
このサンプルはExample6_Navmeshとほぼ同じです。ただBOTにRichAIコンポーネントを使っていることが違うらしい。よく分からない・・・
###Example7_Door1 (Door1シーン)
動的に移動経路を制御する方法その1 タグの使い方
タグの制御の仕方を教えてくれるサンプルです。ドアを開け閉めしてBOTにいたずらするサンプルです
ドア部分にはTagが仕込んであり、このTagはドアの開閉状態によって変化します。
BotはこのTagを見て移動可能かどうか考えています。
もう少し具体的に言うと、
BotゲームオブジェクトのSeekerコンポーネント(名前がかっこいい)のValid Tagsにチェックが入っているTagは通れるようになります。
このサンプルではValid TagsのClosed Doorにチェックが入っていません。
こんな感じのコードで動的にドアのTagを変えられるようです。
//boundsはドアのColliderのbounds
GraphUpdateObject guo = new GraphUpdateObject(bounds);
guo.modifyTag = true;
//tagはint型。今回のサンプルだと1ならOpen,2ならCloseになってた
guo.setTag = tag;
guo.updatePhysics = false;
AstarPath.active.UpdateGraphs(guo);
###Example7_Door1 (Door2シーン)
動的に移動経路を制御する方法その2 NavmeshCutの使い方
ドアオブジェクトにNavmeshCutコンポーネントがAddされています。
NavmeshCutは、Tagを使った移動経路の制御方法よりも処理負荷が大きいですが、その分柔軟にMapを作成することができます。
NavmeshCutは、Meshの移動によって移動可能かどうかを常に判断します。そのためコード上で操作する必要がないので簡単です。
NavmeshCutでの移動制御とTagでの移動制御を同時に使うとうまく動作しないので注意しましょう。
両方で制御した場合は基本的にTagが優先されます。
###Example8_PathTypes
A*で使えるPathの種類を確認できるサンプルです。
使えるPATHとその特徴はこちら
PathType | 2 |
---|---|
ABPath | 基本的なPATH。AからBに最短距離で移動するためのPATH |
MultiTargetPath | 1つの地点から複数地点までのPATH?よく分からない |
RandomPath | ランダム性をもったPATH。spreadよりaimパラメータのほうが大きければrandomの方向を絞ることもできる |
FleePath | Flee(逃げる)用のPATH |
ConstantPath | 障害物を考慮して移動できる範囲を教えてくれるPATH |
FloodPath | よく分からない |
FloodPathTracer | 真横,真上,斜めしか移動できないPATH。ABPathより負荷が軽い |
###Example9_Penalties
Tagによる移動禁止エリアの指定と、ペナルティの重み付けによる移動パスの制御を見ることができるサンプルです。
ペナルティの重み付けはBotが持つSeekerコンポーネントのTag Penaltiesから設定できる。ペナルティは個体ごとに設定できるので色々面白いAI挙動を作ることができそうだ。
###Example10_LayeredGridGraph
立ち入り禁止エリアがあるサンプルです。
指定した範囲を指定したTagに対して歩けるか/ペナルティはどれくらいかを指示することができます。
###Example11_RVO (RVOシーン)
RVOを使ったLocal Avoidanceのサンプルです。円状に並んだエージェントたちが(お互いぶつからないようにして?)並び順を変えます。
また、ドラックでエージェントを複数選択した後、Aボタンを押すと、その位置を中心として選択されたエージェントたちが円状に並びます。
###Example11_RVO (RVOLightweightシーン)
こちらもRVOを使ったサンプルになります。
エージェンス数や形状を変更してどういう動作をするのか視覚的にわかりやすく表現しています。
###Example12_Procedural
経路探索の探索範囲を動的に更新するサンプルです。
BOTの位置を中心に経路探索を更新しており、処理不可を下げていることがわかります。
###Example13_Moving
動いているオブジェクト上での経路探索サンプルです。
船は絶えず移動していますが、その上にあるBOTは正常に経路探索できていることがわかります。
###Example14_2D
A*を2Dで使うサンプルです。ほとんど3Dで使うのと変わらないですね。
##Get Startedやってみる
Get Startedをやってみます。
(基本的に日本語訳に翻訳した文章を書いていきます)
経路探索の本質は、ポイントAからB間の最適なPATHを見つけることです。A*はどうやって算出しているのでしょうか,このチュートリアルでプロジェクトのセットアップの方法と新しいシーンからオブジェクトを避けて動くシンプルなAIを作る方法を学んでいきます。
あなたが書いたAIは十分に発達しているとは言えません(まじか)。移動とPathに沿って進むのに必要な最小限のコードしか書いていないからです。もしより進んだAIを使いたいなら自分のAIを拡張するか、このチュートリアルでAIPathかRichAIコンポーネントを使うかどちらかです。(ちなみにRichAIについてはGet Started Part2で学びます)
もしテキストよりビデオのほうが好きならタワーディフェンスゲームの作り方を学べるこちらを使うことができます。
###Downloading
一番初めにやらなきゃいけないことはA*のダウンロードです。ここからダウンロードできます。機能制限されたFreeバージョンか全ての機能を使えるProバージョンのどちらかを選んでください。
###Javascript (Unityscript)
もしUnityscriptを使って開発しているならこちらの注意書きに従ってください。
全てのサンプルはC#で作成されています。C#とUnityscriptはとても似ているので注意書きに従って開発すればそんなに難しくないかなと思います。
Unityscriptはoptional parametersをサポートしていないことに注意してください。もしUnityscirptから関数を呼ぶ時にエラーがでたらドキュメントを参照して全ての型とデフォルト値をチェックしてみてください。
###Deploying for Mobile
どのプラットフォーム向けにビルドするかによって変わるのでこちらを見た方が良いです。
###Errors?
もしコンパイラエラーが起きていたらまずはReadme_upgrading.txtを読んでみてください。もし古いバージョンからアップデートした場合は、名前が重複しているかもしれません。名前を変えるか、コンパイルエラーが起きないようにnamespaceで区切ったりして対応してください。
###Overview
- Aの中心的なスクリプトはastarpath.csです。その他の全てのAスクリプトとのハブになります。
AstarPathコンポーネントのインスペクターでは、全てのGraphsと設定が表示されます。
AstarPathコンポーネントは、経路探索を使う1シーンにつき、いつも必ず1つでなければいけません。
astarpath.csはComponents–>Pathfinding–>Pathfinderから探せます。 - 次に大事なコンポーネントはSeeker.csです。Seekerコンポーネントは、経路探索をする全てのGameObjectにつけるべきです。
SeekerコンポーネントはPathに関する処理を行います。厳密に言えばSeekerコンポーネントは必須ではありません。しかし、Seekerコンポーネントは経路探索を簡単にします。なのであった方が良いでしょう。 - モディファー(更新/変更を行うスクリプト)も重要です。(SimpleSmoothModifier.csなど)モディファーがSeekerコンポーネントを持つGameObjectにAddされているなら、モディファーはPathをスムーズにします。詳しくはこちら
##簡単なAIを実装する
空のシーンからAIを持ったエージェントを作成し、歩かせるところまで実装してみます。
###New Scene
新規シーンを作って"PathfindingTest"という名前で保存しましょう。そしたら障害物を避けながら移動できるAIを作っていきます。
- Planeをシーンに追加し、Planeを座標(0,0,0)、スケール(10,10,10)にしてください。
###Adding A*
今、私たちはAIが立つことができる"Ground"と避けるべき"Obstacles"を持っています。次はシーンを経路探索できるようにAの中核となるA* Pathfinding System*を作っていきましょう。
- 空のGameObjectを作り、名前を"A*"にしてください。そして"Components–>Pathfinding–>Pathfinder"からAstarPathコンポーネントをAddしてください。
asterPathと検索しても出てこないのでちょっとわかりにくいので注意しましょう。
(PathfinderをクリックするとAsterPathコンポーネントがAddされます)
AstarPathコンポーネントのインスペクターはいくつかのPartによって区切られています。(素晴らしい)
今はGraphsエリアとインスペクターの下部にある"ScanButton"を覚えておきましょう。この2つは超重要です。
GraphsエリアとScanButton
Graphsエリアではシーンの全てのGraphsを管理しています。いくつでもGraphsを作ることができますが、1 or 2個でも十分に事足りるでしょう。基本的にはシングルグラフの方が良いです。
Graphsエリアのヘッダー(タイトル)をクリックすると追加されているGraphの一覧が表示されます(今はまだGraphを追加していないので"Add New Graph"しか表示されませんが)。ここでGraphsエリアの全ての機能を説明することはできませんので、2つの重要な機能を説明します。
1つ目は、GridパターンでNodeを作成するGrid Graphです。もう一方は、Meshを歩行可能エリアにするNavmesh Graphです。
Scan ButtonはGraphsを更新するためのButtonで、シーンを立ち上げた時にはすでにScanはGraphsの更新は終わっています。(Scanがキャッシュされていない場合は別のPartで解説します)
いくつかのGraphsはGraph設定が変更された時に自動的に再スキャンされます。ラグはないです。
Scanの実行にはショートカットが割り当てられています。
**Cmd+Alt+S (mac) or Ctrl+Alt+S (windows).**です。
このチュートリアルでは、Grid Graphを作ります。"Add New Graph"を開いた後、"Grid Graph"をクリックしてください。
名前の通り、Grid GraphはGrid状にNodeを作成していきます。Grid Graphの大きさはwidth*depthで設定されています。Gridはシーン上のどこにでも配置できますし、回転角度も自由に設定できます。
"Node Size"変数は四角形状に配置されているNodeの大きさを決定します。
widthとdepthでGraphの横幅と縦幅を決定し、Node SizeでGraphのScaleを決定するイメージが近いかなと思います。
このチュートリアルではNode Sizeは1に設定しておきましょう。そうすることで1unitのスペースにつき、1つのNodeが配置されます(たぶん)
しかし、Nodeのポジションは変更可能の方が良いでしょう。Grid Graphエリア内にある"Center"フィールドに(0,-0.1,0)と入力してみましょう。"-0.1"という値は、float型のPoint Errorを避けるためです。シーンのGroundのY座標が0だった場合、GraphのY座標も0になります。Rayを飛ばす際にこれらに反射してPoint Errorを引き起こし、私たちを苛立たせるかもしれません。
"Center"の右側にあるBOXをクリックすることでアンカーを変えることができます。
シーンに合わせるためにGridのサイズを変更するには、widthとdepthを変える必要があります。今回は両方共100にセットしましょう。GridがPlaneとちょうど重なるように配置されるのが見えるかと思います。
Grid配置後にScanしてみた。綺麗に焼けてますね
Nodeを正しい高さに配置するために、A* Systemはシーンに対してどこにHitするか見るためにRayの束を発射します。これをHeight Testingと呼びます。
これらのRayの太さは自由に調整することができます(Lineとは対照的です)。RayはRay lengthによってGridに向かって下方向に発射され、NodeはHitした場所に配置されます。もしどこにもHitしなかった場合は、"Unwalkable When No Ground"にチェックが入っていなかったり、NodeがGridとのY座標の相対差が0だと"Unwalkable When No Ground"のチェックが外れるので、移動不可能になります。
Maskは使用しているものだけに変更した方が良いです。通常は"Everything"に設定されていますが、これだと障害物レイヤーも含まれてしまいます。それだと障害物レイヤーを設定した意味がありません。なのでMaskを早いうちに"Ground"レイヤーだけにしておきましょう。
Mask=Everythingだと障害物の上にもNodeが入っているのがわかる
Mask=Groundだと障害物の上にはNodeがない
Nodeを配置する時に、歩行可能がチェックします。この作業はSphereかCapsule、もしくはRayによって行なわれます。通常は、AIキャラクターと同じ高さと直径を持ったCapsuleが使われ、シーン上を歩けるかどうか確認します。
AIキャラクターが、直径と高さそれぞれ3D世界の単位で1と2の大きさを持っているとすると、collision testing時には直径と高さには(マージンを設定するため)それぞれ2と2を入力します。
次に、Systemに配置した障害物を認識させる(知らせる)ために、Collision testingのMaskを変える必要があります。今回は"Groundレイヤー"を障害物として扱ってほしくないので、"Obstaclesレイヤー"(障害物レイヤー)だけを含むようにセットしましょう。
これでGraphsをスキャンするためのセットアップが完了しました。
Scanボタンを押してGridが生成されるまでの数秒間待ってみてください!
最終的に下の写真見たいな感じになればOK!
余白もちゃんと設定されてるし、障害物の上は歩けないようになってる。
Maskもちゃんと設定済み
###Adding the AI
動くオブジェクトが1つもない経路探索のテストって何なんでしょう。そんなの面白くないですよね。なのでAIを追加して動くようにしていきましょう!
- Capsuleを作成して、"Character Controller"コンポーネントをAddしましょう。それができたらCapasuleをPlane上のどこかに配置してください。
- 次にCapsuleに"Seeker"コンポーネントをAddしてください。このコンポーネントは他のScriptからPathを申請するためのヘルパースクリプトです。
- さて、ここからは自分自身でコードを書く必要があります。といってもAIを動かすのに必要なのは本当にシンプルなスクリプトだけです。好きなスクリプトEditorを開いてください。
ちなみに、A* Projectには、このチュートリアルで書くスクリプトよりも良いスクリプトをすでに用意しています。AIPathとRichAIコンポーネントです。AIPathコンポーネントはどのGraphでも使用可能で、RichAIは主にNavmeshをベースとしたGraphで使用できます。
###Moving Stuff Around
Seekerコンポーネントはとてもシンプルです。3つの引数しか受け取りません。
1つ目は、start position。
2つ目は、end position。
3つ目は、callback function。
です。callback functionはvoid SomeFunction (Path p)
のように戻り値がなしで引数にPath型を1つだけ受け取る必要があります。
それではAstarAIという名前でC#スクリプトを新規作成して次のようなコードを書いてください。
using UnityEngine;
using System.Collections;
//Note this line, if it is left out, the script won't know that the class 'Path' exists and it will throw compiler errors
//This line should always be present at the top of scripts which use pathfinding
using Pathfinding;
public class AstarAI : MonoBehaviour {
public Vector3 targetPosition;
public void Start () {
//Get a reference to the Seeker component we added earlier
Seeker seeker = GetComponent<Seeker>();
//Start a new path to the targetPosition, return the result to the OnPathComplete function
seeker.StartPath (transform.position,targetPosition, OnPathComplete);
}
public void OnPathComplete (Path p) {
Debug.Log ("Yay, we got a path back. Did it have an error? "+p.error);
}
}
コンパイルが正常に通ったらAsterAIコンポーネントを先ほど作成したAIゲームオブジェクト(Capsule)にAddしてください。
InspectorからAsterAIコンポーネントのTargetPositionを適宜変更してください(例えば座標(-20,0,22)などに)。この座標を見てAIはPATHを見つけようとします。
それではPlayボタンを押してゲームを再生してください。Scene Viewを見てみるとAIオブジェクトからTargetPositionまでの経路探索が緑色の線で描かれています。(SeekerコンポーネントはGizmoを使って最新のPATHを描画してくれます)
*見やすくするためにAIオブジェクトとGroundの色を変えています。
もし緑線が見えなかったらSeekerコンポーネントの"Draw Gizmo"にチェックが入っているか確認しましょう。
(またGizmoがGroundによって隠れている場合もあります。)
もしエラーが発生した場合は、ちゃんとSeekerコンポーネントとAsterPathコンポーネントが同じAIオブジェクトにAddされているか確認してください。それでもエラーが消えない場合は、TargetPositionが届かない範囲に設定されているかもしれません。範囲内に収まるように調整してください。
緑線がちょっと角張っているように見えるかもしれません。しかし今はそれで十分です。先ほど書いたコードが何をやっているのかの説明があるまで待ちましょう。
SeekerコンポーネントのStartPath関数が呼ばれてから何が行われているのでしょう。Seekerコンポーネントは新しいPathインスタンスを作成し、それをAsterPathコンポーネントに送っています。(AsterPathコンポーネントは以前A*ゲームオブジェクトにAddしたコンポーネントですね)
AsterPathコンポーネントは、送られてきたPathインスタンスをqueueに追加し、できるだけ早くGridからPathを検索する処理を行います。NodeからEndPositionまでのNodeの経路がここで作成されます。
PATHの検索処理はこのチュートリアルで丁寧に説明されています。厳密には違うところもありますが、コンセプトは同じなので参考になるかと思います。
計算が終わるとすぐに、Pathインスタンスは処理を依頼してきたSeekerコンポーネントに返されます。そしてSeekerコンポーネントはCallback Functionを発火します。このCallbackは自由に登録できるイベントSeeker.pathCallbackにも送られます。全てのStartPath関数のコールバックを受け付けたいときはこちらのイベントを購読しましょう。
//OnPathComplete will be called every time a path is returned to this seeker
seeker.pathCallback += OnPathComplete;
//So now we can omit the callback parameter
seeker.StartPath (transform.position,targetPosition);
*注意
コンポーネントをRemoveしたりDestoryしてもCallbackの参照はRemoveされません。ちゃんとDestory時にCallbackの参照をRemoveするようにしましょう。
public void OnDisable () {
seeker.pathCallback -= OnPathComplete;
}
CallbackでPathを受け取ったとして、どうやってそれを使えば良いのでしょうか?
Pathインスタンスは2つのlistを持っています。
Path.vectorPathは、各PATH(経路座標)を管理するVector3 listです。このリストはPathをスムースにしようとするときに修正されます。Pathを利用する際に推奨されるのはこっちのlistです。
もう1つのPath.pathは、GraphNode elementsのリストになります。これは実際に移動する際に横切る全てのNodeを管理しています。
最初は"path.error"を見てエラーがないかチェックすべきです。もしpath.errorがtrueなら何らかの理由によってpath取得が失敗しています。"Path.errorLog"から詳細なエラー情報を取得できます。
先ほと作ったAstarAIコンポーネントを拡張して、いくつか動きを追加しましょう。
using UnityEngine;
using System.Collections;
//Note this line, if it is left out, the script won't know that the class 'Path' exists and it will throw compiler errors
//This line should always be present at the top of scripts which use pathfinding
using Pathfinding;
public class AstarAI : MonoBehaviour {
//The point to move to
public Vector3 targetPosition;
private Seeker seeker;
private CharacterController controller;
//The calculated path
public Path path;
//The AI's speed per second
public float speed = 100;
//The max distance from the AI to a waypoint for it to continue to the next waypoint
public float nextWaypointDistance = 3;
//The waypoint we are currently moving towards
private int currentWaypoint = 0;
public void Start () {
seeker = GetComponent<Seeker>();
controller = GetComponent<CharacterController>();
//Start a new path to the targetPosition, return the result to the OnPathComplete function
seeker.StartPath (transform.position,targetPosition, OnPathComplete);
}
public void OnPathComplete (Path p) {
Debug.Log ("Yay, we got a path back. Did it have an error? "+p.error);
if (!p.error) {
path = p;
//Reset the waypoint counter
currentWaypoint = 0;
}
}
public void Update () {
if (path == null) {
//We have no path to move after yet
return;
}
if (currentWaypoint >= path.vectorPath.Count) {
Debug.Log ("End Of Path Reached");
return;
}
//Direction to the next waypoint
Vector3 dir = (path.vectorPath[currentWaypoint]-transform.position).normalized;
dir *= speed * Time.deltaTime;
controller.SimpleMove (dir);
//Check if we are close enough to the next waypoint
//If we are, proceed to follow the next waypoint
if (Vector3.Distance (transform.position,path.vectorPath[currentWaypoint]) < nextWaypointDistance) {
currentWaypoint++;
return;
}
}
}
これでAIオブジェクトがPathに沿って進む姿が見れるようになります。
###Smoothing the Path
さて、これまでにシンプルなGrid Graphのセットアップの方法と、経路探索を実行する方法を学びました。でももっとスムースに移動させる方法があるのではないでしょうか?
その通りです。Pathをスムースに、シンプルにするスクリプトはPath Modifiersと呼ばれ、Seekerコンポーネントを持つGameObjectにAddすることができます。
もっとも簡単なのは"Simple Smooth modifier"です。"Components–>Pathfinding–>Modifiers–>Simple Smooth"からAIオブジェクトにAddできます。
Path Modifiersは何をしているのでしょうか?実はPathを何回も分割していて、各セグメントは"Max Segment Length"変数より小さくなるように分割されます。そのためよりスムースに動くことができるようになるのです。Path Modifiersはいくつものパラメータを持っています。ここで全て解説することはできませんが、ぜひSimpleSmoothModifierのドキュメントを見てみてください。各パラメータの解説が載っています。
FunnelModifierも優秀です。SimpleSmoothModifierとの比較はこちらで見ることができます。