学習記事一覧 · WinFormsRPG

【学習】WinFormsで作るRPG入門(2)敵を複数にする 〜配列と List

第1回 では、Enemy1体だけ 持ち、攻撃ボタンで 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 攻撃

lblEnemyListAutoSize = 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 で分岐)