【学習】WinFormsで作るRPG入門(3)コマンド選択 〜if で分岐〜
第2回 までで、複数の敵 に順番に攻撃し、全滅で勝利する流れが作れました。
RPG では 「いま何をするか」 を選ばせる場面がほぼ必ず出てきます。今回は 攻撃 と 回復 を選べるようにし、if で処理を分岐する練習をします。
今回の最小ゴール
- ラジオボタンで「攻撃」「回復」のどちらかを選ぶ
- 決定ボタン1つで、選んだ内容に応じて処理が変わる
- 攻撃: 第2回と同じ(ターゲットの HP を減らす・倒したら次へ)
- 回復: 自分の HP を一定量回復する(上限
MaxHpを超えない)
- 勝利後は第2回と同様、決定ボタンを無効にする
前回からの差分
| 第2回 | 第3回 |
|---|---|
btnAttack = 常に攻撃 |
btnAction(決定) + ラジオでコマンド選択 |
Player は攻撃だけ |
MaxHp と Heal(回復)を追加 |
| Click に攻撃処理が直書き | if / else if で「攻撃用処理」「回復用処理」に分ける |
新しく出てくるキーワード: RadioButton.Checked、GroupBox(任意)、分岐、MaxHp(上限付きの状態)
UI の考え方(ラジオボタン)
1つだけ選ばせたいときは ラジオボタンを使ってみよう が向いています。同じグループ内では、どれか1つだけ Checked になります。
| コントロール | 名前(例) | 用途 |
|---|---|---|
| GroupBox | grpCommand |
「コマンド」枠(なくても動くが、見やすい) |
| RadioButton | rdoAttack |
攻撃を選ぶ |
| RadioButton | rdoHeal |
回復を選ぶ |
| Button | btnAction |
決定(第2回の btnAttack の役割を兼ねる) |
| (第2回までの Label 類) | そのまま | HP 表示・敵一覧など |
デザイナで rdoAttack の Checked を true にしておくと、起動直後は「攻撃」が選ばれた状態になります。
Player クラス(MaxHp と Heal を追加)
Enemy は第2回までと 同じ で構いません。Player だけ拡張します。
public class Player
{
public string Name { get; set; }
public int Hp { get; set; }
public int MaxHp { get; set; }
public int Attack { get; set; }
public Player(string name, int hp, int attack)
{
Name = name;
Hp = hp;
MaxHp = hp;
Attack = attack;
}
public void AttackEnemy(Enemy enemy)
{
enemy.Hp -= Attack;
}
// HP を増やす。MaxHp を超えない。
public void Heal(int amount)
{
Hp += amount;
if (Hp > MaxHp)
Hp = MaxHp;
}
}MaxHp: 戦闘開始時の HP を上限として覚えておく例です(ポーションやレベルアップで上限が変わるゲームでは、別の持ち方にもできます)。Heal: 「回復はプレイヤー自身の責務」とし、上限チェックをクラス内に閉じると、フォーム側のifがすっきりします。
Form1 のフィールド(第2回+α)
using System.Collections.Generic;
using System.Text;
// ...
Player player;
List<Enemy> enemies;
int targetIndex;UI 上のラジオ・ボタンはデザイナがフィールドを生成するので、通常は 追加のフィールドは不要です(rdoAttack などは Form1.Designer.cs にあります)。
初期化(Form1_Load)
第2回と同じく enemies を用意し、Player は MaxHp がコンストラクタでセットされる形にします。
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();
}ターゲット調整(第2回と同じ)
private void EnsureValidTarget()
{
while (targetIndex < enemies.Count && enemies[targetIndex].Hp <= 0)
{
targetIndex++;
}
}攻撃だけを切り出す(DoAttack)
第2回の btnAttack_Click の中身を DoAttack メソッドにまとめます。
private void DoAttack()
{
EnsureValidTarget();
if (targetIndex >= enemies.Count)
return;
Enemy target = enemies[targetIndex];
player.AttackEnemy(target);
if (target.Hp < 0)
target.Hp = 0;
EnsureValidTarget();
}回復(DoHeal)
回復量は定数にしておくと、あとから数字を変えやすいです。
private const int HealAmount = 15;
private void DoHeal()
{
player.Heal(HealAmount);
}決定ボタン:if でコマンド分岐
ここが第3回の中心です。Checked が true のラジオに応じて、呼ぶ処理を変えます。
private void btnAction_Click(object sender, EventArgs e)
{
if (rdoAttack.Checked)
{
DoAttack();
}
else if (rdoHeal.Checked)
{
DoHeal();
}
UpdateUI();
}発展: コマンドが増えたら else if が続きます。さらに増える場合は switch や「コマンド番号」の列挙型に分ける手もあります(第4回以降の整理の題材になります)。
UI 更新(第2回を btnAction に合わせる)
btnAttack をやめたので、無効化するボタン名を btnAction にします。
private void UpdateUI()
{
lblPlayerHp.Text = $"HP: {player.Hp} / {player.MaxHp}";
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 = "-";
btnAction.Enabled = false;
return;
}
Enemy current = enemies[targetIndex];
lblTarget.Text = $"ターゲット: {current.Name}";
lblEnemyHp.Text = $"HP: {current.Hp}";
btnAction.Enabled = true;
}プレイヤー HP 表示に / MaxHp を足すと、回復の効果が分かりやすくなります。
プログラムの流れ
btnAction_Click
→ rdoAttack がオン?
はい → DoAttack(EnsureValidTarget → 攻撃 → 再調整)
→ そうでなければ rdoHeal がオン?
はい → DoHeal(Player.Heal)
→ UpdateUI(勝利なら btnAction を無効)Unity での当たり所
WinForms では ラジオ+1ボタンで「選んだ行動」を決めています。
Unity では次のような対応がよくあります。
| WinForms(今回) | Unity(イメージ) |
|---|---|
rdoAttack.Checked |
UI の Toggle / ボタン群、またはメニューインデックス |
btnAction_Click 内の if |
同じ if を Update や UI イベントから呼ぶ |
DoAttack / DoHeal |
メソッド名はそのままにして、入力だけ差し替え |
分岐の中身(HP の増減・リストの更新)は、エンジンが変わっても型を合わせやすいです。Unity では「コマンド入力 → 1ターン進める」にすると、ターン制 RPG の核に近づきます。
よくあるつまずき
「ラジオを変えても攻撃しかしない」
Checkedを確認している 名前(rdoAttack/rdoHeal)が、デザイナの Name と一致しているか確認します。- 別の
GroupBoxに別グループのラジオがあると、両方オンに見える配置になることがあります。今回は 1つのグループだけにします。
「回復で HP が上限を超える」
- 上限処理は
Player.Healの中に書くと、フォームを何枚も増やしたときに安全です(1か所にルールを集約)。
発展アイデア(次の記事へ)
- 防御コマンド(1ターンだけダメージ半減)… フラグが増えるので「状態」をどこに持つかが課題
BattleManagerクラスにDoAttack/DoHealを移し、フォームは UI だけにする → 第4回
シリーズ全体の目次は WinFormsでRPG入門 〜Unityへ繋がる設計の考え方〜(固定ページ) を参照してください。
まとめ
- コマンド選択は UI(ラジオなど)で「状態」を決め、
ifで処理を振り分ける形が分かりやすい - 回復のように ルールがある処理は
Playerのメソッドに寄せると、フォームが読みやすくなる - Unity でも 同じ分岐構造を、入力と表示だけ差し替えて使える
- 次回: クラス分割(
BattleManagerなど)