STEP13:ラストボス戦の実装(マスタードラゴン・専用魔法・エンディング導入)

STEP13では、いよいよラストボス「マスタードラゴン」を実装します。
通常の敵とは“格”が違う演出にしたいので、今回は次をまとめて入れます。

  • マスタードラゴン専用テンプレート(固定出現)
  • 専用魔法(強力攻撃/全体攻撃/自己強化など)
  • 撃破したらエンディングへ導入(ui層のエンディング表示)

実装の全体像(どの層を触る?)

追加/変更内容
enemies層マスタードラゴンのテンプレート追加、専用魔法定義、生成関数追加
battle層「ボス戦」用の戦闘開始、専用行動(演出・全体攻撃など)対応
map層ボス部屋/ボス出現トリガ(例:特定座標・最深部)
ui層撃破後にエンディング導入(ending画面へ)

このSTEPでは「ボス戦はイベント」なので、ランダムエンカウントとは別ルートで呼び出すのが扱いやすいです。

enemies層:マスタードラゴンのテンプレートを追加

敵IDに「ボス」を追加

enemies.h(例)

typedef enum {
    ENEMY_SLIME = 0,
    ENEMY_GOBLIN,
    ENEMY_WOLF,
    ENEMY_MAGE,
    ENEMY_GOLEM,
    ENEMY_MASTER_DRAGON  /* 追加 */
} EnemyId;

Enemy enemies_create(EnemyId id);
Enemy enemies_create_boss_master_dragon(void); /* 追加 */

マスタードラゴンのパラメータ設計(例)

項目値(例)意図
max_hp900長期戦
atk55通常攻撃でも痛い
def30物理が通りにくい
mag60魔法が脅威
mdef25魔法でも削れるが簡単ではない
max_mp180専用魔法連打できる
ai_heal_threshold40%回復優先がやや早い

※数値はあなたの既存バランスに合わせて調整してOKです。

専用魔法を定義する

ここでは「敵専用魔法」を以下3種類にします。

魔法名種類対象効果
Dragon Breathダメージ全体炎の全体攻撃
Ancient Roar強化自分攻撃力アップ(数ターン)
Dark Regeneration回復自分大回復

全体攻撃を入れることで「ラスボス感」が一気に出ます。

enemies.c(例:生成)

#include "enemies.h"
#include <string.h>

static void set_spell(Spell* s, const char* name, int type, int mp, int power) {
    strncpy(s->name, name, sizeof(s->name)-1);
    s->name[sizeof(s->name)-1] = '\0';
    s->type = type;        /* SPELL_DMG / SPELL_HEAL / SPELL_BUFF */
    s->mp_cost = mp;
    s->power = power;
}

Enemy enemies_create_boss_master_dragon(void) {
    Enemy e;
    memset(&e, 0, sizeof(e));

    strcpy(e.name, "Master Dragon");
    e.max_hp = 900; e.hp = e.max_hp;

    e.atk = 55;
    e.def = 30;

    e.mag = 60;
    e.mdef = 25;

    e.max_mp = 180; e.mp = e.max_mp;

    e.defending = 0;

    /* 専用魔法 */
    set_spell(&e.spells[0], "Dragon Breath", SPELL_DMG, 25, 22); /* 全体攻撃に使う */
    set_spell(&e.spells[1], "Ancient Roar",  SPELL_BUFF, 18, 20);/* 自己強化 */
    set_spell(&e.spells[2], "Dark Regen",    SPELL_HEAL, 20, 180);/* 大回復 */
    /* 残りは空 */

    e.kind = ENEMY_MASTER_DRAGON;

    return e;
}

battle層:ボス専用の戦闘処理を追加

「ボス戦開始」関数を用意

battle.h

int battle_boss_master_dragon(Character party[PARTY_SIZE]);

戻り値は例えば

  • 1:勝利
  • 0:敗北(全滅)

にしておくと、呼び出し側(map/main)が分岐しやすいです。

全体攻撃対応(Dragon Breath)

既存の魔法が単体前提なら、battle側で“全体適用”にするのが簡単です。
Spellに target を追加できるならベストですが、まずは「魔法名」や「kind」で分岐してもOKです。

battle.c(例:ドラゴンブレス実装)

static void boss_cast_dragon_breath(Enemy* boss, Character party[PARTY_SIZE]) {
    Spell sp = boss->spells[0]; /* Dragon Breath */

    boss->mp -= sp.mp_cost;

    printf("Master Dragon uses DRAGON BREATH!!\n");
    sfx_beep(1);

    for (int i = 0; i < PARTY_SIZE; i++) {
        if (!party[i].alive) continue;

        int base = boss->mag * sp.power - party[i].mdef;
        if (base < 1) base = 1;

        int dmg = apply_variance(base); /* 既存のブレ関数がある想定 */
        apply_damage_to_char(&party[i], dmg);

        printf("%s takes %d damage!\n", party[i].name, dmg);
    }
}

