前回はプレイヤーを生成して移動の処理を実装しました。また適当感が否めませんが敵の移動と描画の処理もおこないました。今回はプレイヤーに穴を掘らせます。そして穴落ちた敵を下の階に突き落として倒す処理も実装する予定です。

穴を掘る

このゲームはどうやって穴を掘ったら位置にモンスターをおびき寄せるかがポイントです。穴に落ちたら埋めてしまいます。するとモンスターは穴の下におちて死にます。

モンスターは穴に落ちてもそのままにしておくと穴のなかから這い上がってきてしまい、もっと強力なモンスターに変わってしまいます。するとモンスターを倒しにくくなるわけですが、倒したときの点数も増えるので上級者はわざとモンスターを穴に落としては昇格させ、最後に5段落ちで高得点を狙ったりします。

敵はモンスター、ボス、ドンがあり、ボスは垂直に並んだ穴を掘って2段階連続でおとさないと死にません。ドンにいたっては3つの垂直に並んだ穴に落とさないと倒すことができません。

穴を掘ることができる位置を限定する

では穴を掘ってみましょう。

穴はプレイヤーがいる場所の隣でプレイヤーが向いている方向に作られます。そこでまずプレイヤーがどちらを向いているのかを調べなければなりません。プレイヤーの方向と位置がわかれば穴を掘ります。

垂直に並んだ縦穴を掘る難易度を低下させるためにプレイヤーは移動の最小単位は4ピクセルですが、穴をほることができる場所は16ピクセル単位にします。また横に連続した位置には穴を掘ることはできない仕様にします。

穴を掘ったらその部分に穴が掘られていることが分かるようにマークをつけます。ステージの背景が黒なので、床の穴が掘られた部分を黒で覆ってしまえば穴が開いたかのように見えます。

でははじめましょう。

穴を掘ることができるのはプレイヤーが右または左を向いているときです。プレイヤーがどの方向を向いているかはGameManagerクラスのなかで調べることができます。

プレイヤーが実際に移動するときは移動方向をフィールド変数に格納しておきます。

以下はハシゴの上り下りをするときのプレイヤーのXY座標のズレを補正するための処理です。

Holeクラス

穴を掘る場合はどこに穴を掘ったのかを記憶しておく必要があります。そこで穴を管理するためのHoleクラスを作成します。ゲームではひとつの穴を完成させるために3回掘る動作が必要です。

フィールド変数Depthは穴の深さです。1と2なら未完成の穴、3だとモンスターを落とすことができる完成された穴です。またフィールド変数Enemyは穴にはまってしまった敵オブジェクトです。

穴を掘る場合は本当に穴を掘れる場所なのか確認してから穴をほります。縦に穴を並べて掘りやすくするためにX座標が16(というよりCharctorSizeの半分の整数)の倍数でなければならないという条件をつけます。それ以外の場所に穴を掘ろうとしたら近くのX座標が16の倍数になっている場所に移動して穴を掘ります。

ある数にもっとも近い16の倍数はその数を16で割り、商を四捨五入して16を掛けることで求められます。それからCharctorSizeは他の整数に変更できますが、その場合4の倍数でなければややこしいことになります。

穴を掘ることができるかどうかを判定する

プレイヤーが現在いる位置に穴を掘ることができるかどうかを判定する処理を示します。穴を掘ることができない場合をコメントで示しています。これ以外なら穴を掘ることができます。

実際に穴を掘る

穴を掘ることができるのであれば、その位置に穴を掘ります。プレイヤーを穴を掘るために最適化されたX座標に移動させ穴を掘らせます。

穴を描画するための処理を示します。穴のリストのなかに埋められて深さ0になっているものがあるかもしれないので、これをリストから取り除いています。

hole.HoleRectangleは穴の深さによって異なるサイズの矩形を返します。これをフォーム上に描画するための座標に変換して描画処理をおこないます。

フォームに実際に描画するときの処理を示します。先に穴を描画してそのあと敵を描画します。そうしないと穴にはまった敵がうまく描画されません。

敵が穴にはまったときの処理

次に敵が穴にはまったときの処理を考えます。

敵がすでにはまっている穴があったら引き返します。また敵は穴にはまったあとなにもしないと穴から這い出して穴を埋めてしまいます。この部分はTimerを使って処理をします。穴の深さによってTimer.Intervalをセットして、Timer.Tickイベントが発生したらタイマーをとめて穴から這い出す処理をおこないます。

