今回はステージクリアの判定を実装します。
Contents
ステージクリアの判定方法
プレイヤーが移動する処理がおこなわれるとき、MovePlayerメソッドが実行されますが、その最後にAddPointsIfNeedメソッドが呼び出されます。AddPointsIfNeedメソッドでは点数の加算とステージクリア判定をおこないます。
ステージクリア判定はフィールド変数として宣言されているIsVisitがすべてtrueになっているかどうかでよいのではないかと安易に考えていましたが、実際には違いました。通過した部分を別の色の矩形で塗りつぶしていますが、連続した領域が塗りつぶされていても、IsVisitのなかにはfalseの部分がある場合があるのです(これに気づくのにけっこう時間がかかった)。
そこでIsVisitのある要素がtrueであればそこから離れている距離がCharactorSize / ExpansionRate以内であればIsVisitがfalseであってもtrueとみなします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public partial class Form1 : Form { // 引数として渡した座標の右または下の近い位置にtrueがあればその座標もtrueとみなす bool IsVisitSurroundingPosittions(int x, int y) { for (int i = 0; i <= CharactorSize / ExpansionRate; i++) { for (int j = 0; j <= CharactorSize / ExpansionRate; j++) { if (y + i < 0) continue; if (y + i >= MapSourceHeight) continue; if (x + j < 0) continue; if (x + j >= MapSourceWidth) continue; if (IsVisits[y + i, x + j]) return true; } } return false; } } |
Map[y, x] において POSITION_NONE ではないすべてがIsVisits[y, x] == true、またはIsVisits[y, x]がfalseでもIsVisitSurroundingPosittions(x, y)である場合はステージクリアと判断します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class Form1 : Form { bool CheckStageClear() { for (int y = 0; y < MapSourceHeight; y++) { for (int x = 0; x < MapSourceWidth; x++) { if (!IsVisits[y, x] && Map[y, x] != POSITION_NONE && !IsVisitSurroundingPosittions(x, y)) return false; } } return true; } } |
AddPointsIfNeedメソッドを示します。
IsVisits[y, x]がtrueになるたびに加点していては点数が大きくなりすぎるので、4回に1回の割合で10点を加算します。そして先述のCheckStageClearメソッドでステージクリアかどうか調べてステージクリアの場合はOnStageClearメソッド(後述)を呼び出して次のステージに移ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public partial class Form1 : Form { int EatCount = 0; void AddPointsIfNeed() { if (PlayerX < MapSourceWidth && PlayerY < MapSourceHeight) { if (!IsVisits[PlayerY, PlayerX]) { IsVisits[PlayerY, PlayerX] = true; EatCount++; if (EatCount % 4 == 0) Score += 10; if (CheckStageClear()) OnStageClear(); } } } } |
ステージクリア後の処理
OnStageClearメソッドはタイマーをいったん停止してステージクリアの効果音を鳴らし、次のステージに移動するためのToNextStageメソッド(後述)を呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public partial class Form1 : Form { WMPLib.WindowsMediaPlayer mpStageClear = new WMPLib.WindowsMediaPlayer(); void OnStageClear() { Timer1.Stop(); Timer2.Stop(); // 目立つ塗り残しがあるかもしれないのでIsVisitsをすべてtrueにする for (int y = 0; y < MapSourceHeight; y++) { for (int x = 0; x < MapSourceWidth; x++) { if (Map[y, x] != POSITION_NONE) IsVisits[y, x] = true; } } Invalidate(); // 効果音を鳴らし、敵を倒したときに加算される点数をリセット(100点)する mpStageClear.URL = Application.StartupPath + "\\d.mp3"; AddPointCrashEnemy = 100; Timer timer = new Timer(); timer.Interval = 2000; timer.Tick += (sender, e) => { Timer t = (Timer)sender; t.Stop(); t.Dispose(); ToNextStage(); }; timer.Start(); } } |
次のステージに移るための処理をおこなうToNextStageメソッドを示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public partial class Form1 : Form { void ToNextStage() { // IsVisitsをすべてfalseに for (int y = 0; y < MapSourceHeight; y++) { for (int x = 0; x < MapSourceWidth; x++) IsVisits[y, x] = false; } // プレイヤーと敵を初期の座標へ戻す foreach (Enemy enemy in Enemies) enemy.Reset(); PlayerX = PlayerStartX; PlayerY = PlayerStartY; PlayerDirect = Direct.Stop; // 難易度をあげるためタイマーを少し早める Timer1.Interval -= 4; if (Timer1.Interval < 10) Timer1.Interval = 10; Timer1.Start(); } } |
ゲームとして完成させる
これまで作成してきたクラッシュローラーっぽいゲームを完成させます。
プレイヤーと敵を描画する
まずプレイヤーと敵が色つきの矩形では見た目がイマイチなのでイメージを表示させます。素材は以下を使います。
入手元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public partial class Form1 : Form { Bitmap BitmapPlayer = null; Bitmap BitmapEnemy1 = null; Bitmap BitmapEnemy2 = null; public Form1() { InitializeComponent(); BackColor = Color.Black; CreateMapFromBitmap(); InitTimer(); InitEnemies(); rollerNS =CreateRollerNS(); rollerWE = CreateRollerWE(); Score = 0; // キャラクタを描画するためのイメージを取得する BitmapPlayer = Properties.Resources.player; BitmapEnemy1 = Properties.Resources.enemy1; BitmapEnemy2 = Properties.Resources.enemy2; // タイマーをいったん止める(ゲーム開始時にスタートさせる) Timer1.Stop(); Timer2.Stop(); // 残機 0になったらゲームオーバー Rest = 0; Invalidate(); } } |
プレイヤーと敵を描画する処理を書き換えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public partial class Form1 : Form { void DrawPlayer(Graphics graphics) { //graphics.FillRectangle( // PlayerBrush, // new Rectangle(PlayerX * ExpansionRate + LEFT_MARGIN, PlayerY * ExpansionRate + TOP_MARGIN, CharactorSize, CharactorSize)); graphics.DrawImage(BitmapPlayer, PlayerX * ExpansionRate + LEFT_MARGIN - 6, PlayerY * ExpansionRate + TOP_MARGIN-6, CharactorSize + 12, CharactorSize + 12); } void DrawEnemy(Graphics graphics, Enemy enemy) { if (enemy.ID == 1) //graphics.FillRectangle( // Brushes.Red, // new Rectangle(enemy.X * ExpansionRate + LEFT_MARGIN, enemy.Y * ExpansionRate + TOP_MARGIN, CharactorSize, CharactorSize)); graphics.DrawImage(BitmapEnemy1, enemy.X * ExpansionRate + LEFT_MARGIN - 4, enemy.Y * ExpansionRate + TOP_MARGIN - 4, CharactorSize + 8, CharactorSize + 8); else //graphics.FillRectangle( //Brushes.Blue, //new Rectangle(enemy.X * ExpansionRate + LEFT_MARGIN, enemy.Y * ExpansionRate + TOP_MARGIN, CharactorSize, CharactorSize)); graphics.DrawImage(BitmapEnemy2, enemy.X * ExpansionRate + LEFT_MARGIN - 4, enemy.Y * ExpansionRate + TOP_MARGIN - 4, CharactorSize + 8, CharactorSize + 8); //graphics.DrawString("敵", EnemyFont, Brushes.White, new Point(enemy.X * ExpansionRate - 3 + LEFT_MARGIN, enemy.Y * ExpansionRate + TOP_MARGIN)); } } |
残機を表示する
残機を表示するためのプロパティを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { int MaxRest = 6; int _rest = 0; int Rest { get { return _rest; } set { _rest = value; label2.Text = "残 " + _rest.ToString(); if(_rest <= 0) label2.Text = "GAME OVER Press S key to start"; } } } |
ゲームスタートとゲームオーバー時の処理
ゲームスタートとゲームオーバー時の処理を追加します。Sキーを押したらゲームスタート、残機が0になったらゲームオーバーの処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
public partial class Form1 : Form { BGM bgm = new BGM(); void GameStart() { // 現在プレイ中であるフラグを立っている場合はなにもしない if (IsPlaying) return; // プレイ中であるフラグを立てBGMを鳴らす IsPlaying = true; bgm.PlayBGM(); // 残機を最大値にセット Rest = MaxRest; // 通路のどの部分も通過していない状態にする for (int y = 0; y < MapSourceHeight; y++) { for (int x = 0; x < MapSourceWidth; x++) IsVisits[y, x] = false; } // プレイヤーと敵を初期座標に移動 PlayerX = PlayerStartX; PlayerY = PlayerStartY; PlayerDirect = Direct.Stop; foreach (Enemy enemy in Enemies) enemy.Reset(); // スコアを0にする。敵を倒したときに追加される点数を初期値に戻す Score = 0; AddPointCrashEnemy = 100; // Timer1.Intervalを初期値に戻す Timer1.Interval = INIT_INTERVAL; // タイマースタート Timer1.Start(); Timer2.Start(); } } |
SキーがおされたらGameStartメソッドを呼び出せるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { protected override void OnKeyDown(KeyEventArgs e) { if (e.KeyCode == Keys.Up) IsKeyUp = true; if (e.KeyCode == Keys.Down) IsKeyDown = true; if (e.KeyCode == Keys.Left) IsKeyLeft = true; if (e.KeyCode == Keys.Right) IsKeyRight = true; if (e.KeyCode == Keys.S) GameStart(); base.OnKeyDown(e); } } |
ミスをしたときは残機を1減らして0になったらゲームオーバー処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public partial class Form1 : Form { void OnDeadPlayer() { Timer1.Stop(); Timer2.Stop(); mpPlayerDead.URL = Application.StartupPath + "\\c.mp3"; AddPointCrashEnemy = 100; // ミス時は残機を1減らす Rest--; Timer timer = new Timer(); timer.Interval = 2000; timer.Tick += (sender, e) => { Timer t = (Timer)sender; t.Stop(); t.Dispose(); // 残機を減らした結果 0になったらゲームオーバー処理をする if (Rest <= 0) { // ゲームオーバー時の効果音をならす mpPlayerDead.URL = Application.StartupPath + "\\gameover.mp3"; // BGMを止める bgm.StopBGM(); IsPlaying = false; return; } // 以下はミスしてもゲームオーバーにならなかった場合の処理(既出) PlayerX = PlayerStartX; PlayerY = PlayerStartY; PlayerDirect = Direct.Stop; foreach (Enemy enemy in Enemies) enemy.Reset(); Timer1.Start(); Timer2.Start(); }; timer.Start(); } } |
ローラーから敵が逃げる処理
ローラーで敵に反撃するときの処理ですが、ある程度繰り返したらローラーを使おうとすると敵が逃げる機能を追加します。
そのためにpublicに変更しないといけないものがあるので、フィールド変数をプロパティに変更(getはできるがsetはできない)します。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class Form1 : Form { public bool IsHoldRollerE { get; private set; } = false; public bool IsHoldRollerW { get; private set; } = false; public bool IsHoldRollerN { get; private set; } = false; public bool IsHoldRollerS { get; private set; } = false; public int PlayerX { get; private set; } = 0; public int PlayerY { get; private set; } = 0; public int AddPointCrashEnemy { get; private set; } = 100; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
public class Enemy { public void Move(int targetX, int targetY) { int[,] map = GetMap(); // 巣のなかにいるときは巣の外に出す if (map[Y, X] == Form1.POSITION_NONE) { if(NumberOfTimesToMove > 0) NumberOfTimesToMove--; if(NumberOfTimesToMove <= 0) Y--; return; } bool canUp = false; bool canRight = false; bool canDown = false; bool canLeft = false; if (IsInMap(Y, X)) { canUp = map[Y - 1, X] != Form1.POSITION_NONE && map[Y, X] != Form1.POSITION_BRIDGE_NS_CROSS && map[Y, X] != Form1.POSITION_BRIDGE_WE_CROSS; canRight = map[Y, X + 1] != Form1.POSITION_NONE && map[Y, X] != Form1.POSITION_BRIDGE_NS_CROSS && map[Y, X] != Form1.POSITION_BRIDGE_WE_CROSS; canDown = map[Y + 1, X] != Form1.POSITION_NONE && map[Y, X] != Form1.POSITION_BRIDGE_NS_CROSS && map[Y, X] != Form1.POSITION_BRIDGE_WE_CROSS; canLeft = map[Y, X - 1] != Form1.POSITION_NONE && map[Y, X] != Form1.POSITION_BRIDGE_NS_CROSS && map[Y, X] != Form1.POSITION_BRIDGE_WE_CROSS; } if ( EnemyDirect == Direct.Stop || canUp && canRight || canRight && canDown || canDown && canLeft || canLeft && canUp) { int minCost = 0; EnemyDirect = GetDirect(targetX, targetY, ref minCost, ref EnemyPath); } // 次回の加点が3200点を超えたローラーから逃げる処理を追加する if (ParentForm.AddPointCrashEnemy >= 3200) { if (ParentForm.IsHoldRollerW) { if (Y == ParentForm.PlayerY) EnemyDirect = Direct.Left; } if (ParentForm.IsHoldRollerE) { if (Y == ParentForm.PlayerY) EnemyDirect = Direct.Right; } if (ParentForm.IsHoldRollerN) { if (X == ParentForm.PlayerX) EnemyDirect = Direct.Up; } if (ParentForm.IsHoldRollerS) { if (X == ParentForm.PlayerX) EnemyDirect = Direct.Down; } } // これ以降は変更なし int h = map.GetLength(0); int w = map.GetLength(1); if (EnemyDirect == Direct.Right) { if (X == w - 2) X = 0; else X++; } if (EnemyDirect == Direct.Left) { if (X == 1) X = w - 1; else X--; } if (EnemyDirect == Direct.Down) { if (Y == h - 2) Y = 0; else Y++; } if (EnemyDirect == Direct.Up) { if (Y == 1) Y = h - 1; else Y--; } } } |