個人制作ゲームの進捗状況・ゲーム作りに役立つ情報を発信

敵キャラの描画と移動アルゴリズム|らんだむあくしょん!制作記録4

らんだむあくしょん!制作記録

久しぶりの更新です。
ブログ執筆はサボってましたが、ゲーム制作は続けてました!

期間が空いて制作した内容も多いので、今回は2回に分けてお送りします!

今回制作したもの

今回は敵キャラ関連の実装が盛りだくさんです。

敵キャラの表示と配置

まずは敵キャラの表示です!
プレイヤーと同様、数フレームごとに表示画像を切り替えて、その場で足踏みさせてます。

敵キャラのランダム配置は、「部屋内かつプレイヤーや他の敵が居ない場所」にランダムに決めた数を置いていってます。
(ランダムに決めた数:10〜20体など範囲を指定)

敵との当たり判定

次は当たり判定プログラムの改修です。
といってもソースコードの変更量は僅かでした。

今まで壁とだけ衝突判定を行っていたのを、敵キャラとも行うようにしただけです。

ターン制の導入

さて、お次はターン制の導入なのですが、ここはかなり苦戦して、だいぶ期間が空いてます。

ターン制の簡単な実装方法としては、
「プレイヤーの行動」→「敵Aの行動」→「敵Bの行動」→「……」→「プレイヤーの行動」
というように、それぞれの敵の行動を1つずつ行なっていくことが考えられます。

ただこれには問題があって、敵の移動なども1体ずつ行うのでとにかく時間がかかるのです。
それではあまりにゲームのテンポを損なうので、なんとかする必要がありました。

攻撃などは1体ずつの方が分かりやすいのでいいのですが、移動はまとめて行いたい。
よくある不思議のダンジョンシリーズなどでも、テンポを良くするため移動は一度に行われています。

そのため、敵キャラを一気に移動させるための仕組み作りを行いました。

敵ターンのフェイズ分け

まずは敵ターンを、次の4つのフェイズに分割することを考えました。

  • ディサイドアクションフェイズ
  • ムーブフェイズ
  • スキルフェイズ
  • アタックフェイズ

最初のディサイドアクションフェイズでは、各敵キャラのそのターンにおけるアクションを決定します。

移動をまとめて行うために、「どの敵が移動してどの敵が攻撃するのか」といったことを事前に振り分けておく必要があるので、このフェイズでそれを実施。
それぞれの敵が、「移動」「スキル」「攻撃」のうち、どれを行うのかがここで決まります。

全ての敵のアクションが決まったら、その次の3つのフェイズで「移動」「スキル」「攻撃」をそれぞれ実施していきます。
各フェイズは順番に実施されるので、「移動」→「スキル発動」→「攻撃」の順番で敵の行動が行われていくことになります。

ムーブフェイズの処理

ディサイドアクションフェイズが終わったら、ムーブフェイズです。
この段階では、全ての敵が上に移動するだけですが、それでも色々と考えることはありました。

まず当たり判定ですが、壁・プレイヤーに加え、他の敵との衝突も考慮する必要があります。

「プレイヤーが移動する際の、敵との当たり判定」では、位置が確定している敵との衝突を考慮するだけで良かったのですが、「敵キャラが移動する際の、他の敵との当たり判定」ではそうはいきません。

なぜなら、「ムーブフェイズでこれから移動する敵」との当たり判定も考える必要があるからです。
そのため、「敵キャラが移動する際の当たり判定」ではその辺りを考慮し、「攻撃」や「スキル」を行う敵との当たり判定はそのままに、「移動」を行う敵との当たり判定はロジックを少し変更しています。

また、移動方向に必ず移動できるとも限りません。
具体例を挙げると、以下のような状況です。

上が壁でなければ移動できる

まずは問題なく移動できる場合の例です。
画像では、上のマスが空いているので、スライムもゴブリンも1つずつ上のマスに移動することができます。