自己強化(Ancient Roar)を実装

強化は「一時的な補正」を battle側に持つと良いです。
例:Enemyに buff_atk_turns と buff_atk_rate を持たせる、もしくは battle側で管理します。

ここでは最小で boss->atk を一定ターンだけ上げる方式にします。

static void boss_cast_ancient_roar(Enemy* boss) {
    Spell sp = boss->spells[1];

    boss->mp -= sp.mp_cost;

    boss->buff_atk_turns = 3;  /* 3ターン攻撃上昇 */
    boss->buff_atk_rate = 130; /* 130% */

    printf("Master Dragon uses ANCIENT ROAR! ATK up!\n");
    sfx_beep(0);
}

敵の攻撃計算で

int atk = boss->atk;
if (boss->buff_atk_turns > 0) atk = atk * boss->buff_atk_rate / 100;

のように適用します。
ターン終了時に buff_atk_turns-- を忘れずに。

大回復(Dark Regen)

STEP12で回復魔法は作っているはずなので、boss専用で「回復量が大きい」だけにします。

ボスAI:専用ロジックを用意する

通常敵AI(STEP12)のままでも動きますが、ラスボスは「パターン」があると面白いです。

今回のボスAI例:

  • HP 50%以下:回復(Dark Regen)を狙う確率アップ
  • 3ターン周期:Dragon Breath(全体)を混ぜる。
  • 強化が切れている:Ancient Roar をたまに使う。
  • それ以外:通常攻撃

battle.c 内にボス専用判断(例)

static int boss_hp_percent(const Enemy* b) {
    return (b->hp * 100) / b->max_hp;
}

static void boss_take_turn(Enemy* boss, Character party[PARTY_SIZE], int turn) {
    int hp = boss_hp_percent(boss);

    /* 1) 周期ブレス:3ターンごとに全体攻撃(MPあるなら) */
    if (turn % 3 == 0 && boss->mp >= boss->spells[0].mp_cost) {
        boss_cast_dragon_breath(boss, party);
        return;
    }

    /* 2) 強化が切れているなら自己強化(20%) */
    if (boss->buff_atk_turns <= 0 && boss->mp >= boss->spells[1].mp_cost) {
        if ((rand() % 100) < 20) {
            boss_cast_ancient_roar(boss);
            return;
        }
    }

    /* 3) HP半分以下なら回復優先(35%) */
    if (hp <= 50 && boss->mp >= boss->spells[2].mp_cost) {
        if ((rand() % 100) < 35) {
            cast_enemy_heal_spell(boss, 2); /* 既存の回復処理を流用する想定 */
            return;
        }
    }

    /* 4) 通常攻撃 */
    enemy_attack_party(boss, party);
}

map層:ボス部屋に到達したらボス戦へ

例:マップ最深部(座標 23,23)に到達したらボス戦開始。

if (player.x == 23 && player.y == 23) {
    ui_message("A terrifying presence fills the air...");
    int win = battle_boss_master_dragon(party);
    if (win) {
        /* エンディングへ */
    } else {
        /* 全滅処理(教会復活に戻す等) */
    }
}

ui層:撃破後のエンディング導入

STEP04でタイトル・エンディングを作っている前提で、ここでは

  • 「勝利演出」→「世界が救われた」→「The End」

を短いテキストでつなげると良いです。

ui.h

void ui_show_ending_intro(void);
void ui_show_ending(void);

ui.c(例)

void ui_show_ending_intro(void) {
    clear_screen();
    printf("The Master Dragon has fallen...\n");
    printf("A warm light spreads across the land.\n");
    printf("Your journey is finally over.\n");
    wait_key();
}

ボス勝利後に

ui_show_ending_intro();
ui_show_ending();

で締めればOKです。

演習問題+模範解答例

演習:ボスの「怒りモード」を追加する

問題
マスタードラゴンのHPが25%以下になったら、毎ターン Dragon Breath を撃つ確率を 50% にしてください。

模範解答例(boss_take_turn 冒頭に追加)

int hp = boss_hp_percent(boss);

if (hp <= 25 && boss->mp >= boss->spells[0].mp_cost) {
    if ((rand() % 100) < 50) {
        boss_cast_dragon_breath(boss, party);
        return;
    }
}

STEP13のまとめ

  • ボスは「固定出現+専用テンプレート」で演出を作る。
  • 専用魔法(全体攻撃/自己強化/大回復)で格の違いを出す。
  • ボスAIは通常敵AIとは分けて「周期」「条件分岐」を入れると面白い。
  • 撃破後にui層のエンディング導入へ接続して、物語を閉じる。