WinFormsで「クラス分けしないバトル」アプリを作ろう
(チーム開発[リーダー+メンバー3名]+Git/Designerドラッグ&ドロップ前提)
目的
- C# / WinForms でシンプルなバトルを作成
- GitHub Desktop を使った順番作業で、初めてでも安全にチーム開発を体験
- UI(Designer)変更は担当者だけが行い、*.Designer.cs の競合を回避
誰が Designer を触るか(早見表)
ステップ | Designer変更 | 実施者 |
---|---|---|
STEP0 | lblPlayer, lblEnemy, txtLog, btnAttack を配置 | リーダーのみ |
STEP0M | なし(Clone & Run) | 全メンバー |
STEP1 | btnHeal を追加 | 担当Aのみ |
STEP2 | なし(コードのみ) | 担当B |
STEP3 | txtLog を削除し lstLog を追加 | 担当Cのみ |
ルール:作業前 Pull → 作業 → Commit → Push。複数人が同時にDesignerを触らない。
STEP0(リーダー・必須):初期リポジトリと雛形作成
- Visual Studio で Windows フォーム アプリ(.NET) を新規作成
この時点で最初のGit初期化を行なっておきます - ツールボックスからドラッグ&ドロップで以下を配置し、Name を設定
- Label:lblPlayer(プレイヤーHP)
- Label:lblEnemy(敵HP)
- TextBox:txtLog(Multiline=true, ReadOnly=true, ScrollBars=Vertical)
- Button:btnAttack(攻撃)
- 下記の Form1.cs(STEP0版) を貼り付け
- GitHub へ初回 Push(以降は順番作業)
Initial commit
(リーダー) デザイナーで基本のコントロールを配置
(リーダー) Form1クラスの実装
(担当A) 攻撃ボタンの追加
(担当A) 攻撃クリックイベントを実装
(担当A) 回復ボタンの追加
(担当A) 回復処理を実装
(担当B) 敵キャラクターのクリティカル攻撃(20%)を実装
(担当C) 戦闘ログをListBoxに履歴化
(リーダー) ビジュアル調整(結果表示エリアを拡大)
コミット履歴の説明
1. Initial commit
最初のリポジトリ作成時のコミットです。
- 空のプロジェクトファイルやソリューション構成がここで登録されます。
- 実際の開発作業のスタート地点になります。
2. (リーダー) デザイナーで基本のコントロールを配置
リーダーの役割:共通の土台を整える。
- Visual Studio のデザイナーを使い、ボタン・ラベル・ListBoxなど、全員が共通で使うUI部品を配置。
- この段階で「どの画面に何を置くか」が決まり、他のメンバーはここから分担作業に入れる。
3. (リーダー) Form1クラスの実装
リーダーが フォームの基本クラス を実装。
- Form1 にイベントハンドラの枠(攻撃ボタンクリック・回復ボタンクリックなど)を用意。
- 開発チームが迷わないように、共通のコード構造を最初に用意しておく。
4. (担当A) 攻撃ボタンの追加
担当Aが UIの一部(攻撃ボタン) を追加。
- デザイナー上に「攻撃」ボタンを設置。
- 見た目だけで、まだ処理は書かれていない段階。
5. (担当A) 攻撃クリックイベントを実装
攻撃ボタンに 処理を実装。
- プレイヤーが攻撃する処理を Click イベントに追加。
- 例: 敵のHPを減らす処理。
6. (担当A) 回復ボタンの追加
同様に、回復用のボタン を配置。
- UI要素を追加する段階。
- ボタンが押せる見た目になる。
7. (担当A) 回復処理を実装
回復ボタンのクリックイベントに 処理を実装。
- プレイヤーのHPを回復する処理を追加。
- 「UI追加」と「処理実装」を分けてコミットしている点が教材としてわかりやすい。
8. (担当B) 敵キャラクターのクリティカル攻撃(20%)を実装
担当Bが 敵側の特殊行動 を実装。
- 敵の攻撃時に 20% の確率でダメージ2倍(クリティカル)になる処理を追加。
- プレイヤーと敵の戦闘がよりゲームらしくなる。
9. (担当C) 戦闘ログをListBoxに履歴化
担当Cが 戦闘ログ機能 を追加。
- 攻撃や回復の結果を文字列として ListBox に追加。
- 例: 「プレイヤーが攻撃した(20ダメージ)」「敵がクリティカル攻撃!(40ダメージ)」
- UIに履歴が残ることで、戦闘の進行が分かりやすくなる。
10. (リーダー) ビジュアル調整(結果表示エリアを拡大)
最後にリーダーが 見た目を調整。
- 戦闘結果表示部分のエリアを大きくして、ログが見やすくなるように修正。
- 開発終盤でUIの調整をまとめて行い、全体を仕上げている。
全体の流れ
- リーダーが環境を整備(コントロール配置、Form1実装)
- 担当ごとに機能を追加(A=攻撃・回復、B=クリティカル、C=ログ)
- リーダーが最終調整(UI改善)
Form1.cs(STEP0・丸ごと)
GitHub Desktopの表示で文字化けを解消する作業をしておきます
using System;
using System.Windows.Forms;
namespace BattleApp
{
public partial class Form1 : Form
{
private int _playerHp = 30;
private int _enemyHp = 20;
private readonly Random _rand = new Random();
public Form1()
{
InitializeComponent();
UpdateStatus();
AppendLog("バトルスタート!");
}
private void btnAttack_Click(object sender, EventArgs e)
{
if (!BattleOngoing())
{
return;
}
// プレイヤー攻撃
int damage = _rand.Next(5, 10);
_enemyHp = Math.Max(0, _enemyHp - damage);
AppendLog($"プレイヤーの攻撃! {damage} のダメージ!");
UpdateStatus();
if (_enemyHp <= 0)
{
AppendLog("敵を倒した! 勝利!");
btnAttack.Enabled = false;
return;
}
// 敵の反撃(HP減算→残HP表示)
int enemyDamage = _rand.Next(3, 8);
_playerHp = Math.Max(0, _playerHp - enemyDamage);
AppendLog($"敵の攻撃! {enemyDamage} のダメージ!\nプレイヤーHP: {_playerHp}");
UpdateStatus();
if (_playerHp <= 0)
{
AppendLog("プレイヤーは倒れた… 敗北…");
btnAttack.Enabled = false;
}
}
private bool BattleOngoing()
{
return _playerHp > 0 && _enemyHp > 0;
}
private void UpdateStatus()
{
lblPlayer.Text = $"プレイヤーHP: {_playerHp}";
lblEnemy.Text = $"敵HP: {_enemyHp}";
}
private void AppendLog(string line)
{
txtLog.AppendText(line + Environment.NewLine);
}
}
}
STEP0L(リーダー・必須):メンバーをプロジェクトに参加させる
チーム内でプロジェクトを共有するため、メンバーをコラボレータとして追加します
添付リンクの、1. プロジェクトの作成とリポジトリ初期化(リーダー担当: Member A)を進めます
STEP0M(メンバー・必須):クローン&実行確認
上記の添付リンクの、2. チームメンバーによるリポジトリのクローン(Member B, Member C)を進めます
- GitHub Desktop で Clone → Visual Studio で ビルド&実行
- STEP0 が動くことを確認(OKなら次の担当へ)
- 以後は必ず 作業前 Pull/作業後 Commit→Push
STEP1(担当A):回復ボタンを追加(3回まで)
- Designer で Button:btnHeal を追加(テキスト「かいふく」)
- Click イベント(btnHeal_Click)を生成
- 下記 Form1.cs(STEP1版) に貼り替え
- Commit → Push、他メンバーは Pull
Form1.cs(STEP1・丸ごと)
using System;
using System.Windows.Forms;
namespace BattleApp
{
public partial class Form1 : Form
{
private int _playerHp = 30;
private int _enemyHp = 20;
private int _healLeft = 3;
private readonly Random _rand = new Random();
public Form1()
{
InitializeComponent();
UpdateStatus();
UpdateHealButtonText();
AppendLog("バトルスタート!");
}
private void btnAttack_Click(object sender, EventArgs e)
{
if (!BattleOngoing())
{
return;
}
int damage = _rand.Next(5, 10);
_enemyHp = Math.Max(0, _enemyHp - damage);
AppendLog($"プレイヤーの攻撃! {damage} のダメージ!");
UpdateStatus();
if (_enemyHp <= 0)
{
AppendLog("敵を倒した! 勝利!");
SetCommandsEnabled(false);
return;
}
EnemyTurnBasic();
}
private void btnHeal_Click(object sender, EventArgs e)
{
if (!BattleOngoing() || _healLeft <= 0)
{
return;
}
int heal = _rand.Next(6, 12);
_playerHp += heal;
_healLeft--;
AppendLog($"プレイヤーは {heal} かいふくした!\nプレイヤーHP: {_playerHp}");
UpdateStatus();
UpdateHealButtonText();
if (BattleOngoing())
{
EnemyTurnBasic();
}
}
private void EnemyTurnBasic()
{
int enemyDamage = _rand.Next(3, 8);
_playerHp = Math.Max(0, _playerHp - enemyDamage);
AppendLog($"敵の攻撃! {enemyDamage} のダメージ!\nプレイヤーHP: {_playerHp}");
UpdateStatus();
if (_playerHp <= 0)
{
AppendLog("プレイヤーは倒れた… 敗北…");
SetCommandsEnabled(false);
}
}
private bool BattleOngoing()
{
return _playerHp > 0 && _enemyHp > 0;
}
private void UpdateStatus()
{
lblPlayer.Text = $"プレイヤーHP: {_playerHp}";
lblEnemy.Text = $"敵HP: {_enemyHp}";
}
private void SetCommandsEnabled(bool enabled)
{
btnAttack.Enabled = enabled;
btnHeal.Enabled = enabled && _healLeft > 0;
}
private void UpdateHealButtonText()
{
btnHeal.Text = $"かいふく({_healLeft})";
}
private void AppendLog(string line)
{
txtLog.AppendText(line + Environment.NewLine);
}
}
}
STEP2(担当B):敵のクリティカル(20%)を追加
- Designer変更はなし(コードのみ)
- クリティカル時は 10〜15 ダメージ/通常は 3〜8 ダメージ
- どちらも HP減算→残HPを表示 の順でログ出力
Form1.cs(STEP2・丸ごと)
using System;
using System.Windows.Forms;
namespace BattleApp
{
public partial class Form1 : Form
{
private int _playerHp = 30;
private int _enemyHp = 20;
private int _healLeft = 3;
private readonly Random _rand = new Random();
public Form1()
{
InitializeComponent();
UpdateStatus();
UpdateHealButtonText();
AppendLog("バトルスタート!");
}
private void btnAttack_Click(object sender, EventArgs e)
{
if (!BattleOngoing())
{
return;
}
int damage = _rand.Next(5, 10);
_enemyHp = Math.Max(0, _enemyHp - damage);
AppendLog($"プレイヤーの攻撃! {damage} のダメージ!");
UpdateStatus();
if (_enemyHp <= 0)
{
AppendLog("敵を倒した! 勝利!");
SetCommandsEnabled(false);
return;
}
EnemyTurnWithCritical();
}
private void btnHeal_Click(object sender, EventArgs e)
{
if (!BattleOngoing() || _healLeft <= 0)
{
return;
}
int heal = _rand.Next(6, 12);
_playerHp += heal;
_healLeft--;
AppendLog($"プレイヤーは {heal} かいふくした!\nプレイヤーHP: {_playerHp}");
UpdateStatus();
UpdateHealButtonText();
if (BattleOngoing())
{
EnemyTurnWithCritical();
}
}
private void EnemyTurnWithCritical()
{
bool isCritical = (_rand.Next(0, 5) == 0); // 20%
int enemyDamage = isCritical ? _rand.Next(10, 15) : _rand.Next(3, 8);
_playerHp = Math.Max(0, _playerHp - enemyDamage);
if (isCritical)
{
AppendLog($"敵のクリティカルヒット! {enemyDamage} の大ダメージ!\nプレイヤーHP: {_playerHp}");
}
else
{
AppendLog($"敵の攻撃! {enemyDamage} のダメージ!\nプレイヤーHP: {_playerHp}");
}
UpdateStatus();
if (_playerHp <= 0)
{
AppendLog("プレイヤーは倒れた… 敗北…");
SetCommandsEnabled(false);
}
}
private bool BattleOngoing()
{
return _playerHp > 0 && _enemyHp > 0;
}
private void UpdateStatus()
{
lblPlayer.Text = $"プレイヤーHP: {_playerHp}";
lblEnemy.Text = $"敵HP: {_enemyHp}";
}
private void SetCommandsEnabled(bool enabled)
{
btnAttack.Enabled = enabled;
btnHeal.Enabled = enabled && _healLeft > 0;
}
private void UpdateHealButtonText()
{
btnHeal.Text = $"かいふく({_healLeft})";
}
private void AppendLog(string line)
{
txtLog.AppendText(line + Environment.NewLine);
}
}
}
STEP3(担当C):ログを履歴化(ListBoxへ)
- Designer で txtLog を削除し、ListBox:lstLog を追加
- 下記の Form1.cs(STEP3版) に貼り替え
- Commit → Push、他メンバーは Pull
STEP3は「ログを1行=1イベントとして扱えるUIへ差し替える」練習で、学習上とチーム開発上の狙いがあります。
- 可読性・操作性の向上:TextBoxだと長文が流れますが、ListBoxなら「1行=1イベント」で視認・選択・コピーがしやすい(スクロールも最新行へ追従)。記事でも「txtLog を削除→ lstLog を追加」という明確な置換手順になっています。
- UIリファクタリングの体験:AppendLog を経由する設計にしているため、表示部品を差し替えても呼び出し側をほとんど直さずに済む=抽象化の効果を体感できます。実際にSTEP3のコードではListBox版の AppendLog に置き換えています。
- チーム開発でのDesigner変更の責任分担:Designerは競合しやすいので、担当CだけがUI変更を行う段取りにして、他メンバーはPullで確認のみ→.Designer.csの競合回避を学ぶ意図があります(早見表でもSTEP3は担当Cのみと明記)。
- 拡張の布石:将来、フィルタ(ダメージだけ表示)/重要度別表示/ログ保存などをやりやすい。ListBoxなら選択行からメタ情報にアクセスしやすく、DataBindingへの拡張も自然です。
- 教材の一貫性:STEP0でtxtLogのプロパティはDesignerで設定する方針を明示しており(Multiline/ReadOnly/ScrollBars)、STEP3でUIコンポーネントを安全に差し替える流れに繋がります。
いつTextBoxのままにする?
- ログを自由編集したい、1つの大きなテキストとして保存したい、といった用途ならTextBox継続もアリです。ただ、今回の「イベントログ」としてはListBoxがハマります。
まとめると、STEP3は“UIを安全に差し替える設計と、チームでのDesigner変更の進め方”を学ぶための意図的なステップです。
Form1.cs(STEP3・丸ごと)
using System;
using System.Windows.Forms;
namespace BattleApp
{
public partial class Form1 : Form
{
private int _playerHp = 30;
private int _enemyHp = 20;
private int _healLeft = 3;
private readonly Random _rand = new Random();
public Form1()
{
InitializeComponent();
UpdateStatus();
UpdateHealButtonText();
AppendLog("バトルスタート!");
}
private void btnAttack_Click(object sender, EventArgs e)
{
if (!BattleOngoing())
{
return;
}
int damage = _rand.Next(5, 10);
_enemyHp = Math.Max(0, _enemyHp - damage);
AppendLog($"プレイヤーの攻撃! {damage} のダメージ!");
UpdateStatus();
if (_enemyHp <= 0)
{
AppendLog("敵を倒した! 勝利!");
SetCommandsEnabled(false);
return;
}
EnemyTurnWithCritical();
}
private void btnHeal_Click(object sender, EventArgs e)
{
if (!BattleOngoing() || _healLeft <= 0)
{
return;
}
int heal = _rand.Next(6, 12);
_playerHp += heal;
_healLeft--;
AppendLog($"プレイヤーは {heal} かいふくした!\nプレイヤーHP: {_playerHp}");
UpdateStatus();
UpdateHealButtonText();
if (BattleOngoing())
{
EnemyTurnWithCritical();
}
}
private void EnemyTurnWithCritical()
{
bool isCritical = (_rand.Next(0, 5) == 0); // 20%
int enemyDamage = isCritical ? _rand.Next(10, 15) : _rand.Next(3, 8);
_playerHp = Math.Max(0, _playerHp - enemyDamage);
if (isCritical)
{
AppendLog($"敵のクリティカルヒット! {enemyDamage} の大ダメージ!\nプレイヤーHP: {_playerHp}");
}
else
{
AppendLog($"敵の攻撃! {enemyDamage} のダメージ!\nプレイヤーHP: {_playerHp}");
}
UpdateStatus();
if (_playerHp <= 0)
{
AppendLog("プレイヤーは倒れた… 敗北…");
SetCommandsEnabled(false);
}
}
private bool BattleOngoing()
{
return _playerHp > 0 && _enemyHp > 0;
}
private void UpdateStatus()
{
lblPlayer.Text = $"プレイヤーHP: {_playerHp}";
lblEnemy.Text = $"敵HP: {_enemyHp}";
}
private void SetCommandsEnabled(bool enabled)
{
btnAttack.Enabled = enabled;
btnHeal.Enabled = enabled && _healLeft > 0;
}
private void UpdateHealButtonText()
{
btnHeal.Text = $"かいふく({_healLeft})";
}
private void AppendLog(string line)
{
lstLog.Items.Add(line);
lstLog.TopIndex = lstLog.Items.Count - 1; // 常に最新へスクロール
}
}
}
Git/進め方のコツ
- 順番作業:リーダー → A → B → C(各自、作業前 Pull/作業後 Commit→Push)
- コミットは小さく・わかりやすく:「回復追加」「クリティカル実装」など
- Designerの競合を回避:UI変更は担当者だけが行い、他メンバーはPullして確認に徹する
- 競合が出たら:どちらの変更を残すか相談。必要ならDesignerで再配置し直す方が早い場合も
ディスカッション
コメント一覧
まだ、コメントがありません