ゴブリンが移動するマスには、当初スライムが居るのですが、このスライムも移動するだろうと仮定して目的地をスライムの居るマスに設定しています。

実際にスライムの移動方向は空いていて、スライムの元居たマスも空くので、ゴブリンも無事に移動できるという訳です。

上が壁だと移動できない

次は、スライムの移動先である上方向が壁の場合です。
この場合、スライムが壁に衝突するので移動することができません。

ゴブリンは先ほどと同様に、スライムが移動するだろうと仮定して目的地をスライムの居るマスに設定しています。

ただ今回の場合は、スライムが移動できませんでした。
そのためゴブリンの座標を目的地ではなく、元々の座標に戻すという処理が必要になります。

この目的地に移動できない事例は全ての敵に発生しうるので、「全敵キャラに対して、目的地が他の敵の居るマスと被っていないかを確認して、被らなくなるまで元の座標に戻し続ける」という作業を行うことで、ようやく移動後の座標が確定します。

そうして決定した座標を元に、マップ情報を更新してムーブフェイズは完了です!

上の2つ目のポスト(ツイート)で、片方の敵が消えてしまうバグは、このマップ情報の更新をミスっていたのが原因でした。
 ※差分だけ変更しようとしたらミスったので、座標を元に全情報を更新するよう修正して直しました

敵キャラのなめらかスクロール移動

敵の移動が出来るようになりましたが、すごくカクカクした移動でした。
そこでスクロール移動を実装し、なめらかに移動できるように。

実装方法は制作記録2で実装した、プレイヤーのマップスクロールと大して変わりません。
違う点としては、プレイヤーの時はマップをスクロールさせていましたが、今回は敵キャラ自体をスクロールさせているところが異なるくらいです。

ミニマップ上に敵の位置を表示

さて、敵がなめらかにスクロール移動できるようになったので、本格的に敵キャラの移動AIを制作……
といきたいところではありましたが、その前にやるべきことがありました。

それがミニマップへの敵の位置の表示です!
これがないと、せっかく移動AIを実装しても、画面外で敵キャラがどんな動きをしているか分かりません。

制作記録3で実装したミニマップに、赤丸を作って敵の位置を示します。

赤丸の描画には、EbitengineのDrawFilledRectという関数を使っています。
要は塗り潰された四角形を描画する関数です。

四角形で赤丸? と思うかもしれませんが、位置をズラした長方形を2つ重ねるとそれっぽくできます。

⬜︎⬛︎⬛︎⬜︎   ⬜︎⬜︎⬜︎⬜︎   ⬜︎⬛︎⬛︎⬜︎
⬜︎⬛︎⬛︎⬜︎   ⬛︎⬛︎⬛︎⬛︎   ⬛︎⬛︎⬛︎⬛︎
⬜︎⬛︎⬛︎⬜︎ + ⬛︎⬛︎⬛︎⬛︎ = ⬛︎⬛︎⬛︎⬛︎
⬜︎⬛︎⬛︎⬜︎   ⬜︎⬜︎⬜︎⬜︎   ⬜︎⬛︎⬛︎⬜︎

DrawFilledRect関数は、中々使い勝手の良い関数なので、他のところでもちょくちょく使ってます。

DrawFilledRect関数使用箇所:HPバーとか文字の背景色とか

ミニマップの着色

さらにミニマップの改修が続きます。

結構複雑なダンジョンの形を採用しているので、通路が絡み合うところなんかでは、どっちが通路でどっちが壁かが分かりにくい場面があったりしました。

そこでミニマップに着色することで、プレイヤーの行ける場所が視覚的に分かりやすくなりました!
もちろん色がない方が邪魔になりにくくて良いという人も居ると思うので、後々設定画面を作った際には、設定項目として盛り込む予定です。

また、部屋だけ着色するバージョンも実装。
私は今のところ、「部屋だけ」が好みです。

