今回は当たり判定の処理を実装します。
当たり判定の処理
敵とプレイヤーが重なっているかどうかはプレイヤーと敵の座標を比較すればわかりそうですが、立体交差になっている部分があるので、そのときは除外しなければなりません。両者が同じ橋のうえにいる、または両者とも橋ではない場所にいるのであれば接触していると判断できます。
XY座標それぞれの違いの絶対値の和が CharactorSize / ExpansionRate よりも小さければ接触していることになりますが、もうすこし接近した場所にしたほうが自然にみえるかもしれません。そこでXY座標それぞれの違いの絶対値の和が CharactorSize / ExpansionRateの半分よりも小さいときに接触していると判定しています。
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 |
public partial class Form1 : Form { void CheckHit() { Enemy hitEnemy = null; foreach (Enemy enemy in Enemies) { if (Math.Abs(enemy.X - PlayerX) + Math.Abs(enemy.Y - PlayerY) < CharactorSize / ExpansionRate / 2) { // 近い距離にいても一方が橋のうえで他方がそうではない場所にいる場合は除外する // 両者が同じ橋のうえにいるなら両者は接触している if ( (Map[PlayerY, PlayerX] == POSITION_BRIDGE_NS && Map[enemy.Y, enemy.X] == POSITION_BRIDGE_NS) || (Map[PlayerY, PlayerX] == POSITION_BRIDGE_WE && Map[enemy.Y, enemy.X] == POSITION_BRIDGE_WE) ) { hitEnemy = enemy; break; } // 両者が橋ではない場所にいるなら両者は接触している if ( Map[PlayerY, PlayerX] != POSITION_BRIDGE_NS && Map[PlayerY, PlayerX] != POSITION_BRIDGE_NS_CROSS && Map[PlayerY, PlayerX] != POSITION_BRIDGE_WE && Map[PlayerY, PlayerX] != POSITION_BRIDGE_WE_CROSS && Map[enemy.Y, enemy.X] != POSITION_BRIDGE_NS && Map[enemy.Y, enemy.X] != POSITION_BRIDGE_NS_CROSS && Map[enemy.Y, enemy.X] != POSITION_BRIDGE_WE && Map[enemy.Y, enemy.X] != POSITION_BRIDGE_WE_CROSS ) { hitEnemy = enemy; break; } } } // プレイヤーと敵が重なっている場合 // ローラーをもっている場合は敵を倒したときの処理をおこない // そうでない場合はミスの処理をおこなう if (hitEnemy != null) { if (IsHoldRollerN || IsHoldRollerS || IsHoldRollerW || IsHoldRollerE) OnHitEmemy(hitEnemy); else OnDeadPlayer(); } } } |
敵を倒したときの処理
敵を倒したときの処理です。いったんタイマーを停止させ、敵は最初に出現する位置に戻します。そして加算される点数を表示させます。そしてタイマーを再スタートさせてInvalidateメソッドを呼び出します。
加算される点数は最初は100点、その後、倍々と増えていき、最大で9900点まで増えます。ミスをしたりステージクリアのときはリセットされて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 |
public partial class Form1 : Form { Point PointDrawAddScore = Point.Empty; int AddPointCrashEnemy = 100; WMPLib.WindowsMediaPlayer mpEnemyDead = new WMPLib.WindowsMediaPlayer(); void OnHitEmemy(Enemy enemy) { Timer1.Stop(); Timer2.Stop(); // お好みで効果音を鳴らす mpEnemyDead.URL = Application.StartupPath + "\\b.mp3"; // 倒した敵の近くに加算される点数を表示するための座標をセット // そのあと実際に表示させるためにInvalidateメソッドを呼び出す PointDrawAddScore = new Point(enemy.X * ExpansionRate + LEFT_MARGIN, enemy.Y * ExpansionRate + TOP_MARGIN); Invalidate(); Timer timer = new Timer(); timer.Interval = 2000; timer.Tick += (sender, e) => { Timer t = (Timer)sender; t.Stop(); t.Dispose(); // PointDrawAddScore == Point.Empty になったら加算される点数は表示されなくなる PointDrawAddScore = Point.Empty; enemy.Reset(); // 得点追加の処理 次回の得点は倍(ただし上限は9900点)になる // Scoreプロパティは後述 Score += AddPointCrashEnemy; AddPointCrashEnemy *= 2; if (AddPointCrashEnemy > 9900) AddPointCrashEnemy = 9900; // 停止させていたタイマーをStartさせてゲームを続行 Timer1.Start(); Timer2.Start(); }; timer.Start(); } } |
ミスをしたときの処理です。この場合もいったんタイマーを停止させます。そしてプレイヤーと敵をゲーム開始の位置に戻します。そしてタイマーを再スタートさせて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 { WMPLib.WindowsMediaPlayer mpPlayerDead = new WMPLib.WindowsMediaPlayer(); void OnDeadPlayer() { Timer1.Stop(); Timer2.Stop(); // 効果音を鳴らす。また敵を倒したときに加算される点数をリセットする mpPlayerDead.URL = Application.StartupPath + "\\c.mp3"; AddPointCrashEnemy = 100; Timer timer = new Timer(); timer.Interval = 2000; timer.Tick += (sender, e) => { Timer t = (Timer)sender; t.Stop(); t.Dispose(); // プレイヤーと敵の座標をもとに戻す PlayerX = PlayerStartX; PlayerY = PlayerStartY; PlayerDirect = Direct.Stop; foreach (Enemy enemy in Enemies) enemy.Reset(); // 停止させていたタイマーをStartさせてゲームを続行 Timer1.Start(); Timer2.Start(); }; timer.Start(); } } |
敵を倒したときに加算される点数を表示させるための処理を示します。これをForm1.OnPaintメソッドの最後に追加しておきます。
1 2 3 4 5 6 7 8 9 10 11 |
public partial class Form1 : Form { void DrawAddScoreIfNeed(Graphics graphics) { if (PointDrawAddScore != Point.Empty) { graphics.FillRectangle(PassedBrush, new Rectangle(PointDrawAddScore, new Size(CharactorSize, CharactorSize)) ); graphics.DrawString(AddPointCrashEnemy.ToString(), new Font("MS ゴシック", 16, FontStyle.Bold), Brushes.Red, PointDrawAddScore); } } } |
効果音を鳴らす
プレイヤーがローラーをもって移動しているときに音を鳴らします。
最初だけ普通に音を鳴らし、あとはイベントハンドラ Timer1_Tickが呼び出されるたびに再生が終了していて効果音を鳴らす必要があるときだけ再生処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public partial class Form1 : Form { WMPLib.WindowsMediaPlayer mpPlayerHoldRoller = new WMPLib.WindowsMediaPlayer(); bool IsFirstSoundRollerIfPlayerHold = true; void SoundRollerIfPlayerHold() { if (IsHoldRollerW || IsHoldRollerE || IsHoldRollerN || IsHoldRollerS) { // はじめてローラーを持って移動したとき if (IsFirstSoundRollerIfPlayerHold) { IsFirstSoundRollerIfPlayerHold = false; mpPlayerHoldRoller.URL = Application.StartupPath + "\\a.mp3"; } // 次回以降でローラーを持って移動したときで効果音の再生が完了しているとき else if (mpPlayerHoldRoller.playState == WMPPlayState.wmppsStopped) mpPlayerHoldRoller.controls.play(); } } } |
Scoreプロパティに値がセットされたらスコアを表示します。フォームの左上あたりにLabelをはりつけて(label1)そこにスコアを表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public partial class Form1 : Form { int _score = 0; int Score { get { return _score; } set { _score = value; label1.Text = "Score " + _score; } } } |
最後にイベントハンドラ Timer1_Tickのなかで CheckHitメソッドとSoundRollerIfPlayerHoldメソッドを呼びだすようにすれば当たり判定の処理は完成です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { private void Timer1_Tick(object sender, EventArgs e) { MovePlayer(); MoveEnemies(); MoveRollerIfPlayerHold(); SoundRollerIfPlayerHold(); CheckHit(); Invalidate(); } } |
BGMを鳴らす
効果音を鳴らしたので、ついでにBGMを鳴らす処理も考えます。
まずはこんなクラスを作成します。PlayBGMメソッドが実行されるとBGMの再生が開始されます。またタイマーで300ミリ秒ごとに再生が完了しているかチェックして完了しているときはもう一度最初から再生しなおします。再生を終了するときはStopBGMメソッドを実行します。このときタイマーも停止しないとすぐに再生が再開されてしまいます。
BGMクラスのオブジェクトをフィールド変数として作成し、ゲーム開始のタイミングでPlayBGMメソッド、終了のタイミングでStopBGMメソッドを実行すればよいのではないでしょうか?
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 |
public class BGM { WMPLib.WindowsMediaPlayer mpBGM = new WMPLib.WindowsMediaPlayer(); Timer timer = new Timer(); public BGM() { timer.Interval = 300; //300msecごとに処理を実行 timer.Tick += (sender, e) => { //止まっていたら再び再生 if (mpBGM.playState == WMPPlayState.wmppsStopped) { mpBGM.controls.play(); } }; } public void PlayBGM() { //以下を追加 mpBGM.URL = Application.StartupPath + "\\8bit29.mp3"; timer.Start(); } public void StopBGM() { mpBGM.controls.stop(); timer.Stop(); } } |