【学習】WinFormsで作るRPG入門(2)敵を複数にする 〜配列と List〜
第1回 では、Enemy を 1体だけ 持ち、攻撃ボタンで HP を減らしました。
ゲームでは 複数の敵 を同時に扱いたくなるので、今回は 同じ型のデータをたくさん並べる 考え方を足します。
今回の最小ゴール
- 敵を 複数体(例: スライム2体とゴブリン1体)用意する
- 攻撃のたびに、いま狙っている敵の HP だけが減る
- その敵の HP が 0 になったら、自動で次の生きている敵 をターゲットにする
- 全員の HP が 0 になったら「勝利」など分かる表示にする(攻撃ボタンは無効化)
前回からの差分
| 第1回 | 第2回 |
|---|---|
Enemy enemy(1体) |
List<Enemy> で複数体を管理 |
| ターゲットは常にその1体 | targetIndex で「いま攻撃する敵」を指す |
UpdateUI は1体分 |
全員分の状態表示+現在ターゲットの表示 |
新しく出てくるキーワード: List<Enemy>、Add(またはコレクション初期化子)、インデックス、生きている敵へ進む ループ
配列と List のちがい(ざっくり)
| 種類 | 特徴 |
|---|---|
Enemy[] 配列 |
作るときに 長さが決まる。enemies[0], enemies[1] のように番号で触る |
List<Enemy> |
あとから Add で増やせる。[0] のように番号で触れるところは配列と似ている |
教材や実務では List<T> をよく使うので、今回の完成例は List<Enemy> にします。配列版は「中身は同じで、型宣言と追加方法だけ違う」と理解しておけば十分です。
// 配列の例(人数が最初から決まっているとき)
Enemy[] enemies = new Enemy[3];
enemies[0] = new Enemy("スライムA", 30);
// …
// List の例(今回の方針)
List<Enemy> enemies = new List<Enemy>();
enemies.Add(new Enemy("スライムA", 30));using System.Collections.Generic; が必要です(Visual Studio が自動で足すことも多いです)。
ソリューションとプロジェクトを作る
- テンプレート: Windows フォーム アプリ(.NET / C#)
- プロジェクト名: 任意(例:
MiniRpgStep2)
第1回から続けて書き換える場合は、同じプロジェクトのまま フィールドと UI を増やして も構いません。
事前学習(任意): List の感覚をコンソールで固めたい場合は、クラス気づき学習:Listで人数を増やす も参照してください。
フォームに配置する
第1回に加えて、いま誰を狙っているか と 全員の状態 が分かると学習しやすいです。
| コントロール | 名前(例) | 用途 |
|---|---|---|
| Label | lblPlayerHp |
プレイヤー HP |
| Label | lblTarget |
現在のターゲット名 |
| Label | lblEnemyHp |
ターゲットの HP |
| Label | lblEnemyList |
全敵の一覧(複数行) |
| Button | btnAttack |
攻撃 |
lblEnemyList は AutoSize = false にし、十分な高さを取ると見やすいです。
Player / Enemy クラス
第1回と 同じ で構いません(Player.AttackEnemy(Enemy enemy) をそのまま使います)。
Form1 のフィールド
using System.Collections.Generic;
using System.Text;
// ...
Player player;
List<Enemy> enemies;
int targetIndex;enemies: 登場する敵の並びtargetIndex: 「今enemiesのどれを攻撃するか」(0 から始まる番号)
初期化(Form1_Load)
private void Form1_Load(object sender, EventArgs e)
{
player = new Player("勇者", 100, 20);
enemies = new List<Enemy>
{
new Enemy("スライムA", 30),
new Enemy("スライムB", 40),
new Enemy("ゴブリン", 60),
};
targetIndex = 0;
EnsureValidTarget();
UpdateUI();
}EnsureValidTarget は、HP が 0 の敵をスキップして、次の生きている敵の番号に targetIndex を合わせるメソッドです(次節)。
ターゲットを進める(心臓部)
先頭から順に見て、HP が残っている敵 が見つかるまで targetIndex を進めます。
private void EnsureValidTarget()
{
while (targetIndex < enemies.Count && enemies[targetIndex].Hp <= 0)
{
targetIndex++;
}
}- 全員倒したあとは
targetIndex >= enemies.Countになります(「もう攻撃する相手がいない」状態)。
攻撃ボタン(Click)
private void btnAttack_Click(object sender, EventArgs e)
{
EnsureValidTarget();
if (targetIndex >= enemies.Count)
{
UpdateUI();
return;
}
Enemy target = enemies[targetIndex];
player.AttackEnemy(target);
if (target.Hp < 0)
target.Hp = 0;
EnsureValidTarget();
UpdateUI();
}第1回と同じく HP の下限を 0 にそろえたあと、倒れた敵を飛ばすために EnsureValidTarget をもう一度呼びます。
UI 更新
全員の名前と HP を 複数行 で出しつつ、ターゲット用ラベルも更新します。
private void UpdateUI()
{
lblPlayerHp.Text = $"HP: {player.Hp}";
var sb = new StringBuilder();
foreach (var enemy in enemies)
{
sb.AppendLine($"{enemy.Name}: {enemy.Hp}");
}
lblEnemyList.Text = sb.ToString();
if (targetIndex >= enemies.Count)
{
lblTarget.Text = "ターゲット: なし(勝利!)";
lblEnemyHp.Text = "-";
btnAttack.Enabled = false;
return;
}
Enemy current = enemies[targetIndex];
lblTarget.Text = $"ターゲット: {current.Name}";
lblEnemyHp.Text = $"HP: {current.Hp}";
btnAttack.Enabled = true;
}プログラムの流れ
Form1_Load
→ List に敵を複数入れる
→ targetIndex = 0 → EnsureValidTarget
→ UpdateUI
btnAttack_Click
→ EnsureValidTarget(死んでいる番号を飛ばす)
→ まだ敵がいれば AttackEnemy
→ HP 下限 0
→ EnsureValidTarget(倒した直後に次へ)
→ UpdateUI(全員 0 なら勝利表示・ボタン無効)Unity での当たり所
WinForms では List<Enemy> と targetIndex で「複数体のデータ」と「いまの1体」を表しています。
Unity ではよく、次のような形になります(イメージです)。
| WinForms(今回) | Unity(イメージ) |
|---|---|
List<Enemy>(データだけのクラス) |
List<EnemyHealth> や「敵用コンポーネント」の参照リスト |
targetIndex |
「いまロックオンしている Transform」やリスト上の番号 |
foreach で一覧表示 |
各 GameObject の上に UI を出す/1つのログにまとめる |
「複数キャラを配列/リストで持ち、インデックスや参照で『今の相手』を決める」 という発想は、そのまま移植しやすいです。見た目は GameObject を並べますが、HP を減らす処理の流れは近いです。
よくあるつまずき
「targetIndex がおかしい」
- 攻撃の 前後で
EnsureValidTargetを呼ぶと、倒した直後に次の敵へ進みやすくなります。 - ループ条件は
targetIndex < enemies.Count && enemies[targetIndex].Hp <= 0かどうか、紙に書いて追うと安心です。
「List と配列、どちらを覚えればいい?」
- まず
List<T>で OK です。配列は「長さ固定・番号で触る」練習として触れておけば十分なことが多いです。
発展アイデア(次の記事へ)
- ランダムなダメージや、攻撃対象をランダムに選ぶ(
Random) - コマンド選択(攻撃/回復を
ifで分岐)→ 第3回
シリーズ全体の目次は WinFormsでRPG入門 〜Unityへ繋がる設計の考え方〜(固定ページ) を参照してください。
まとめ
- 敵が複数になると
List<Enemy>のような「同じ型のコレクション」が必要になる targetIndex+EnsureValidTargetで「生きている敵だけを順に攻撃する」が表現できる- Unity でも リストと現在ターゲット は同様の考え方で出てくる
- 次回: コマンド選択(
ifで分岐)