敵が穴にはまったらその深さによってY座標を大きくします。そのとき元のY座標を記憶しておきます。それから自分がはまった穴とその深さを記憶しておくためのプロパティも用意しておきます。

敵が穴にはまったときの処理

敵が穴にはまったときの処理を示します。まず自分がいる場所に穴があるかどうか調べます。穴があったらハマリです。穴の深さに応じてY座標を変えるとともにTimer.Intervalをセットしてタイマーをスタートさせます。MoveDirectをMoveDirect.Stopにして動かないようにします。

敵が穴から這い出してくる処理

敵が穴から這い出してくる処理です。まずタイマーを止めます。そして3回にわけて少しずつ上昇させ、最終的にハマるときに記憶したY座標をセットします。最後に乱数で右または左に移動させます。穴は埋めてしまうのでDepthに0をセットしてフィールド変数Depthにはnullをセットします。

それから穴から這い出してきた敵はモンスターはボスに、ボスはドンに昇格し、倒しにくくなります。逆にいうと倒したときの点数が大きいのでわざとドンに昇格させて5段落としで高得点ゲットという方法もあるのですが・・・。Lifeを追加する処理を書き足せば実装できますね。

穴の前で引き返す処理

別の敵がすでにハマっている穴があるときは敵はその穴にハマることも超えることもできず、その場でUターンをします。そのための処理を示します。

モンスターの動きに関するメソッドを示します。上で作成したメソッドを使うとこんな感じになります。

敵を穴から落として倒す処理

次に穴をうけてモンスターを倒す処理を考えます。

穴をうめる処理

以下は穴をうめる処理です。まずプレイヤーのZ座標を引数にGetXPositionToDigメソッドを呼び出し、この座標に穴があるかどうかを調べます。穴があればプレイヤーをそこへ移動させます。埋める処理をすることで穴は浅くなります。

穴にはモンスターがいる場合といない場合があります。完成した穴のなかにモンスターがいた場合は深さ0になったときにモンスターを穴から突き落としたことになります。このときはEnemy.Fallメソッドが呼び出され、モンスターは落下します。その場合、モンスターが落ちた穴は縦に何個並べられていたかとモンスターのLifeを比較してモンスターを倒せたかどうかが決まります。

ゲームクリア時の処理

すべての敵を倒したら新しく敵をつくってゲームを継続します。

public class GameManager
{
async Task OnCleared()
{
await Task.Delay(3000);
InitFloors();
}

void InitFloors()
{
Enemies.Add(new Enemy(CharctorSize * 3, GetYFromNumberOfFloors(6)));
Enemies.Add(new Enemy(CharctorSize * 6, GetYFromNumberOfFloors(5)));
Enemies.Add(new Enemy(CharctorSize * 9, GetYFromNumberOfFloors(4)));
Enemies.Add(new Enemy(CharctorSize * 12, GetYFromNumberOfFloors(3)));
Enemies.Add(new Enemy(CharctorSize * 15, GetYFromNumberOfFloors(2)));

PlayerX = 7 * CharctorSize;
PlayerY = 15 * CharctorSize;
Holes.Clear();
}
}

モンスターが落下する処理

これはモンスターが落下する処理です。

敵を倒したときに入る点数を計算する処理を示します。いわゆるザコ敵は100点、ボスは300点、ドンは800点です。高いところから落とすとさらに点数が増えます。

穴の存在によるプレイヤーの動作の制約

穴を掘ることでプレイヤーの動作にも制約がでてきます。

プレイヤーは完成した穴から飛び降りて下の階に移動できる

プレイヤーは完成した穴であればそこから飛び降りて下の階に移動することができます。

プレイヤーが移動できない場所

プレイヤーは水平移動をするときに未完成の穴や敵がハマっている穴のなかに移動することはできません。そのためGameManager.CanMoveLeftメソッドとGameManager.CanMoveRightメソッドには追加の処理が必要です。

あとはこれらをForm1クラスから呼び出せるようにするだけです。

Form1クラスではZキーを押すとプレイヤーが向いている方向の足下に穴をほり、となりのXキーをおすと穴を埋めます。GameManager.DigHole()とGameManager.FillHole()を呼べるようにしました。