今回は戦闘とエンディング処理をおこないます。戦闘がおきるのはフィールド上と敵の城です。
まずフィールド上における戦闘の処理を実装します。
Contents
BeginBattleイベントの定義
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 |
public class Map1 : Map { Random Random1 = new Random(); public event EventHandler BeginBattle; int moveCount = 0; public override void MovePlayer(Direct direct) { // フィールド上を移動すると64分の1の確率で戦闘が発生する // ただし立て続けに戦闘がおきることを防ぐために、前回の戦闘から32回未満の移動ではなにもおきない moveCount++; if (Random1.Next(64) == 0 && moveCount > 32) { moveCount = 0; BeginBattle.Invoke(this, new EventArgs()); BgmStop(); return; } // 城がある場合は城のなかに入る(既出) if (direct == Direct.Up) { if (IsCastle(PlayerPoint.X, PlayerPoint.Y - 4)) { EnterCastle?.Invoke(this, new EventArgs()); BgmStop(); return; } if (IsEnenyCastle(PlayerPoint.X, PlayerPoint.Y)) { EnterEnemyCastle?.Invoke(this, new EventArgs()); BgmStop(); return; } } base.MovePlayer(direct); } } |
Form1クラスではBeginBattleイベントに対応する処理をおこないます。
Formに自分のHPとMPを表示するLabelと敵の姿を描画するためのPictureBoxをはりつけておきます。
<でざいな>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); DoubleBuffered = true; InitDungeons(); // HPとMPに最大値を設定してLabelに表示する Battle1.InitPlayerStatus(); label2.Text = String.Format("HP {0} MP {1}", Battle1.PlayerHP, Battle1.PlayerMP); _map = mapCastle; mapCastle.BgmStart(); mapCastle.SetPlayerQueen(); // メッセージを表示するLabelと敵を表示するPictureBoxは最初は非表示にする label1.Visible = false; pictureBox1.Visible = false; } } |
InitDungeonsメソッド内でBeginBattleイベントに対応できるようにしておきます。
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 |
public partial class Form1 : Form { void InitDungeons() { map1 = new Map1(this); map1.EnterCastle += Map1_EnterCastle; map1.EnterEnemyCastle += Map1_EnterEnemyCastle; // フィールドにおけるザコ敵との戦闘 map1.BeginBattle += Map1_BeginBattle; mapCastle = new MapCastle(this, 0); mapCastle.ExitCastle += MapCastle1_ExitCastle; mapCastle.ShowMessage += MapCastle_ShowMessage; mapEnemyCastle = new MapCastle(this, 1); mapEnemyCastle.ExitCastle += MapCastle1_ExitCastle; // 最終ボスとの戦闘 mapEnemyCastle.BeginBattleBoss += MapEnemyCastle_BeginBattleBoss; } private void Map1_BeginBattle(object sender, EventArgs e) { label1.Text = "敵に襲われた!!"; label1.Visible = true; isKeyDown = true; isBattleFirst = true; // 敵は2種類とする Random random = new Random(); if(random.Next(2) == 0) Enemy1(); else Enemy2(); } } |
敵はスライムともうひとつ(自分でもよくわからないキャラ)です。HPは3と7のザコ敵の名にふさわしい敵キャラです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public partial class Form1 : Form { void Enemy1() { List<string> vs = new List<string>(); vs.Add("スライムが現れた!!"); EnemyStatus enemy = new EnemyStatus("スライム", 3); // HPは3 Battle1.BeginBattle(enemy); pictureBox1.BackColor = Color.Black; pictureBox1.Image = Properties.Resources.enemy1; Messages.AddRange(vs); } void Enemy2() { List<string> vs = new List<string>(); vs.Add("魔物が現れた!!"); EnemyStatus enemy = new EnemyStatus("魔物", 7); // HPは7 Battle1.BeginBattle(enemy); pictureBox1.BackColor = Color.Black; pictureBox1.Image = Properties.Resources.enemy2; Messages.AddRange(vs); } } |
戦闘関連のクラス
次に戦闘関連のクラスを示します。
EnemyStatusクラス
まず敵の状態を管理するクラス EnemyStatusをつくります。
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 class EnemyStatus { public EnemyStatus(string name, int hp) { Name = name; HP = hp; } public int HP { private set; get; } public string Name { private set; get; } public void Damage(int damage) { HP -= damage; } } |
BattleResultクラス
戦闘をするとボタンをおすことでコマンド選択時はコマンドの選択、それ以外のときは結果を一行ごとに表示します。そして結果を表示する文字列はBattleResultオブジェクトに格納し、これが複数存在するときはリストに格納してひとつずつ取り出して表示させます。
そのために必要となるBattleResultクラスを示します。
ひとつのアクションをすることで、敵へのダメージ、敵の反撃によるダメージが表示されるのですが、HPの変化はじっさいにダメージをうけたときにあわせて変更させたいものです。そこで
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 |
public class BattleResult { public BattleResult(string message, int hp, int mp, string soundEffect) { Message = message; HP = hp; MP = mp; SoundEffect = soundEffect; } public string Message { private set; get; } public int HP { private set; get; } public int MP { private set; get; } public string SoundEffect { private set; get; } } |
Battleクラス
次にBattleクラスを示します。ここではプレイヤーのHPとMP、敵を攻撃したりされたときのダメージの計算をおこないます。また敵は1体だけとします。
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 class Battle { public int PlayerHP = 0; public int PlayerMP = 0; public int PlayerMaxHP = 0; public int PlayerMaxMP = 0; Random Random = new Random(); EnemyStatus EnemyStatus1 = null; List<BattleResult> Results = new List<BattleResult>(); public Battle() { IsWaitCommand = true; } // コマンド待ちの状態か否か? public bool IsWaitCommand { set; get; } // ゲーム開始時のプレイヤーのHPとMPは30とする public void InitPlayerStatus() { PlayerMaxMP = 30; PlayerMaxHP = 30; PlayerMP = PlayerMaxMP; PlayerHP = PlayerMaxHP; } } |
戦闘が開始されるときはBeginBattleメソッドで敵をEnemyStatus1に格納します。そしてコマンド入力可能の状態にします。
1 2 3 4 5 6 7 8 |
public class Battle { public void BeginBattle(EnemyStatus enemyStatus) { EnemyStatus1 = enemyStatus; IsWaitCommand = true; } } |
EnemyStatus1がnullであり、Results.Countが0のとき戦闘が終了し、メッセージの表示も完全に終了したことになります。そうでない場合は戦闘中であるとみなします。
1 2 3 4 5 6 7 |
public class Battle { public bool IsBattle() { return EnemyStatus1 != null || Results.Count > 0; } } |
PopResultメソッドは戦闘に関するメッセージのうち表示されていないもので最初に格納されたものを返します。GetMessageCountメソッドはまだ表示されていない表示待ちのメッセージの個数を返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class Battle { public BattleResult PopResult() { if (Results.Count == 0) return null; BattleResult result = Results[0]; Results.RemoveAt(0); return result; } public int GetMessageCount() { return Results.Count; } } |
敵を攻撃するメソッド
敵を攻撃するコマンドが選択されたときの処理を示します。乱数で敵に与えるダメージを決定し、敵の残りのHPを計算します。0以下になったら敵は死んだことになり、戦闘も終了します。そうでない場合は敵による攻撃の処理がおこなわれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Battle { public void GetResultsOnAttack() { Results.Add(new BattleResult("勇者の攻撃", -1, -1)); int damage = Random.Next(10) + 1; EnemyStatus1.Damage(damage); // メッセージを表示するタイミングで効果音を鳴らせるようにmp3ファイルのパスを渡す // BattleResultクラスのコンストラクタの引数が-1のときはこのメッセージが表示されるときHPMPは変更されない Results.Add(new BattleResult(EnemyStatus1.Name + "に" + damage + "のダメージ", -1, -1, Application.StartupPath + "\\attack.mp3")); if (EnemyStatus1.HP <= 0) { Results.Add(new BattleResult(EnemyStatus1.Name + "を倒した", -1, -1, Application.StartupPath + "\\battle-end.mp3")); EnemyStatus1 = null; } EnemyAttack(); } } |
HPを回復するメソッド
プレイヤーが回復魔法を使ったときの処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class Battle { public void GetResultsOnRecover() { // 安易だがMP3の消費でHP全回復とする PlayerMP -= 3; PlayerHP = PlayerMaxHP; Results.Add(new BattleResult("勇者は回復呪文を唱えた", -1, -1, "")); // このメッセージが表示されるタイミングでForm1で表示されているプレイヤーのHPとMPを変更する Results.Add(new BattleResult("勇者のHPが回復した", PlayerHP, PlayerMP, Application.StartupPath + "\\up.mp3")); EnemyAttack(); } } |
敵が攻撃してきたときの処理
敵が攻撃してきたときの処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Battle { void EnemyAttack() { if (EnemyStatus1 == null) return; int damage = Random.Next(5) + 1; PlayerHP -= damage; if (PlayerHP < 0) PlayerHP = 0; Results.Add(new BattleResult(EnemyStatus1.Name + "の攻撃", -1, -1)); Results.Add(new BattleResult("勇者に" + damage + "のダメージ", PlayerHP, PlayerMP, Application.StartupPath + "\\damage.mp3")); if (PlayerHP == 0) { Results.Add(new BattleResult("勇者は傷つき倒れた", -1, -1, Application.StartupPath + "\\damage.mp3")); // プレイヤー敗北の場合も戦闘終了なのでEnemyStatus1 = nullとする EnemyStatus1 = null; } } } |
プレイヤーのHPとMPを全回復させる処理
王女さまに話しかけるとプレイヤーのHPとMPを全回復させますが、そのためのメソッドをつくります。
1 2 3 4 5 6 7 8 |
public class Battle { public void Recover() { PlayerMP = PlayerMaxMP; PlayerHP = PlayerMaxHP; } } |
Form1クラスにおける処理
Form1クラスにおける処理を示します。
これは王女さまに話しかけるとプレイヤーのHPとMPを全回復させる処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { void ShowMessageOnMapCastle(ShowMessageArgs e) { if (Messages.Count > 0) return; string[] vs = e.Message.Split('\n'); label1.Text = vs[0]; label1.Visible = true; isKeyDown = true; if(vs.Length > 1) Messages.AddRange(vs.Skip(1)); Battle1.Recover(); label2.Text = String.Format("HP {0} MP {1}", Battle1.PlayerHP, Battle1.PlayerMP); } } |
キー操作にかんする処理
Map1.BeginBattleイベント関連の処理は冒頭で示したので、キーがおされたときの処理を示します。
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 |
public partial class Form1 : Form { protected override void OnKeyDown(KeyEventArgs e) { if (isEnding) return; if (OnKeyDownBattle(e)) return; if(OnKeyDownShowMessage()) return; if (_map != null) { if (e.KeyCode == Keys.Left) _map.MovePlayer(Direct.Left); if (e.KeyCode == Keys.Up) _map.MovePlayer(Direct.Up); if (e.KeyCode == Keys.Right) _map.MovePlayer(Direct.Right); if (e.KeyCode == Keys.Down) _map.MovePlayer(Direct.Down); Invalidate(); } base.OnKeyDown(e); } } |
戦闘中にキーを押したときの処理を示します。
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
public partial class Form1 : Form { // 対ボス戦、対ザコ敵戦がはじまって初めてキーが押されたことを示すフラグ bool isBossFirst = false; bool isBattleFirst = false; // たったいま戦闘が終わったことを示すフラグ bool isLastBattle = false; // 選択されたコマンド(0なら攻撃、1なら回復魔法) int command = 0; bool OnKeyDownBattle(KeyEventArgs e) { if (Battle1.IsBattle() && Messages.Count == 0) { if (!isKeyDown) { isKeyDown = true; // これまでの戦闘にかんするメッセージをすべて表示し終わっているのであれば // ユーザーに対して次のコマンドを入力するように要求する if (Battle1.GetMessageCount() == 0 && !Battle1.IsWaitCommand) { command = 0; label1.Text = "⇒ 攻撃\n 回復魔法"; Battle1.IsWaitCommand = true; } // これまでの戦闘にかんするメッセージをすべて表示し終わっていて // コマンド待ちの状態でユーザーがキーを押した場合である。この場合は // (1)いま対ボス戦または対ザコ敵戦が開始されたところであればコマンド選択の文字列を表示する // (2)上下のキーがおされたのであれば現在選択されているコマンドがわかるように表示する // (3)それ以外のときは現在選択されているコマンドを実行する else if (Battle1.GetMessageCount() == 0 && Battle1.IsWaitCommand) { if (isBossFirst || isBattleFirst) { // 対ボス戦または対ザコ敵戦が開始されたところである pictureBox1.Visible = true; pictureBox1.BackColor = Color.Black; if(isBossFirst) pictureBox1.Image = Properties.Resources.big_boss; command = 0; label1.Text = "⇒ 攻撃\n 回復魔法"; Battle1.IsWaitCommand = true; PlayBgmLastBoss(); // 対ボス戦も対ザコ敵戦もBGMは同じものをつかう(手抜き) // フラグをクリア isBattleFirst = false; isBossFirst = false; return true; } // 上下のキーでコマンドを切り替えられるようにする // それ以外の場合は選択されているコマンドを実行する if (e.KeyCode == Keys.Up) { label1.Text = "⇒ 攻撃\n 回復魔法"; command = 0; } else if (e.KeyCode == Keys.Down) { label1.Text = " 攻撃\n⇒ 回復魔法"; command = 1; } else { Battle1.IsWaitCommand = false; if (command == 0) Battle1.GetResultsOnAttack(); else Battle1.GetResultsOnRecover(); BattleResult result = Battle1.PopResult(); if (result != null) { label1.Text = result.Message; // 攻撃または回復コマンドが実行されたので効果音を鳴らす WMPLib.WindowsMediaPlayer mplayer = new WMPLib.WindowsMediaPlayer(); mplayer.URL = Application.StartupPath + "\\decision.mp3"; } } } // これまでの戦闘にかんするメッセージをすべて表示し終わっていないので、それを表示する else { BattleResult result = Battle1.PopResult(); if (result != null) { label1.Text = result.Message; // もし効果音が設定されていたのであれば鳴らす if (result.SoundEffect != "") { WMPLib.WindowsMediaPlayer mplayer = new WMPLib.WindowsMediaPlayer(); mplayer.URL = result.SoundEffect; } // HPとMPに変動があるのであれば表示を変更する if (result.HP != -1) { label2.Text = String.Format("HP {0} MP {1}", result.HP, result.MP); } } } // 戦闘が終了し、戦闘にかんするすべてのメッセージが表示された場合 // isLastBattleフラグをセットして戦闘のBGMを終了する。 if (!Battle1.IsBattle() && Battle1.GetMessageCount() == 0) { isLastBattle = true; StopBgmLastBoss(); } } return true; } return false; } } |
戦闘が終了してすべてのメッセージが表示され終わっている場合、しかもlabel1が表示されているときにキーが押されたのであれば、それは戦闘が終了してはじめてキーが押されたことになります。この場合はBattle1_Finishedメソッドを呼び出します。
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 |
public partial class Form1 : Form { bool OnKeyDownShowMessage() { if (Messages.Count > 0) { if (!isKeyDown) { isKeyDown = true; string str = Messages[0]; label1.Text = str; label1.Visible = true; Messages.RemoveAt(0); } } else if (Messages.Count == 0 && label1.Visible) { if (!isKeyDown) { isKeyDown = true; label1.Visible = false; // 戦闘後、はじめてキーが押されたのであればBattle1_Finishedメソッドを呼び出す if (isLastBattle) { isLastBattle = false; pictureBox1.Visible = false; Battle1_Finished(); } } return true; } return false; } } |
対ボス戦の処理
敵の城で魔王に話しかけると戦闘が開始されます。その処理を示します。
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 |
public partial class Form1 : Form { bool isBoss = false; // この戦闘が対ボス戦であることを示すフラグ private void MapEnemyCastle_BeginBattleBoss(object sender, EventArgs e) { if (Messages.Count > 0) return; label2.Visible = true; isBoss = true; isBossFirst = true; EnemyStatus enemy = new EnemyStatus("魔王", 100); Battle1.BeginBattle(enemy); label1.Text = "よく来たな。ここがお前の墓場だ。4ね!"; label1.Visible = true; isKeyDown = true; Battle1.IsWaitCommand = true; label2.Text = String.Format("HP {0} MP {1}", Battle1.PlayerHP, Battle1.PlayerMP); List<string> vs = new List<string>(); vs.Add("魔王が現れた!!"); Messages.AddRange(vs); } } |
戦闘終了時の処理
対ボス戦でもそれ以外でも戦闘が終了するとBattle1_Finishedメソッドが呼び出されます。ザコ敵との戦闘に勝利した場合はとくに処理はおこなわれません。プレイヤーが死亡した場合は最初の城に戻され、「おお、死んでしまうとは情けない」と表示させます。そのあとHPとMPをゲーム開始と同じ状態に戻します。
対ボス戦に勝利した場合はエンディングの処理に移ります。それ以外のときはフィールド上におけるBGMを再生します。
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 |
public partial class Form1 : Form { private void Battle1_Finished() { if (Battle1.PlayerHP == 0) { // 死んだ場合は最初の城に戻る _map = mapCastle; mapCastle.BgmStart(); mapCastle.SetPlayerQueen(); Invalidate(); label1.Visible = true; label1.Text = "おお、死んでしまうとは情けない"; // HPMPをもとに戻す map1.InitPlayer(); Battle1.InitPlayerStatus(); label2.Text = String.Format("HP {0} MP {1}", Battle1.PlayerHP, Battle1.PlayerMP); } else { if (isBoss) { // エンディングではプレイヤーのHPやMPは関係ないので隠す label2.Visible = false; Ending(); } else map1.BgmStart(); } } } |
エンディングの処理
エンディングの処理を示します。エンディングが始まるとキー操作を無効にします(OnKeyDownメソッド内でisEndingが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 26 27 28 29 30 31 32 33 34 35 36 37 |
public partial class Form1 : Form { bool isEnding = false; int EndingSeconds = 0; void Ending() { isEnding = true; _map = mapCastle; mapCastle.SetPlayerQueen(); mapCastle.BgmStart(); Invalidate(); Timer timer = new Timer(); timer.Interval = 1000; timer.Tick += Timer_Tick; timer.Start(); void Timer_Tick(object sender, EventArgs e) { Timer t = (Timer)sender; EndingSeconds++; if (EndingSeconds == 1) { label1.Visible = true; label1.Text = "魔王を倒してくれてありがとうございました。"; } if (EndingSeconds == 5) { label1.Visible = true; label1.Text = "ゲームクリア"; t.Stop(); t.Dispose(); } } } } |