
C言語入門|STEP16:ターン制バトルの内部構造-コマンド入力と戦闘ループの流れ
はじめに:RPGの心臓部がバトル処理
STEP15では、敵キャラクターとAIの実装を解説しました。
STEP16では、いよいよ バトル全体を制御しているロジック を読み解いていきます。
ターン制バトルは、
- 味方が行動
- 敵が行動
- 勝敗を判定
という流れを、
何度も繰り返す構造 になっています。
一見複雑そうに見えますが、
実はとても整理されたループ構造です。
バトル処理は battle 関数に集約されている
バトルは、battle 関数1つで完結しています。
static int battle(GameState* gs, Enemy enemy)この関数の役割
| 内容 | 説明 |
|---|---|
| 戦闘開始 | 敵出現の演出 |
| ターン制御 | 味方→敵の行動 |
| 勝敗判定 | 勝利・全滅 |
| 後処理 | 経験値・画面遷移 |
戻り値は、
- 1:勝利
- 0:敗北
というシンプルな設計です。
戦闘ループの基本構造
バトルの中心となるのが、
次の while ループです。
while (enemy.hp > 0) {
// 味方ターン
// 敵ターン
}この条件の意味
- 敵のHPが0になるまで戦闘継続
- 敵が倒れたらループ終了
勝利条件が明確なので、
処理の流れがとても分かりやすくなっています。
パーティ全滅チェックを最初に行う理由
ループの冒頭で、
パーティが全滅していないかを確認しています。
int alive_cnt = 0;
for (int i = 0; i < PARTY_SIZE; i++) {
if (gs->party[i].alive) alive_cnt++;
}なぜ最初に確認するのか
- これ以上戦闘を続ける意味がない。
- 無駄な処理を防ぐ。
- 即ゲームオーバーへ移行できる。
失敗条件を早めに判定する のは、
安定したロジックの基本です。
ステータス表示は毎ターン行う
戦闘中は、
毎ターンごとに状態を表示しています。
print_party_status(gs->party);
print_enemy_status(&enemy);これがあることで
- 現在HP・MPが分かる。
- 防御状態が確認できる。
- 次の行動を考えやすい。
プレイヤーが
判断しながら戦える設計 になっています。
味方の行動順を固定している理由
味方の行動は、
配列の先頭から順番に処理されます。
for (int i = 0; i < PARTY_SIZE; i++) {
Character* c = &gs->party[i];
if (!c->alive) continue;固定順のメリット
- 実装がシンプル
- 行動順が分かりやすい。
- 学習用として理解しやすい。
速度や素早さを入れなくても、
十分RPGらしさは出せます。
コマンド選択の仕組み
各キャラのターンで、
次のような選択肢が表示されます。
printf("1:戦う 2:魔法 3:アイテム 4:防御\n");コマンド設計のポイント
| コマンド | 内容 |
|---|---|
| 戦う | 通常攻撃 |
| 魔法 | Spell 処理 |
| アイテム | 使用・装備 |
| 防御 | ダメージ軽減 |
RPGの基本要素がすべて揃った構成 です。
入力が不正だった場合の保険処理
入力が不正だった場合は、
通常攻撃にフォールバックしています。
printf("迷っている…(通常攻撃)\n");なぜこうしているのか
- 入力ミスで進行不能にならない。
- テストプレイが楽になる。
- ゲームが止まらない。
学習用RPGとして、
多少のミスを許容する設計 になっています。
防御状態を毎ターン解除している理由
味方ターンの開始時に、
防御フラグをリセットしています。
c->defending = 0;この処理の意味
- 防御は次の攻撃1回分だけ。
- 永続防御を防ぐ。
- 状態管理を簡単にする。
状態効果の持続時間を
1ターンに限定 することで、
ロジックがとても分かりやすくなります。
敵ターン処理の流れ
味方全員の行動が終わったら、
敵の行動に移ります。
if (enemy_try_cast_spell(&enemy, gs->party)) {
// 魔法行動
} else {
// 通常攻撃
}ここでやっていること
- 魔法を使えるか判断
- 使えなければ通常攻撃
敵の行動も、
1ターンに1回だけ 行われます。
ダメージ処理と戦闘不能判定
攻撃後には、
必ずHPと alive を更新しています。
if (gs->party[t].hp <= 0) {
gs->party[t].hp = 0;
gs->party[t].alive = 0;
}
なぜ alive フラグが必要か
- HPが0でも復活前か判定できる。
- 行動可否の判定に使える。
HPと状態を分けて管理することで、
処理が安定します。
バトル終了後の分岐処理
敵を倒したあとには、
勝利時専用の処理が行われます。
if (!enemy.is_boss) {
add_exp_party(gs->party, enemy.exp);
wait_enter_to_return();
}ボス戦の場合は、
エンディング専用の流れに進みます。
ターン制バトルがきれいに動く理由
ここまで見てきたように、
このバトル処理は、
- ループ
- 分岐
- 関数分割
がとても整理されています。
1ターンでやることが明確 なので、
コードを追いやすく、修正もしやすい構造です。
STEP16で押さえておきたいポイントまとめ
STEP16の重要ポイントはこちらです。
- battle 関数が戦闘の司令塔
- ターン制は while ループで表現する
- 味方→敵の順で処理を進める
- 入力ミスへの保険処理が重要
- 防御や状態はフラグで管理する
