クラッシュローラーの敵は2体です。パックマンは4体なので一見、こちらのほうが簡単に思えます。しかし実際にはそうではありません。子供のころ実際にプレイしているところを見たのですが、挟み撃ち攻撃がエグいです。

そこで今回は敵を移動させるのですが、ランダムに移動させるのではなく挟み撃ち攻撃でプレイヤーを捕まえる方法を考えます。

挟み撃ちのアルゴリズム

まずプレイヤーを捕まえるには敵をプレイヤーがいる座標に移動させる方法を考えないといけません。そのための方法としてダイクストラ法があります。プレイヤーがいる座標までの最短経路を求めてそのとおりに移動させるという方法です。

ところが単純なダイクストラ法を使った追跡アルゴリズムでは2体の敵が重なってしまうと重なりっぱなしです。同じ地点を出発点とする最短経路でプレイヤーを追うわけですから当然そうなってしまいます。

プレイヤーの一番近くにいる敵には最短経路で追跡させ、もうひとつの敵には待ち伏せポイントのようなものをつくってそちらに向かわせるという方法でやってみましたが、うまくいきません。

しかたがないので尊敬するプログラミング配信系YouTuberのひとりであるこの方に質問をしてみました。

質問に対する回答はプレイヤーの左右に「仮想の壁」をつくって考えてみてはどうかと。そこで敵は2体いるので1体はプレイヤーを上または左側から、もう1体には下または右側から捕まえるように移動させることにしました。これだといい感じに挟み撃ちをすることができます。

以下は教えてもらった挟み撃ちのアルゴリズムで作成したテストプログラムです。一部謎の動きもありますが、いい感じに挟み撃ちされて死ぬことができています。

Enemyクラスをつくる

まず敵の移動処理と描画処理をおこなうためのEnemyクラスをつくりたいのですが、そのときにForm1クラスのマップなどのデータを共有できるようにしておこなければなりません。

まずは敵の移動方向を算出したり、現在の敵の座標を管理するためのEnemyクラスをつくってみましょう。

そのまえにEnemyクラスで使用するデータをForm1クラスから取得できるようにメソッドを追加します。

初期化

最初にEnemyクラスのコンストラクタ、フィールド変数、プロパティを示します。

移動方向を求める

GetDirectメソッドは挟み撃ちでプレイヤーを狙うために移動する方向を取得するためのものです。

ダイクストラ法で最短経路を求めるのですが、敵のIDでプレイヤーをどの方向から捕まえるのかを変えています。それから橋と道路が交差している部分は直進しかできないので注意が必要です。また敵がプレイヤーを捕まえにいく方向が決まっているので、プレイヤーが角にいる場合どうやっても捕まえることができません。その場合の対策としては例外的に全方向から捕まえにいくことができるようにしています。

敵を移動させるための処理を示します。敵が方向転換をするのは角と丁字路、十字路です。ちなみに「T字路」ではなく「丁字路(ていじろ)」が元々は正しいそうです。

移動させる

Form1クラスにおける処理

Enemyクラスを使うためにForm1クラスに追加すべき処理を示します。

敵の初期化

最初に敵を初期化します。

敵の初期座標は下記イメージの茶色(Color.FromArgb(128, 0, 0))の部分です。この部分の座標を取得してリストに格納するとともにEnemyオブジェクトを生成してEnemiesに格納します。

敵の移動

Timer.Tickイベントが発生したら敵を移動させます。

敵を描画する処理を示します。いまは実験段階なので「敵」と書いている矩形を表示させるだけです。これはあとで修正します。また橋の上を移動している敵は橋を描画したあともう一度描画しなおします。

敵の描画

オーバーライドしたOnPaintにおける処理部分を示します。デバッグを兼ねて敵の追跡ルートも描画しています。