RTSのRoyal Revolt2みたいな感じで作ってみようと思います。
今はまだまっすぐな1本道ですが、途中で道が曲がったりする感じの自動マップをやろうと思います。ローグライクみたいなフラクタル構造の部屋割りとかではないので単純ですね。
途中に敵のSpawnポイントを設置して、道沿いにタワーを設置する。このへんの間隔とか量・質でレベルデザインとして緩急付けるみたいな方向性です。
タワーを作った
とりあえずタワーを作ってみました。砲塔の部分をこちらに旋回させて弾を打ってきます。
破壊されると砲塔部分がAddForceで吹っ飛んで道に転がります。タワーオフェンスですね。
青い円が触れると敵が発生するコライダー。これをマップに配置することでレベルがデザインできます。
白い球は経路探索用のノード。これはデバッグじゃない場合は非表示になります。
敵と味方の移動に経路探索を追加
自前の経路探索も使うようにした。味方AIはプレイヤーキャラをRAY光線的に見失った場合は、経路探索にパスを作ってもらって戻ってくる。
敵も、RAY光線で見失った場合は、プレイヤーキャラへのパスを経路探索から取得して、接近してきます。
画像の白いボックスが、テスト用の障害物でRAYを通しません。よってこれに隠れると互いに見えなくなるんですが、経路探索が走るので、ゾンビは隠れても近づいてきます。
見えないなら近づいてこないという方向もあるんですが、この場合は記憶ですね。
まったく見たことがないなら、見えなくても経路探索はしない。もともと捕捉してたなら、見えなくなったら経路探索するみたいな使い分けがいいかもしれません。後者は記憶と推論の結果です。
前者まで経路探索してたら、偵察衛星付きな行動になってしまう(´・ω・`) 臭いで分かりましたって場合もあるかもしれんけど。
障害物ですが、ただ設置するとうまく行かない。キューブの四方にnodeを設置しておくと、経路探索がうまく行きます。
例えば、
〇------〇
このような二つのノードの間に障害物を置いてしまうと、
〇--□--〇
このようにノード間の連絡が途絶えますので、経路が切断されます。
しかし、障害物の四方にnodeを置いておけば
〇--〇-〇--〇 |□| 〇-〇
ノード間の接続を保てます。
マップを自動合成する場合も、四角の障害物の四方にnodeを設置するって方向でやれば、ノード配置も全自動で行けるんではないかと思います。
ウェイポイントナビゲーション
経路探索の手法ですがあらかじめnodeを配置しておいて、node間の経路を調べるって手法は、「ゲーム開発者のためのAI入門」のP120から解説してあるウェイポイントナビゲーションと同じですね。
「ゲームAIプログラミング」だとP200のナビゲーションシステムと同じ。
A*探索は、探索する必要のあるノードをオープンリストに登録、探索が終わってる場合はクローズリストに登録、といった手法で探索対象を絞るアルゴリズムのようです。A*がもっとも優れた探索手法らしい。
あと、探索するノードを、ヒューリスティックを用いて順序付ける方法もやってるみたい(たとえばゴールに近づくノードから先に探索する)
ヒューリスティックを使うと、正解ノードが実はゴールが遠ざかる場合は逆に探索開始が遅れて無駄が膨らむけど、一般的には効率がよくなるのでしょう。
ヒューリスティックでの順序付けぐらいはやっててみてもいいかな。
- 作者: David M. Bourg,Glenn Seemann,株式会社クイープ
- 出版社/メーカー: オライリージャパン
- 発売日: 2005/01/12
- メディア: 大型本
- 購入: 24人 クリック: 395回
- この商品を含むブログ (78件) を見る
味方キャラと距離を保つ
あと、味方キャラ同士で近づきすぎた場合に距離を保つ処理ですが、今までは一番近いキャラから逃げるみたいな方向で実装してましたが、SpringBoneのコライダー接触処理のように全体との接触処理をやるようにしました。
近いキャラから避ける動きを、複数のキャラで行って、ベクトルを加算したら、ちょうどいい感じに逃れますね。
距離が近い場合に、相手から遠ざかる方向のベクトルnormalを加算して求めます。
いままでは左右で挟まれると左右への逃げで振動してましたが、ループ回して加算すれば、厳密に正確に左右に挟まれてるならその場で静止したままです。
この処理は、鉄砲を持ってるキャラが敵から逃げて距離を保つ場合も使えます。
全体でベクトルを加算するので、複数のキャラに囲まれている場合にいい感じの方向に逃げてくれます。これはなかなか良い改良になりました。
void KeepSpaceSelf(float radius) { for (int i = 0; i < gm.chara.Count; i++) { Vector3 targetPos = gm.chara[i].transform.position; if (Vector3.Distance(transform.position, targetPos) <= radius) normal += (transform.position - targetPos).normalized * radius; } normal.y = 0f; }