ミニマップの表示モード切り替え

ミニマップの最後の改修、表示モードの切り替えです!
プログラム内で切り替えるのではなく、ゲーム画面上のボタンから切り替えられるよう手を加えました。

強調モードでは周りが暗くなり、マップ情報だけを確認しやすくなります。

非表示モードはミニマップが邪魔な時に使ってもらう感じです。
ダンジョンを自動生成している関係で、プレイヤーの顔の上にミニマップが来たりすることもあるので、すぐ非表示にできるようにしています。

敵キャラの移動AI

そして今回のラスト! 敵キャラの移動AIです!
こいつもほんとに難しかった……。

よくある不思議のダンジョンシリーズだと、通路は幅1マスの直線というのが多いです。

その場合、通路の直線に従って進めていけば、自ずと移動アルゴリズムは完成します。
あとは部屋での処理と、通路の分岐地点でどちらに行くか選ぶくらい。

ただ、うちのゲームだとマップがランダムすぎて、この手法は全然使えません笑
よって1から方法を考えました。

この移動アルゴリズムでは、大まかに次の2つの手順に分かれています。

  • 目的地を求める
  • 移動座標を求める

目的地を求める

まず目的地ですが、目的地を求めるために「移動方向」という概念を取り入れています。

各敵キャラごとに「移動方向」と「サブ移動方向」を持っており、その方向を元に目的地を算出します。
例えば、あるスライムの移動方向が「上」、サブ移動方向が「左」だった場合、このスライムは大体左上の方向に進んでいく感じです。

上のポスト(ツイート)にもあるように、「プレイヤーの近く」「部屋」「通路」で目的地の求め方が違うのですが、まずは基本となる通路から行きます。

通路での目的地

移動方向・サブ移動方向が「上・左」だった場合、下図のように敵キャラの居るマスから左上に2マスを考えます。

⬛︎⬛︎⬛︎
⬛︎⬛︎⬜︎
⬛︎⬜︎敵    ⬛︎……目的地となりうるマス

このうち、黒い四角のマスが目的地となりうるマスで、今居るマスから2歩で移動できるマスになります。
この中からランダムに目的地を決定するのですが、中には壁によって行けないマスもあるので、その辺りを考慮して候補を減らします。

例えば、敵キャラの上2マスが壁だった場合は、壁に阻まれて左下の1マスにしか移動できません。

⬜︎⬜︎⬜︎
⬜︎壁壁
⬛︎⬜︎敵    ⬛︎……目的地となりうるマス

上1マスが壁だった場合は、以下の3マスに移動できます。

⬜︎⬜︎⬜︎
⬛︎⬛︎壁
⬛︎⬜︎敵    ⬛︎……目的地となりうるマス

このように周囲の地形情報を確認し、2歩で移動できるマスが目的地となります。
ただし、その方向が全部壁で進めない! という場合も当然ながらあります。

その場合は、「上・左」だった移動方向を、「上・右」または「下・左」にして目的地があるか確認……
その両方が全部壁だった場合は完全に反転して、「下・右」方向を確認……というように、移動方向を変えながら目的地を探していきます。

行き止まりだったとしても今来た道はあるはずなので、反転すれば最終的に目的地は見つかります。

部屋での目的地

部屋に居る場合は、敵キャラを次の通路に進ませたいので、目的地を「その部屋に隣接している最も近い通路」に設定します。

ただし部屋に入ったばかりの状況で一番近い通路だと、当然今やってきた通路が一番近くなってしまうので、「移動方向側で」という注釈が付きます。

こちらも通路の時と同様、行き止まりの部屋で「移動方向側に通路なんてない!」という場合もあるので、その時はまた移動方向の転換を行います。

そうして目的地が見つかればあとは移動するだけ!……と行きたいんですが、これでも上手くいかないパターンがあります。
以下のような状況ですね。

⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎
⬛︎⬛︎⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎⬛︎⬛︎
☆⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎⬛︎⬛︎
⬛︎⬛︎⬛︎⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎⬜︎
⬛︎⬛︎敵⬜︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎  ⬛︎……壁
⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎⬛︎  ☆……目的地

ダンジョンをランダム生成している都合上、変な形の部屋が結構な頻度でできます。
その時の敵の初期配置が上図・移動方向が「上・左」のようになってしまうと、ハマって動けなくなります。

移動方向側で、その部屋に隣接している一番近い通路(目的地)は☆の地点ですが、後述する「移動座標を求める処理」では、移動方向を元にして移動座標を求めています。

ただし上図の場合、敵キャラの左上3マスは壁なので移動できず、延々とその場に留まってしまうのです。

これの解決策として、「部屋に居る時でも、通路用の目的地算出アルゴリズムを3割の確率で使う」こととしました。

通路用の方だと、確実に行けるマスを目的地にしてくれるので、うまいこと抜け出すよう動いてくれます。
上図において通路用のアルゴリズムが実行されると、移動方向が「上・右」になるので、次に部屋用のアルゴリズムが呼ばれた際には、部屋の右側にある通路が目的地になります。

右の通路は壁に阻まれておらず問題なく辿り着けるので、無事脱出できます。

プレイヤーの近くに居る時の目的地

プレイヤーの近くに居る時は、当然プレイヤーに近付くように目的地を設定します。
具体的には、以下のようにプレイヤーの周囲8マスのいずれかです。

⬜︎⬜︎⬜︎⬜︎⬜︎
⬜︎⬛︎⬛︎⬛︎⬜︎
⬜︎⬛︎プ⬛︎⬜︎
⬜︎⬛︎⬛︎⬛︎⬜︎  プ……プレイヤー
⬜︎⬜︎⬜︎⬜︎⬜︎  ⬛︎……目的地となりうるマス

この中で、今敵キャラが居るマスと最も近いマスが目的地となります。
 ※他の敵キャラが居たり、壁だったりするマスは除く

移動座標を求める

さて、目的地が定まれば、そちらに向かって移動します。

といっても「通路用の目的地算出」と似ていて、移動方向側の2歩先まで確認した上で、目的地に一番近付けるマスを移動座標とします。
(各マスごとに目的地とどのくらい近いかをスコアとして記録し、最もスコアの低い(一番近い)マスを採用)

⬛︎⬜︎⬜︎
⬜︎☆⬜︎    ⬛︎……目的地
⬜︎⬜︎敵    ☆……移動座標

上図の例だと、敵キャラの居るマスから左上に2マス移動することで目的地に辿り着けるので、移動座標は左上の☆のマスになります。

ただし、今回も壁を考慮するのに加え、目的地算出の時は気にしていなかった「攻撃」や「スキル」行う敵の位置も、移動座標の候補から除いて考えます。
「移動」する敵は、ムーブフェイズの処理の項目で説明した、「被らなくなるまで元の座標に戻し続ける」処理を後で実施すれば問題ないので、ここでは考慮しません。

ここまでやって、ようやく全部の敵の移動座標が確定です。
あとはその方向を向かせてスクロールさせてあげれば、敵キャラの移動とムーブフェイズが完了します。

おわりに

以上で今回制作したものの解説は終了です!

いや、マジでムズい!!!

ちゃんと説明しきれたのか少し不安です笑
そもそもここまでの解説を求めている人がどれだけいるのだろう……とか考えちゃいますけど、まぁ自分の記録のために書いてるのでしっかり読まれなくても全然オッケーです。

といっても、これで作ったの全部じゃないんですよね……。

ここから更に、敵キャラとのバトルも既に実装してあります!!
そっちの制作記録も(ヒィヒィ言いながら)書いていくので、近日中に公開予定!

それじゃあまた!

コメント

タイトルとURLをコピーしました