NavMeshAgentを使って逃げるAIを作った話
どうも、ゆべしネコです。そういえば最近Unityいじれてないなとふと思ったので、リハビリがてら簡単なゲームを作ってみようと思いましてここ最近はUnityをいじって遊んでました。今回の目標はUniRxを使った鬼ごっこゲームを作ることです。...と言っても、ほとんどの時間を逃げるAIの作成に割かれてしまった訳ですが...笑
結果、こんなんできました。
逃げる仕組み
逃げるAIって言ってもどうやって逃げるようにしたらいいのか悩みますよね。最初は逃げる方向に適当に次の目的地を設定して、そこが移動可能かどうか判定してもし移動不可ならもう一度実行する...みたいなAIがいいかと思ったのですが、なんか良さげな実装が思いつかなかったので、もっと単純化することにしました。
逃げる位置となるポジションを最初から置いておく
シーンが始まると同時にゲームオブジェクトが生成されます。
MovePointGenerete.cs
using UnityEngine; public class MovePointGenerete : MonoBehaviour { [SerializeField] private int _genereteNum = 30; void Start () { var parent = new GameObject ("MovePoints"); for (int i = 1; i <= _genereteNum; i++) { var nextMovePoint = new GameObject(); nextMovePoint.name = ("MovePoint(" + i + ")"); nextMovePoint.tag = "MovePoint"; nextMovePoint.layer = 9; nextMovePoint.AddComponent<SphereCollider>(); nextMovePoint.AddComponent<NextMovePosition>(); var rb = nextMovePoint.AddComponent<Rigidbody>(); rb.useGravity = false; rb.constraints = RigidbodyConstraints.FreezePosition; nextMovePoint.transform.parent = parent.transform; nextMovePoint.transform.position = new Vector3 (Random.Range(-50, 50), 0, Random.Range(-50, 50)); } } }
ポイントはレイヤーとコライダー、Rigidbodyコンポーネントの追加ですね。この時にPhysicsウィンドウを開いてレイヤーでの衝突設定を行い、プレイヤー、逃げる敵がこのオブジェクトに触れられないようにします。この流れでこのあとどうするかわかった方もおられるのではないでしょうか。
プレイヤーに近づかれたら周辺の逃げるポジションを探して逃げる
プレイヤーに近づかれたら周辺のゲームオブジェクトを探してきます。
Enemy.cs一部抜粋
public void RunAway () { currentState = EnemyState.TENSION; coloring.material.color = Color.red; escapeTime = 0; agent.angularSpeed = 200; //プレイヤーと逆方向を向く var diff = (transform.position - player.transform.position).normalized; diff.y = 0; transform.rotation = Quaternion.FromToRotation(diff, Vector3.up); agent.SetDestination(GetNextPosition()); //目的地に近づいたら次の目的地を検索 agent.ObserveEveryValueChanged(d => agent.remainingDistance) .Where (d => d < 2.0f) .Where(_ => currentState == EnemyState.TENSION) .Subscribe (_ => { var nextposition = GetNextPosition (); agent.SetDestination(nextposition); }).AddTo(gameObject); //一定距離離れて一定時間経ったら状態を戻す this.UpdateAsObservable () .TakeWhile(_ => currentState == EnemyState.TENSION) .Select(_ => (transform.position - player.transform.position).sqrMagnitude) .Where(distance => distance > targetDistance * targetDistance) .Subscribe(distance => { escapeTime += Time.deltaTime; if (escapeTime >= 10) Usual (); }); } public Vector3 GetNextPosition () { if (m_foundList.Count > 0) m_foundList.Clear(); m_foundList.AddRange(Physics.OverlapSphere(transform.position, _searchRadius, mask)); if (m_foundList.Count == 0) { //近くになかったときは半径40mの中にあるオブジェクトを獲得し、そこからランダムに選ぶ m_foundList.AddRange (Physics.OverlapSphere(transform.position, 40.0f, mask)); foreach (var obj in m_foundList) Debug.Log (obj.gameObject.name); } else { for (int i = 0; i < m_foundList.Count; i++) { var foundData = m_foundList[i]; if (!CheckFoundObject(foundData.gameObject)) m_foundList.Remove( foundData ); } } return m_foundList.Count > 0 ? m_foundList[Random.Range(0, m_foundList.Count-1)].transform.position : Vector3.zero; } private bool CheckFoundObject( GameObject i_target ) { var myPositionXZ = Vector3.Scale( transform.position, new Vector3( 1.0f, 0.0f, 1.0f ) ); var targetPositionXZ = Vector3.Scale( i_target.transform.position, new Vector3( 1.0f, 0.0f, 1.0f ) ); var toTargetFlatDir = ( targetPositionXZ - myPositionXZ ).normalized; //同位置にいるときは範囲内にいるとみなす if (toTargetFlatDir.sqrMagnitude <= Mathf.Epsilon) return true; return (Vector3.Dot (transform.forward, toTargetFlatDir)) >= m_searchCosTheta; } }
まず最初にプレイヤーと逆の方向を向かせます。その後、次のポジションを見つけるのですが、ここで先ほど作ったゲームオブジェクトが役立ちます。
Physics.OverlapSphere(transform.position, _searchRadius, mask)
を使って探索範囲中にある次のポジションのコライダーの配列を取得し、リストに追加します。しかし、球の中全てからランダムに選ぶのではあまりよろしくありません。なぜなら、プレイヤーの方向にある次のポジションも選択可能になってしまうからです。そこで、得られたリストのなかのデータの選別を行います。それにはこちらの記事を参考にしました。
得られたコライダーのポジション情報とEnemy自身のポジション情報から角度を判定し、一定範囲内にないものをリストから除外するという流れですね。これで自身の前方の一定範囲内にある次の移動地点が入ったリストができるというわけです。最後にこれらのリストからランダムに一点を選ぶようにして次のポジションを決定します。一も見つからなかったときは操作範囲を広げて、Physics.OverlapSphereメソッドを実行し、こちらでは角度判定を行わないで最終移動地点を選択するようにしています。
このように、逃げる位置を最初から作っておき、そこから角度を判定して適切な逃げる位置を選択し続けることでプレイヤーから遠ざかるような動きをするAIを作りました。
欠点
角度を取ってくるときに壁の向こうも取ってきてしまうことですね。そのため、壁の向こう側が次の移動地点に選ばれてしまうと壁でものすごく加速します。まぁ、それはそれで面白いのですが、改善点ではありますね。
あと、次のポジションのY座標が0で固定なので、Terrainや階層構造を持つステージでは使えない点ですね。平面ステージ限定でしか使えません。
その他やったこと
初めてAudioManagerを実装してみました。以前はC#の知識やUnityの知識が足りなかったため敬遠していたのですが、そろそろやってみてもいいだろうという訳でやってみました。実装にはこちらの記事のものを利用させていただきました。
やばい...すごく使いやすい!!
感動しました。ただ、立体音響には使えなさそうなのでVRでやるには工夫が必要ですね。
まとめ
鬼ごっこで使うには十分かな? と思えるようなAIができました。リハビリにもなったので自分としては満足です。一応GitHubにもあげておきましたので、中身の詳細が気になる方はぜひダウンロードしてみてください。
GitHub - yubesi/Tag: 鬼ごっことかで使えそうな逃げるAIをUniRxを使って作ってみました。ただし、平面だけでしか使えないです。
Markdown記法の基礎
はじめまして。ゆべしネコと申します。
はてなブログでの初めてのブログ投稿というわけで今後使っていくであろうMarkdown記法の備忘録的なものを書いていきます。
目次
Markdown記法とはなんぞや
Markdown(マークダウン)は、文書を記述するための軽量マークアップ言語のひとつである。本来はプレーンテキスト形式で手軽に書いた文書からHTMLを生成するために開発されたものである。
要するに単純な記号を使って簡単にHTMLを生成できる記法ですね。それだけでなく、PDFに変換したりもできるらしいです。拡張子は .md となります。
見出し
行頭に#
と半角スペースをつけてやると見出しが書けます。#の数がh1 ~ h6の数字の部分に対応します。
# h1
## h2
### h3
#### h4
##### h5
###### h6
結果
h1
h2
h3
h4
h5
h6
強調
強調したい文字を*
で囲みます。HTMLで言うところのemタグです。より強い強調をするときは**
で囲みます。HTMLで言うところのstrongタグですね。なお、*
は_
で代用できます。
*クラムボン*
_クラムボン_
**クラムボン**
__クラムボン__
結果
段落、改行
一行空けて書き始めると段落分けができ、行末で半角スペースを2つ以上入れると改行ができます。
二疋ひきの蟹かにの子供らが青じろい水の底で話していました。 <!-- <-空白が2つ入力されている -->
『クラムボンはわらったよ。』
『クラムボンはかぷかぷわらったよ。』
『クラムボンは跳はねてわらったよ。』
『クラムボンはかぷかぷわらったよ。』
<!-- 1行開ければ段落分けできる-->
上の方や横の方は、青くくらく鋼はがねのように見えます。そのなめらかな天井てんじょうを、つぶつぶ暗い泡あわが流れて行きます。
引用とリンク
行頭に>
と半角スペースを書くことで引用ができます。
また、[リンク文字列](URL)
を書くことでリンクを貼ることができます。
> バーチャルリアリティのバーチャルが仮想とか虚構あるいは擬似と訳されているようであるが、これらは明らかに誤りである.
[バーチャルリアリティとは - 日本バーチャルリアリティ学会](https://vrsj.org/about/virtualreality/)
結果
バーチャルリアリティのバーチャルが仮想とか虚構あるいは擬似と訳されているようであるが,これらは明らかに誤りである.
水平線
*
, _
, -
のいづれかを3つ以上入力すると水平線が書けます。
****
----
____
結果
リスト
ハイフン、プラス記号、アスタリスクのいづれかと半角スペースでリストが書けます.
- りんご
- みかん
<!-- 番号付きリストも書ける -->
1. はやぶさ
1. やまびこ
結果
- りんご
みかん
- はやぶさ
- やまびこ
ソースコード
一行だけならバッククォートで囲みます。複数行ならば行頭に半角スペースを4つ入れます。また、シンタックスハイライトに対応している言語ならばコードを```言語名
とバッククォート3つで囲むとハイライトされます。
`int foo = 0;`
var tmp = 0;
tmp = a;
a = b;
b = tmp;
```html
<h1> html </h1>
<span style="color: #0000cc"> こんにちは </span>
````
結果
int foo = 0;
var tmp = 0
tmp = a;
a = b;
b = tmp;
<h1> html </h1> <span style="color: #0000cc"> こんにちは </span>
その他
ブログでは使わないと思いますがコメントは<!-- -->
で書くことができます。ただし、前後に空行が必要です。
また、記号のエスケープをするにはバックスラッシュ\
を前に挿入するとできます。
まとめ
とりあえずこれぐらいできればいいかくらいのものを紹介しました。他にも表を作ったりはてな記法を書いたりもできるらしいですが、それはまた今度ということで。皆さんもMarkdown記法を使いましょう!
参考文献
メモ書きやドキュメント作成に便利な「Markdown記法」を使ってみよう : ビジネスとIT活用に役立つ情報